feat: add Qwen2.5-VL OpenXLA vision path - #920
Conversation
53e0ffb to
b45080d
Compare
|
Rebased onto Post-rebase structural validation:
A fresh post-#923 two-image CUDA oracle was attempted with the existing local 4-bit checkpoint and fixture. Both the newly built binary and the preserved pre-rebase binary fail before numerical comparison while loading Therefore no post-#923 Q4 numeric result is claimed. The diagnostics artifact stays explicitly |
Implement the Qwen2.5-VL windowed vision graph, checkpoint schema, and resident IREE runtime while preserving the shared Qwen-VL prefill contract. Reorder windowed patch groups and rotary positions, isolate local attention windows, restore merger output order, and reject unsupported temporal grids explicitly. Also fix the eager encoder restoration permutation used by non-self-inverse window layouts. Validation: cargo test -p mlxcel-xla qwen2_vl --lib; cargo test -p mlxcel-xla parses_and_validates_explicit_mrope_sections --lib; cargo test --lib restoration_is_the_true_inverse_for_non_self_inverse_permutation; cargo check -p mlxcel-xla --features iree --lib; cargo check --features xla-iree --lib. Refs #866
Keep the root quantization contract for text embeddings while excluding it from the resident vision contract when vision_config.skip_vision=true. This matches the pinned Qwen2.5-VL checkpoint, whose vision tensors have no affine metadata, and prevents its non-group-aligned intermediate size from being rejected without changing quantized Qwen2-VL behavior. Validation: cargo fmt --all -- --check; cargo test -p mlxcel-xla qwen2_vl --lib; cargo check -p mlxcel-xla --features iree --lib; cargo clippy -p mlxcel-xla --features iree --lib --fix --allow-dirty. Refs #866
Compare the eager MLX and resident IREE Qwen2.5-VL window plan, reordered patch embeddings, representative window attention, every configured full-attention layer, merger restoration, prepared M-RoPE, and final logits from one production processor payload. Add diagnostics-only negative runs that force Qwen2-style full attention, identity permutation, and zero vision positions while leaving the production graph and serving outputs unchanged. Validation: cargo fmt --all -- --check; cargo check --example xla_qwen25_vl_window_check --features xla-diagnostics; cargo test -p mlxcel-xla --lib --features diagnostics qwen2; cargo check --features xla-iree --lib; cargo test --lib restoration_is_the_true_inverse_for_non_self_inverse_permutation. Refs #866
Route Qwen-VL auxiliary graphs through the device precision resolver so CUDA demotes contractions to f16 while retaining f32 accumulation and sensitive math. Include the resolved precision in the artifact identity. Expand Qwen2.5-VL diagnostics across layers 24-31 and the final full-attention block substages, and emit complete RMS/error reports before failing the strict oracle.
Move the Qwen2.5-VL block-substage diagnostic seam from the already-divergent final full-attention block to the third-to-last final-interval layer, which is layer 29 for the pinned checkpoint and the first failing boundary in the actual report. Keep final full-layer output coverage unchanged.
Snapshot the eager block substages before loading and invoking the mixed IREE/CUDA runtime. Reject vacuous all-zero RMSNorm references before comparison so the strict report cannot misclassify an invalid lazy capture as a numeric divergence.
Materialize private diagnostic copies at the layer-29 producer boundary before each source feeds the next block substage. This prevents later MLX graph evaluation from reusing intermediate storage while preserving the production graph and strict oracle tolerances.
Read each Qwen2.5-VL block diagnostic into host-owned F32 memory at its producer boundary so later MLX graph evaluation cannot alias or donate the oracle values. Keep the production path and comparison thresholds unchanged, and cover snapshot survival with a synthetic RMSNorm regression.
Characterize the manual eager RMSNorm square-overflow signature and report input/norm statistics at the pre-IREE guard. Production graphs and comparison thresholds remain unchanged.
The layer-23 Q/K/V diagnostics are emitted as rank-3 patch, head, and head-dimension tensors, but the auxiliary runtime declared every non-merger result as a rank-2 hidden-state tensor. IREE therefore rejected output 16 even though the flattened element count matched. Generate the complete ordered diagnostic output descriptor schema from the captured layer layout, retain rank 3 for Q/K/V at invocation, and keep the existing flat owned vectors only after transfer for comparison API compatibility. A regression pins all 25 actual-checkpoint output semantics, ordinals, ranks, and shapes. Validation: Qwen2.5-VL diagnostics tests (9 passed), actual descriptor regression, diagnostics example check, Clippy, formatting, and diff checks. Refs #866
Context: The successful 2d13e47 two-media diagnostic run passes layer 15 but reaches layer 23 with its input already outside tolerance, leaving the first divergence somewhere in layers 16 through 22. Changes: - capture the seven ordered layer outputs between the preceding full-attention boundary and the layer 23 substage probe - expose matching eager and IREE diagnostics and compare them in the strict oracle report - pin the actual-model descriptor labels, ordinals, ranks, and shapes Validation: - cargo fmt --all -- --check - cargo test -p mlxcel-xla --features diagnostics qwen25 -- --nocapture - cargo test -p mlxcel-xla --features diagnostics pre_probe_layers -- --nocapture - cargo check --features xla-diagnostics --example xla_qwen25_vl_window_check - cargo clippy -p mlxcel-xla --features diagnostics --tests --no-deps Refs #866
The matching IREE 3.12 CUDA report at 43f7da7 passes layer 16 but first fails catastrophically at layer 17, so the layer-23 probe is no longer the honest diagnostic boundary. Move the existing ordered substage capture to layer 17, retain only the layer 16 and 17 boundary outputs, rename their public/report schema generically, and pin the actual 27-output descriptor order and ranks. Validation: focused Qwen25 tests, diagnostic boundary test, diagnostics example check, formatting, diff checks, and XLA Clippy. Refs #866
The 93cc1e2 CUDA report passes the layer-17 attention, residual, and second normalization boundaries but first diverges catastrophically at the MLP output, so the next bounded run needs the exact SwiGLU internal seams. Expose gate projection, SiLU gate activation, up projection, gated product, and down projection on eager and IREE diagnostics, derive active output lengths from descriptor shapes, and pin the actual output order and ranks. Validation: focused Qwen25 tests, diagnostic boundary test, diagnostics example check, formatting, diff checks, and XLA Clippy. Refs #866
The 9173a39 CUDA report shows that layer-17 gate and up projections contain sparse outliers before the gated product amplifies them, but the existing evidence does not prove a production correction. Expose diagnostics-only eager F32 dense controls for the gate and up projections using the same norm2 values and checkpoint weights, and persist every comparison's maximum-error index and values so one bounded run can distinguish the native F16 projection from the IREE accumulation contract. Validation: Qwen25 XLA tests, diagnostic boundary test, diagnostics example check, formatting, and diff checks. Refs #866
The 790c5da CUDA report shows native F16 and eager dense-F32 gate/up controls remain close to each other while both retain the same 8/23 failures against IREE, disproving projection result precision as the root cause. Replay the captured IREE layer-17 norm2 values through the same checkpoint gate/up weights after matching the graph's F16 input demotion, then compare those eager dense-F32 controls with the existing IREE outputs without changing the IREE ABI. Validation: Qwen25 XLA tests, diagnostic boundary test, diagnostics example check, formatting, and diff checks. Refs #866
The 89bed56 CUDA report proves that replaying captured IREE norm2 through the same checkpoint gate and up projections passes with zero failures, excluding projection, reduction, and weight semantics and confirming amplification of inherited norm2 drift. The actual checkpoint keeps its patch and block 0 through 17 weights in F16, and eager MLX preserves F16 block inputs, normalization outputs, and residual states. Keep IREE normalization and contraction accumulation in F32, but explicitly round the patch state, both normalization results, and both residual outputs through F16 while retaining the public F32 ABI. Validation: Qwen25 XLA tests, the diagnostic boundary test, the eager 18-block dtype regression, the diagnostics example check, focused Clippy, formatting, and diff checks. Refs #866
The 2b1c1f6 CUDA run preserved patch and layer-15 acceptance but regressed layer 17 from 392 failures at max_abs 104.7239 to 1,380 failures at max_abs 320, the down projection from 338/104.0575 to 1,504/319.2441, and the merger from 170/0.8468 to 974/1.5832. Revert only the coarse patch, normalization, and residual F16 round-trip boundaries while preserving the diagnostics through 89bed56. Matching eager F16 safely requires an operation-by-operation backend rounding contract; no further production approximation or tolerance waiver is justified by current evidence. Validation: Qwen25 XLA tests, diagnostic boundary test, diagnostics example check, formatting, and diff checks. Refs #866
Keep the rebased Qwen2.5-VL diagnostics on the explicit legacy artifact path until the full numeric contract and post-CUDA-kernel oracle are qualified.\n\nRefs #866
b45080d to
02d41a1
Compare
|
Rebased this draft onto The only conflict was the Qwen emitter import boundary. The resolution preserves Validation after rebase:
The PR remains draft. The production-reference CUDA Q4 probe required by #932 |
Current-head CUDA actual at
|
| stage | max_abs | failures |
|---|---|---|
vision.post_full_layer_23_capture_2 |
105.27258 | 586 |
vision.post_layer_17_diagnostic |
104.72388 | 392 |
vision.merger_window_ordered |
0.8468342 | 170 |
So the earlier numbers stand as current-head evidence. A separate single-image run at this
head passes every numeric comparison and then panics at the later negative control
(negative oracle accepted an identity patch permutation, line 883), consistent with the
already-documented weakness of the 224x224 fixture.
Hypothesis implemented and rejected: op-by-op f16 state rounding
Every MLX expected value in the report is exactly representable in float16 at every stage
(patch embedding, each layer output, norm1/norm2, query/key/value, attention context,
residual, gate, silu, up, gated product, down). Every IREE value is full f32. That is the
predicted signature of the contract gap: Precision::F16 demotes only dot_general inputs
and returns f32, while the eager path evaluates an f16 checkpoint in which every op
materializes its result at f16.
That was implemented completely, not coarsely: a Builder::round_state helper applied at
the patch embedding, norm1, norm2, QKV, post-RoPE query and key, the SDPA context, the
attention output projection, both residuals, gate, silu, up, the gated product and the down
projection. Softmax interior stayed f32 to match a fused SDPA, and the merger was excluded
because its outputs are not on the f16 grid.
Measured effect on the two-image fixture, most stages improved substantially:
| stage | before | after |
|---|---|---|
reordered_patch_embedding |
0.003506 | 0.00024 |
post_full_layer_15 |
0.196769 | 0.125 |
post_layer_16_diagnostic |
0.487801 | 0.35938 |
layer_17.norm2 |
0.251740 | 0.125 |
layer_17.query failures |
13 | 1 |
layer_17.value failures |
1 | 0 |
But the massive-activation channel regressed (post_full_layer_23 105.27 to 170.00,
failures 586 to 946), and decisively, the previously passing single-image case regressed
from all comparisons passing to 11 failing stages. The change is therefore rejected and not
included here. The patch is retained outside the repository as negative evidence at
/var/tmp/mlxcel-cuda-qual/pr920/rejected-f16-state-contract.patch.
This is the second refutation of the "round the state" family, after the earlier coarse
four-boundary attempt. Together they rule out intermediate storage dtype as the explanation,
which narrows the remaining search.
Where the divergence actually concentrates
Failures are not at the large-magnitude elements. At |expected| = 2088 the tolerance
atol + rtol*|expected| is about 167, wider than float16's own resolution there. The
failing elements are small-|expected| ones, and the ~1e33 max-relative figures come from
near-zero elements that are not themselves the failures.
The outliers localize to MLP intermediate channel 1727 and hidden channel 849, at the same
patch index within each of the two 100-patch images. The existing controls already showed
that replaying IREE's own norm2 through the dense f32 projection reproduces IREE's gate and
up outputs, so the projections are faithful and the difference enters at the normalization.
A sub-ULP difference in the dominant channel rescales every output of that row through the
RMS, which the wide projection and the SwiGLU product then amplify.
The blocker recorded in the PR body is unchanged, and the multi-media logits/top-1, repeated
multi-image, and mixed continuous-batch cancellation and slot-reuse gates remain outstanding.
This PR stays draft.
TF32 reference policy checked here: immaterial, so the numbers above standI filed on #932 that MLX defaults Re-run at
Every emitted-graph stage is identical to six decimal places. The only values that moved are
That contrast is a clean confirmation of the mechanism: the flag reaches the f32 host controls The blocker stated in the PR body is therefore unchanged, and so is the conclusion from the |
Summary
vision_config.skip_vision=true, while preserving the existing quantized Qwen2-VL pathfast_rms_norm, preventing square overflow from collapsing non-zero rows to zeroValidation
cargo fmt --all -- --checkgit diff --checkcargo test -p mlxcel-xla --features diagnostics qwen25 -- --nocapture(9 passed)cargo test -p mlxcel-xla --features diagnostics diagnostic_layers -- --nocapture(1 passed)cargo check --features xla-diagnostics --example xla_qwen25_vl_window_checkcargo clippy -p mlxcel-xla --features diagnostics --tests --no-deps(passed with existing unrelated warnings)Review hardening and actual-checkpoint evidence
The first pinned CUDA run exposed a startup blocker before native execution: the root 4-bit text quantization contract was inherited by an unquantized Qwen2.5-VL vision tower whose
intermediate_size=3420is not divisible by the textgroup_size=64. Commit5a6ee4b7separates the resident vision contract while preserving text-embedding quantization.Source and actual-oracle comparison then showed that the Qwen vision emitter bypassed the resolved device precision and built every contraction in F32, while the eager checkpoint path uses the F16 text-embedding dtype. Commit
592f96bbapplies the CUDA F16-contraction/F32-sensitive-math policy to production and diagnostic graphs and fingerprints that policy in the compiled artifact identity.The subsequent single-224-image investigation initially appeared to diverge late in the network, but host-owned producer snapshots proved that eager
VisionRMSNormitself collapsed non-zero F16 rows to zero. The manual fallback squared values in F16; magnitudes abovesqrt(65504) ~= 255.94overflowed before the mean, making the reciprocal RMS zero. Commits31b05e91through230e5be4progressively ruled out graph donation and added a focused characterization test.Commit
52313107applies the production fix: eager Qwen2.5-VL now uses MLXfast_rms_norm, matching upstreamnn.RMSNormbehavior and the IREE graph's F32-sensitive normalization contract. The three focused GPU/RMSNorm tests pass. With that fix, the bounded single-224-image actual run passed every positive comparison under the unchanged strictatol=0.08, rtol=0.08contract. Its Qwen2-style-full-attention mutation remained numerically within tolerance for that fixture, so numeric sensitivity alone is no longer treated as a valid structural guard.A separate bounded actual run used two 140x140 checker images, each with
grid_thw=[1,10,10]: 200 active patches in a 256-row bucket, exercising both non-identity window restoration and multiple media. Full/window outputs through layers 0, 7, and 15 passed. The first failure was full-attention layer 23:max_abs=101.309080.4572Layer 24 onward propagated that divergence, so later-layer failures are not independent evidence. The report was persisted at
/tmp/mlxcel-qwen25-vl-multiwindow-multimedia-report.json.Static and focused runtime audits rule out host mask construction for this fixture:
[0,100,200]Commit
c6dccafemakes that structural audit the deterministic negative control and moves the next diagnostic seam to the first observed failure, layer 23. It exposes post-RoPE Q/K, V, the masked attention context before output projection, projected attention, residual, norm, and MLP on both runtimes. Diagnostic outputs validate the full bucket, including padding, before truncating to the 200 active patches. Production arithmetic, tolerances, and output contracts are unchanged.The first two-media
local-syncinvocation atc6dccafedid not reach numerical comparison because the new diagnostic ABI exposed a host descriptor defect:xla_auxreported output 16 as rank3/2, type553648160/553648160, and elements327680/327680. Output 16 is the layer-23 post-RoPE query. StableHLO correctly returns[256,16,80], while the Rust runtime had declared every non-merger result as[256,1280]; the element count matched but the meaningful head axes did not.Commit
2d13e470defines the complete ordered 25-output diagnostic descriptor schema and preserves rank-3[patch,head,head_dim]descriptors for query, key, and value. The existing public comparison API still receives flat owned vectors only after the validated device transfer. A regression pins every output semantic, ordinal, rank, and shape for the actual 32-layer configuration, including output 16 query, output 17 key, output 18 value, and the final merger. No emitted graph or numerical operation changed.The subsequent bounded two-media
local-syncrun at2d13e470completed the numerical comparison and validated that descriptor fix. Layer 15 still passed with zero failures (max_abs=0.18795204, error RMS0.00795757), but the captured input to layer 23 was already divergent: 350 failures,max_abs=132.40136719, error RMS0.49355285, and no non-finite values. Within layer 23, both the masked attention context and projected attention passed with zero failures (error RMS0.00515040and0.00488825, respectively). This evidence moves the unresolved boundary before layer 23 rather than attributing it to that layer's attention implementation.Commit
43f7da74adds the compact follow-up capture: the seven ordered post-layer outputs for layers 16 through 22 appear before the layer-23 substages in both eager and IREE diagnostics. The descriptor regression now pins all 32 current outputs: rank-2 state outputs through layer 23 input/norm1, rank-3 query/key/value, the remaining rank-2 substages, and the merger. The strict report records the pre-probe layer plan and comparisons. Production arithmetic, tolerance, and output contracts remain unchanged.The matching IREE 3.12 CUDA run at
43f7da74completed and reduced the boundary to two adjacent layers. Layer 16 passed with zero failures (max_abs=0.48780060, error RMS0.01032155); layer 17 was the first catastrophic failure with 392 values outside tolerance,max_abs=104.72387695, error RMS0.41014538, and no non-finite values. The report is persisted at/tmp/mlxcel-qwen25-vl-43f7da74-cuda-report.json. Layers 18 onward are propagation and do not justify additional large captures.Commit
93cc1e2fmoves the ordered substage probe from layer 23 to layer 17 and retains only the layer 16 and 17 boundary outputs. The formerpre_probefields and labels are now genericdiagnostic_layernames because the compact interval includes both the passing and first failing layers. The actual-config regression pins all 27 current output labels, ordinals, ranks, and shapes, including rank-3 layer-17 query/key/value descriptors. No duplicate layer output, production arithmetic, tolerance, or output contract was added.The matching IREE 3.12 CUDA run at
93cc1e2fpasses the layer-17 input, first normalization, masked attention context, projected attention, post-attention residual, and second normalization with zero failures. Query, key, and value have only 13, 8, and 1 marginal failures. The first catastrophic boundary is the MLP output: 338 failures,max_abs=104.05749512, error RMS0.40832933, and no non-finite values. The report is persisted at/tmp/mlxcel-qwen25-vl-93cc1e2f-cuda-report.json.Commit
9173a394replaces that single opaque MLP result with the exact implemented SwiGLU seams: gate projection, SiLU gate activation, up projection, gated product, and down projection. The existing final MLP tensor is renamed to down projection rather than duplicated. The actual-config regression now pins all 31 output labels, ordinals, ranks, and shapes, including four[256,3420]intermediate outputs and the[256,1280]down projection. Runtime active-row truncation derives each flat vector length from its validated descriptor, preserving the wider intermediate axis. Production arithmetic, tolerance, CSVs, and production output contracts remain unchanged.The bounded IREE 3.12 CUDA run at
9173a394completed every diagnostic comparison, persisted/tmp/mlxcel-qwen25-vl-9173a394-cuda-report.json, and exited 101 only when the unchanged strict oracle panic enforced the recorded failures. Layer-17 input, norm1, attention context, projected attention, post-attention residual, and norm2 passed. The MLP substages showed:max_abs=2.014036max_abs=0.758430max_abs=4.825338max_abs=105.50305max_abs=104.057495The gated product amplifies sparse projection outliers; it is not evidence that multiply itself introduced the first error. Source audit also corrected the quantization hypothesis for this fixture. Layer-17 gate/up/down weights and biases are dense F16 tensors with no affine scales or biases sidecars. Eager therefore selects
UnifiedLinear::Regular, transposes[out,in], performs MLX's native F16x @ W^T, then adds an F16 bias. The IREE loader widens the exact same F16 bytes to F32, the CUDA precision contract demotes only the activation and weight inputs back to F16 fordot_general, returns F32, and adds the widened F32 bias. Axes and operation order agree; the remaining concrete seam is native MLX F16 projection/result behavior versus IREE's F16-input/F32-result reduction and bias rounding. The current report does not prove which production side should change.Commit
790c5dadadds only eager diagnostics for that decision. Using the same eager norm2 values and checkpoint weights, it computes dense F32 gate and up projections and compares each existing IREE output against both the native eager projection and the F32 control. No IREE output or descriptor is added. Every comparison now records the maximum-error flat index plus actual and expected values. One matching actual run can therefore determine whether IREE follows the native F16 result or the F32 accumulation control before any production correction is considered.The bounded IREE 3.12 CUDA run at
790c5dadcompleted every comparison, persisted/tmp/mlxcel-qwen25-vl-790c5dad-cuda-report.json, and exited 101 only when the unchanged strict oracle panic enforced the recorded failures. Both controls retained exactly the same sparse failures and maximum-error indices:-16.59533882, native eager F16-18.60937500, eager dense F32-18.61830139; both comparisons had 8 failures, with error RMS0.00858201and0.00857863-12.20591164, native eager F16-17.03125000, eager dense F32-17.02601814; both comparisons had 23 failures, with error RMS0.01666641and0.01664133Native F16 and dense F32 eager results are close to each other while both remain far from IREE at the same outliers. This disproves projection result precision as the root cause for this fixture; changing production projection precision is not justified by the evidence.
Commit
89bed569adds the next bounded diagnostic without changing the IREE ABI. It takes the already-captured IREE layer-17 norm2 values, rounds them through the same F16 input type used immediately before the CUDAdot_general, and replays the same checkpoint gate/up weights through the eager dense-F32 control. The two new host-side comparisons distinguish amplification of inherited norm2 drift from backend contraction/reduction behavior using the existing IREE gate/up outputs.The bounded IREE 3.12 CUDA run at
89bed569made that distinction definitive. Both projections replayed from captured IREE norm2 passed with zero failures:max_abs=0.0001220703, error RMS0.0000011104max_abs=0.0002899170, error RMS0.0000013243The same checkpoint weights and dense-F32 projection schedule reproduce IREE when supplied IREE's own norm2. Projection reduction, weight loading, axes, and bias semantics are therefore excluded; the sparse gate/up failures are inherited norm2 drift amplified by the wide projections and SwiGLU product.
A bounded source and runtime dtype audit then found an explicit contract mismatch. The actual checkpoint's patch weight and all twelve norm/QKV/projection/MLP weight-or-bias tensors in each block 0 through 17 are F16. Eager MLX preserves F16 at every block input,
fast_rms_normoutput, post-attention residual, second norm, and block output. In contrast, IREEPrecision::F16demoted only eachdot_generalinput and returned F32, so patch state, normalization outputs, and both residual streams accumulated as F32 across layers.Commit
2b1c1f62tested a bounded coarse state-boundary hypothesis. It kept RMSNorm, softmax, RoPE, contraction accumulation, and the public ABI in F32 while rounding patch state, normalization results, post-attention residuals, and block outputs through F16.The matching actual CUDA run rejected that hypothesis. Patch embedding still passed with
max_abs=0.00024414, and layer 15 still passed withmax_abs=0.125, but the first critical boundaries regressed relative to the89bed569baseline:max_abs=104.7239became 1,380 failures atmax_abs=320max_abs=104.0575became 1,504 failures atmax_abs=319.2441max_abs=0.8468became 974 failures atmax_abs=1.5832The report is persisted at
/tmp/mlxcel-qwen25-vl-2b1c1f62-cuda-report.json. Commit53e0ffb1reverts only the coarse patch, normalization, and residual state round trips, restoring those production files exactly to89bed569while preserving all diagnostics. The evidence requires an operation-by-operation backend rounding contract before another production correction is safe; no tolerance waiver is justified.Remaining production validation
The earlier non-diagnostic pinned CUDA/IREE smoke completed successfully (
Orange, one token, exit 0). Strict single-image positive acceptance passes. The multi-media diagnostic has localized inherited norm2 drift, but the rejected2b1c1f62experiment proves that applying blanket F16 round trips at visible state boundaries is not a safe correction.The strict blocker is now a per-operator backend rounding contract that identifies where eager MLX rounds normalization, residual, bias, and intermediate results—not merely their observable tensor dtypes. That contract must be modeled and tested before another production change. Repeated multi-image execution, final logits/top-1 acceptance for the multi-media fixture, and mixed continuous-batch cancellation/slot-reuse validation also remain outstanding. This PR therefore remains draft.
No actual checkpoint was rerun after the revert commit. No tolerance, CSV, IREE output contract, or other production change is included.
Closes #866