Fix batched multimodal prefill rank mismatch in Gemma 4 bidirectional mask - #2339
Fix batched multimodal prefill rank mismatch in Gemma 4 bidirectional mask#2339gmhelmold wants to merge 1 commit into
Conversation
Code Metrics Report━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Language Files Lines Code Comments Blanks ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ C Header 23 4454 3116 790 548 CSS 3 282 252 6 24 CUDA 119 23681 19217 1704 2760 Dockerfile 1 35 19 9 7 HTML 2 27 27 0 0 JavaScript 3 577 562 12 3 Jinja2 7 694 656 5 33 JSON 27 15864 15861 0 3 Makefile 1 18 16 0 2 MDX 36 5901 0 4327 1574 Metal Shading Lan| 37 14287 11284 1136 1867 PowerShell 1 656 570 31 55 Python 151 12284 10191 484 1609 Shell 3 1062 843 117 102 Plain Text 53 10687 0 9209 1478 TOML 28 1368 1189 39 140 TypeScript 11 1641 1404 66 171 YAML 3 25 23 2 0 ───────────────────────────────────────────────────────────────────────────────── Jupyter Notebooks 3 122 83 23 16 |- Markdown 1 60 30 22 8 |- Python 1 122 113 1 8 (Total) 304 226 46 32 ───────────────────────────────────────────────────────────────────────────────── Markdown 273 11726 0 8736 2990 |- BASH 23 295 217 46 32 |- Dockerfile 2 5 5 0 0 |- JSON 6 289 289 0 0 |- PowerShell 1 1 1 0 0 |- Python 135 7239 6021 310 908 |- Rust 62 3820 2838 388 594 |- TOML 7 77 65 0 12 (Total) 23452 9436 9480 4536 ───────────────────────────────────────────────────────────────────────────────── Rust 674 299702 267081 5872 26749 |- Markdown 413 9761 452 8126 1183 (Total) 309463 267533 13998 27932 ───────────────────────────────────────────────────────────────────────────────── Svelte 19 1969 1826 51 92 |- CSS 1 4 4 0 0 |- JavaScript 19 921 767 25 129 (Total) 2894 2597 76 221 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Total 1478 429656 345022 41537 43097 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ |
|
We hit this same crash while investigating #2323 and can offer on-hardware validation: we'd been running an equivalent local patch (same shape — per-row override, Setup: DGX Spark (GB10, sm_121, CUDA 13.0), Gemma-4-26B-A4B, Q6K UQFF, 0.8.23 base. Crash (Part A): gone.
End-to-end effect: with the equivalent patch, a width-2 batched vision workload (5-page document review) went from near-total output degeneration (every page hitting the token cap with degenerate text) to substantively correct output on our eval — so the fix restores real quality at batch width 2, not just crash-avoidance. Still broken after the fix (consistent with this PR's "Part A only" scope):
One note for review: downstream mask chunking dispatches on rank, so it's worth confirming the always-4D mask preserves single-request behavior — our local variant conservatively kept the 2-D Happy to re-run the repro or a width ladder on this exact branch if useful. |
|
Thank you for the on-hardware validation — that's exactly the coverage this needed! Great to hear the deterministic repro is gone at both N=2 and N=5 on GB10, and especially that you verified it on the cuda-only build (which always takes the masked path). With your two-build validation on real Gemma-4-26B workloads plus the byte-parity proof for bs=1 in the PR body, I believe Part A is fully covered here; Part B (batched-prefill rank mismatch) remains scoped out as discussed in #2323. |
Fixes #2323 (Part A)
Root cause
Gemma4TextModel::apply_image_bidirectional_maskassumed batch size 1: it didinput_ids.squeeze(0)?followed byto_vec1(), which errors withunexpected rank, expected: 1, got: 2 ([2, 1080])the moment two same-length requests are batched into a single prefill (input_idsis[bsz, seq]). This path is always taken on cuda-only / no-flash-attn builds. The bidirectional vision-group override was also built as one shared 2-D mask, so even past the crash it couldn't represent per-row image positions.What changed
Compute the vision-group override per row against the shared 2-D causal mask, then stack the rows into
[bsz, 1, seq, total].bsz == 1returns the original[seq, total]tensor unchanged, so the single-request path is byte-identical (same dims, dtype, values).[bsz, 1, seq, total]broadcasts to[bsz, n_heads, seq, total]at every consumer of this mask — naive SDPA (broadcast_add), the cuBLASLt rank-4 branch, Metal SDPA (broadcast_as), and the CPU flash-attn mask indexer — for both the full and sliding-window masks.Tests
Three CPU tests added in
vision_models/gemma4/text.rs:bsz == 1keeps the old[seq, total]shape and exact values,cargo test -p mistralrs-core --lib gemma4→ 47 passed; 0 failed.cargo fmt -- --checkclean.Scope
This is Part A (the rank-mismatch crash) only. Part B — mixed-length image batches silently corrupting output — is a separate chunk-coordinate issue and is intentionally left out of this PR.
Happy to have @achandi validate on the deterministic repro mentioned in the issue.