Skip to content

fix(youtu_vl): expand the template image placeholder per merged feature - #2133

Merged
inureyes merged 2 commits into
mainfrom
fix/issue-1618-youtu-vl-image-pad-expansion
Oct 6, 2026
Merged

inureyes merged 2 commits into
mainfrom
fix/issue-1618-youtu-vl-image-pad-expansion

Conversation

@inureyes

@inureyes inureyes commented Oct 6, 2026

Copy link
Copy Markdown
Member

Summary

Root cause of #1618 is not in the window path. The Youtu-VL chat template renders one <|image_pad|> per image, and the checkpoint's processor (YoutuVLProcessor.__call__) replaces it with one token per merged feature. insert_youtu_vl_image_tokens saw an image token and treated the prompt as already expanded, so the prompt carried 1 image token for 121 or 196 features and the masked scatter placed only the first (top-left) feature. A solid-color image reads correctly from any single token, which is why the 224 control passed and the "single window" pattern looked like a window bug.

  • Placeholder expansion now mirrors the processor: one placeholder per image is expanded in place inside the template's framing; an already expanded prompt is a no-op; no placeholder keeps the splice-after-BOS fallback; any other count is a request error.
  • YoutuVLModel::get_input_embeddings refuses a placeholder/feature count mismatch (upstream raises) instead of truncating silently.
  • The two #[ignore]d content tests in tests/youtu_vl_parity.rs keep only the checkpoint requirement; stale comments attributing the 224 pass to the identity window permutation are corrected.

How the alternatives were ruled out

Stage-by-stage diff against transformers 4.56.0 (checkpoint's own remote code, f32, CPU):

fixture grid processor pixels vs HF tower+merger vs HF, HF pixels (cos min / mean)
224 14x14 max abs 1.5e-8 0.99991 / 0.99991
336 22x22 lanczos vs bilinear resize, mean abs 0.003 0.914 / 0.998
448 28x28 max abs 3.0e-8 0.903 / 0.997

The mlxcel run is bf16, so per-token cosine near 1 with a few low tokens is precision, not a permutation. get_window_index, cu_window_seqlens (including partial windows and dedup), and the full-attention boundaries agree with the reference; a new unit test pins get_window_index to reference values on 11x11, 14x14, a batched pair, and a non-square grid. The decisive evidence was the prompt dump: mlxcel sent 128262, 128264, 128263 where the reference sends 128262, 128264 x 196, 128263.

Test plan

Before and after on CUDA (GB10), mlxcel generate ... -n 48 -t 0, "What shapes and colors are in this image? Answer briefly.":

fixture before after (CLI and mlxcel-server) transformers greedy
224 orange "The image is a solid, uniform orange color with no shapes or other colors present." same same
336 shapes "The image contains a single white circle on a black background." "three shapes: red square (top left), blue circle (top right), green triangle (bottom center) ..." identical text
448 shapes "The image contains a single, solid black circle on a plain white background." "- Red square - Blue circle - Green triangle" identical text
  • youtu_vl_parity --ignored on the real checkpoint: 3 passed (both previously known-failing tests now pass)
  • mlxcel-server /v1/chat/completions on all three fixtures: same text as the CLI, prompt_tokens 82 / 154 / 229, equal to the reference processor's token counts
  • youtu_vl unit tests (37), including new prompt-expansion tests on a 28x28 multi-window grid and the mismatch error
  • clippy --lib --tests --features cuda -D warnings, cargo fmt --check

Not verified on this host

The issue's evidence was on M1 Ultra (Metal). The failure reproduced word for word on CUDA and the fix is backend-independent (token ids only), but the Metal run itself was not repeated here.

Closes #1618

The Youtu-VL chat template renders one `<|vision_start|><|image_pad|><|vision_end|>` per image, and the checkpoint's own processor then replaces each `<|image_pad|>` with one token per merged vision feature. `insert_youtu_vl_image_tokens` read any image token in the prompt as "already expanded" and left the single placeholder alone, so the masked scatter placed only the first (top-left) feature and dropped the rest. A uniform image survives that, which is why the solid orange control passed; every multi-object fixture was described from its corner.

The insertion now mirrors the processor: one placeholder per image is expanded in place, an already expanded prompt is left alone, a prompt with no placeholder keeps the splice-after-BOS fallback, and any other count is an error. `YoutuVLModel::get_input_embeddings` also refuses a placeholder/feature count mismatch, as upstream does, instead of truncating silently.

A stage-by-stage diff against the transformers reference ruled out the window path named in the issue: with the reference's own pixels the mlxcel tower output matches per merged token (cosine mean 0.997 or better on the 11x11 and 14x14 grids), and the processor's pixels are bit-identical at 448. A unit test pins `get_window_index` to reference values on multi-window grids.

The two known-failing tests in tests/youtu_vl_parity.rs are un-gated and pass on the real checkpoint on CUDA, and CLI and mlxcel-server output now matches the transformers greedy output word for word on all three fixtures.

Closes #1618
@inureyes inureyes added status:review Under review type:bug Bug fixes, error corrections, or issue resolutions priority:high High priority area:models Model architectures, weights, loading, metadata modeltype:vlm Vision-language model labels Oct 6, 2026
@inureyes
inureyes merged commit 0cf4b08 into main Oct 6, 2026
26 checks passed
@inureyes inureyes added status:done Completed and removed status:review Under review labels Oct 6, 2026
@inureyes
inureyes deleted the fix/issue-1618-youtu-vl-image-pad-expansion branch October 6, 2026 07:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:models Model architectures, weights, loading, metadata modeltype:vlm Vision-language model priority:high High priority status:done Completed type:bug Bug fixes, error corrections, or issue resolutions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(youtu_vl): multi-window images still described wrong after the patch-order fix

1 participant