Skip to content

Phase 7.3b WP5: Qwen3-VL vision tower, mergers and strict HF golden - #58

Merged
perNyfelt merged 23 commits into
mainfrom
phase7-3b-wp5
Oct 23, 2026
Merged

perNyfelt merged 23 commits into
mainfrom
phase7-3b-wp5

Conversation

@perNyfelt

@perNyfelt perNyfelt commented Oct 22, 2026 •

Copy link
Copy Markdown
Member

Summary

Implements Phase 7.3b WP5: Qwen3-VL vision tower (req/plans/phase7-3b-plan.md), per the design recorded in req/plans/phase7-3b-reference-findings.md §11 (committed on this branch).

Lands the vision tower + mergers — the last model-side piece before Qwen3VlModel composition in WP6:

  1. New jmlx-core components (se.alipsa.jmlx.nn): PatchMerger (the 2×2 spatial merge), TwoAxisRope/TwoAxisRotation (interleaved two-axis rotary preparation) and TwoAxisRotaryAttention (the vision block's qkv-projection attention), each with its own strict core tests.
  2. Qwen3VlVisionTower (jmlx-models): patch embed + 3-D interpolated position embeddings (the torch 2.6.0 CPU linspace_kernel ported exactly), N Qwen3VlVisionBlocks, DeepStack level merges + the main merger, returning Qwen3VlVisionFeatures (merged rows + per-level DeepStack rows) hoisted into the tower's scope. Each image runs in a private child scope that closes on return (the SmolVlmModel per-request pattern). A test-only capture sink feeds the golden.
  3. Strict HF intermediate-feature golden: tools/hf-reference/qwen3vl_vision.py (manual, reviewed, locked 4.57.6 venv — never Gradle-invoked) generates block-level captures (norm/attn/mlp outputs, DeepStack + merged rows, position-lookup corner indices) for 4 probe images, plus a tiny synthetic vision checkpoint (HF default init) and the images, all SHA-256-pinned in tools/hf-reference/provenance.json and verified by verifyHfReferenceGoldens.
  4. Qwen3VlVisionTowerGoldenTest (tagged full-float32): every case max diff ≤ 3.1e-6 against the 1e-4 / 7-significant-digit tolerance; 3/3 executed.
  5. Qwen3VlVisionPeakTest (ordinary test): real-2B tower geometry at reduced depth 3, one synthetic 1024×1024 image (grid (1, 64, 64), 4096 patch rows).

Root cause found by the golden

The first golden run diverged at fc1 of block 0: Java applied norm2 to the block input. The pinned 4.57.6 Qwen3VLVisionBlock.forward is two sequential statements —

hidden_states = hidden_states + self.attn(self.norm1(hidden_states), ...)
hidden_states = hidden_states + self.mlp(self.norm2(hidden_states))

— so line 274's norm2(hidden_states) receives x + attn(norm1(x)), the updated residual. A forward-pre-hook on the actual model's blk.norm2 confirmed its input digest equals the attention-added residual, not the block input. Fixed in Qwen3VlVisionBlock.forward; the findings §11.1 verbatim transcription was always correct. (The norm1/norm2 identical-digest anomaly seen during debugging is not a bug: the tiny checkpoint uses HF default init, so every LayerNorm is identity.) The attention path itself verified within the golden's 1e-4 tolerance (measured max diff 3.1e-6) before the fc1 divergence point.

Exit evidence — measured per-image peak (findings §11.4)

Method per findings §11.4 (all statics materialized before the settled baseline so the delta excludes weight materialization):

  • Sustained per-image evaluated peak: 16,777,216 B — exactly 16 MiB: the merged 1024 × 2048 float32 rows plus one DeepStack level, nothing else retained after the per-image scope closes.
  • Transient attention workspace: 335,544,320 B = exactly 320 MiB (0.3125 GiB) over baseline (the bracket runs after every per-request scope closes, so it carries no retained-row offset). MLX's fused scaled-dot-product kernel tiles the score computation — the full 16-head 4096×4096 float32 score matrix (1,073,741,824 B = 1 GiB) is never resident.
  • Environment: M5 Max, ordinary test task (TF32 default mode). Both figures are far below the plan §4 5 GiB planning bound.

CI

The "Assert required native suites executed" step gains Qwen3VlVisionTowerGoldenTest (jmlx-models list + float32_suites, so it is required in :jmlx-models:float32GoldenTest) and Qwen3VlVisionPeakTest (jmlx-models list, ordinary test).

Validation (all local, green)

  • :jmlx-core:check (432 test / 30 float32, 0 failures; the single skip is the pre-existing MoeMlpBenchmarkTest), :jmlx-models:check (383 test / 50 float32, 0 failures), root verifyHfReferenceGoldens, spotlessApply clean, git diff --check clean.
  • Result XMLs (unfiltered reruns): Qwen3VlVisionTowerGoldenTest 3/3 in float32GoldenTest, Qwen3VlVisionPeakTest 1/1 in test; the CI-suite assertion logic passes against them.
  • No golden/fixture regeneration: Java was made to match the goldens; committed digests verified unchanged by verifyHfReferenceGoldens.
  • No native pins, no generated bindings, no Gradle task changes.

Docs

  • Findings §11.1–§11.4: verbatim 4.57.6 block source, golden schema/provenance, measurement method and the measured peak (replacing PEAK-TBD).
  • AGENTS.md: WP5 paragraph in "Phase 7.3b additions".

Explicitly deferred

  • Qwen3VlModel composition, loader dispatch and the DeepStack consumption of VisualFeatureInjection → WP6 (plan §7); generation/lifecycle/cache policy → WP7.

…xisRotaryAttention core components with tests
…trict HF intermediate-feature golden and tiny checkpoint

The block applies the pinned 4.57.6 sequential semantics: attention sees
norm1(input), then MLP sees norm2 of the updated residual. The golden is
generated by tools/hf-reference/qwen3vl_vision.py (manual, reviewed step)
and digest-pinned in tools/hf-reference/provenance.json;
Qwen3VlVisionTowerGoldenTest runs it in the full-float32 task.
…anning bound

Real-2B tower geometry at reduced depth 3 with one DeepStack level,
constant weights, one 1024x1024 synthetic image (grid (1,64,64)).
All statics are materialized before the settled baseline so the delta
excludes weight materialization. Measured: sustained 16 MiB, attention
transient about 0.33 GiB on an M5 Max.
…equire the new suites in CI

Fills findings 11.4 PEAK-TBD with the measured M5 Max run (sustained
16 MiB, attention transient about 0.33 GiB, both far under the 5 GiB
bound). Qwen3VlVisionTowerGoldenTest joins the required native suites
and the full-float32 suites; Qwen3VlVisionPeakTest joins the required
native suites. AGENTS.md gains the WP5 status paragraph.
…uest target scope and release multi-image parts after the evaluated concatenation
…es so the strict golden covers norm wiring

The reference model init left all 16 LayerNorms at their PyTorch default
(weight 1, bias 0), so the strict golden could not catch a norm1/norm2
swap, a weight/bias swap, the wrong norm on the merger versus a DeepStack
merger, or a norm-key typo in visionKeyToTowerKey. The generator now
perturbs every norm affine before saving (weight ~ N(1.0, 0.1), bias
~ N(0, 0.05), from a dedicated seed-20261021 generator isolated from the
model-init global RNG), records the perturbation in the golden
provenance, and asserts the expected norm count. Regenerated the
checkpoint and golden (reproducible across runs), re-pinned the digests,
and recorded the perturbation in findings 11.3. Java matched the new
golden unchanged: no latent norm-wiring bug.
…ant weights so it covers attention, RoPE and norms
@perNyfelt
perNyfelt merged commit c6efa64 into main Oct 23, 2026
3 checks passed
@perNyfelt
perNyfelt deleted the phase7-3b-wp5 branch October 23, 2026 16:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant