Repository navigation
feat(multimodal): render per-group video timestamps for the Qwen3-VL family - #2910
Conversation
…family (lablup#2402) The reference Qwen3-VL processor renders a video as one `<t.t seconds>` marker plus its own `<|vision_start|>...<|vision_end|>` block per temporal patch group and positions each group as a `(1, h, w)` MRoPE grid. mlxcel rendered one flat, timeless video block, which the model reads as a spatial collage. - video: `SampledVideo` / `load_video_source_sampled_with_policy[_and_budget]` return the source frame indices and fps next to the frames; the existing loaders become thin wrappers. - qwen_vl: `QwenVlmPromptInfo::video_timestamps` gates the behavior to Qwen3-VL, Qwen3-VL-MoE and Qwen3.5 VLMs; Qwen2-VL and Qwen2.5-VL output is unchanged. Add `qwen_video_group_timestamps` (port of `_video_group_timestamps`), `insert_qwen_vl_media_tokens_timestamped`, and per-group MRoPE positioning that restarts the temporal axis after each timestamp. - vlm_runtime / model_worker: thread the sampled timing and the tokenizer into `compute_qwen_vl_media_embeddings`; a timestamp family without timing is an error instead of a silent fallback to the flat layout.
…t it (lablup#2402) Pull the per-group timestamp marker resolution out of compute_qwen_vl_media_embeddings into qwen_video_group_prefixes so the missing-timing, video-count, group-count and tokenizer-error paths can be unit-tested without a QwenVlRuntime. Behaviour is unchanged.
Integrate current main and reject overflow, unordered indices, and non-finite timestamps without allocating padded frame metadata. Preserve the original temporal grouping and add boundary regressions, upstream attribution, and accurate CLI scope documentation. Validate 126 focused tests, 53 ffmpeg video tests, Metal Clippy, formatting, and real Qwen3-VL before/after video inference. Record identical Llama vision fallback output as regression evidence, not a quality claim. Refs lablup#2402, lablup#2910
Hardening reviewReviewed PR #2910 against issue #2402, its discussion, and the upstream timestamp implementation. Commit 613247d integrates main and fixes unchecked dimension multiplication, potentially oversized padding allocation, unordered frame indices, and non-finite/negative timestamps. Added boundary regressions and the upstream MIT derivation notice. No new network resolution, subprocess path, shared mutable state, or unbounded cache is introduced; timestamp calculation stays linear without a padded copy. The original server-only scope note is inaccurate: native Qwen video already uses the shared preparation path from Validation
Full workspace tests and real Qwen3-VL-MoE/Qwen3.5 VLM checkpoints were not run. No throughput improvement is claimed. Existing native C++ unused-capture warnings remain. |
Summary
The reference Qwen3-VL processor renders a video as one
<t.t seconds>marker plus its own<|vision_start|>...<|vision_end|>block per temporal patch group, and positions each group as a(1, h, w)MRoPE grid. mlxcel rendered one flat, timeless video block, which the model reads as a spatial collage. This renders per-group timestamps for the Qwen3-VL family on the server video path, using the times of the frames the sampler actually kept (Neywa-Labs/mlx-vlm#2142, #2413).Related issues
Closes #2402
Type of change
feat— new user-visible featureWhat changed
multimodal/video.rs:SampledVideo { frames, frame_indices, source_fps }andload_video_source_sampled_with_policy[_and_budget]. The existing loaders are thin wrappers returning.frames. If the decoder returns fewer frames than requested,frame_indicesis truncated to match.multimodal/qwen_vl.rs:QwenVlmPromptInfo::video_timestampsgates the behavior: true for Qwen3-VL, Qwen3-VL-MoE and Qwen3.5 VLMs; false for Qwen2-VL, Qwen2.5-VL, GLM, Cohere Compass, Qwen4-Exp and MiMo. Their token output is unchanged.qwen_video_group_timestampsports_video_group_timestamps: indices are padded to a multiple oftemporal_patch_sizeby repeating the last one, a frame's time isindex / source_fps, a group's time is the mean of its first and last frame time. It errors on a non-positive/non-finite fps or when the padded count does not matchgrid_t * temporal_patch_size.insert_qwen_vl_media_tokens_timestampedemits<ts> <start> <video>* <end>per group. It handles the same three prompt states as the flat layout: no placeholders, one placeholder per item (the template's own start/end pair is folded in, not doubled), and already-expanded prompts (flat or per-group runs validated).qwen_vl_mrope_positions_from_tokensreads a video run that is exactly one group long as the first oftper-group(1, h, w)runs, so the temporal axis restarts after each timestamp. Flat runs behave as before.multimodal/vlm_runtime.rs:compute_qwen_vl_media_embeddingstakesOption<&QwenVideoTimestamps>. A timestamp family with videos and no timing is an error, not a silent fallback to the flat layout.server/model_worker.rs:prepare_qwen_vl_video_embeddingstakes the tokenizer, encodes<t seconds>without special tokens, and computes group times from the sampled indices.docs/supported-models.md: one paragraph noting the new rendering.Test plan
cargo fmt --checkcargo clippy -p mlxcel --all-targets -- -D warnings(Metal off, macOS arm64)cargo test --profile test-fast -p mlxcel --features accelerate --lib qwen_vl— 29 passed. New tests cover: MRoPE temporal positions restarting per group after the timestamp text and total length matching the token count; missing/malformed group runs rejected; already-expanded prompts accepted; one framed run per group; images kept flat and mixed media ordered; insertion after BOS with no placeholders; mean-of-first/last timestamps; one-decimal text; inconsistent inputs rejected.cargo test --profile test-fast -p mlxcel --features accelerate --lib -- multimodal::qwen_vl multimodal::vlm_runtime multimodal::video— 124 passed, 13 ignored. The timestamp-resolution step ofcompute_qwen_vl_media_embeddingsis the pure fnqwen_video_group_prefixes, unit-tested without aQwenVlRuntime(missing timing, video/group count mismatch, tokenizer error, non-timestamp family, success).cargo test ... --lib -- --include-ignored multimodal::video(ffmpeg present) — 53 passed, including the newsampled_loader_reports_source_indices_and_fps. The 1 failure,bench_single_pass_768_frames, is a wall-clock benchmark that its own doc says is not a gate.Qwen3-VL-4B-Instruct-4biton a clip of one moving square, before/after). No Qwen3-VL checkpoint is available locally. Reviewers with one, please run it. This is synthetic-only validation of the token layout and MRoPE positions, not model quality.cargo test --workspace --features metal,acceleratewas not run. I ran--libwith Metal off: 11393 passed / 17 failed. The failures are CPU-only-environment or flaky cases; I re-ran three of them (qwen4_exp ... qk_normalization_matches_upstream,catalog_cache_uses_epoch...,sam3d_objects ... compiled_objects_gelu_tanh...) on the unmodified base and they fail identically there.batched_lookahead_matches_force_sync_past_the_windowpasses and fails intermittently on repeated runs without my change.cargo clippy --workspacefails onmlxcel-xla/src/emitter/builder.rs:374(manual_is_multiple_of), a pre-existing lint in code this PR does not touch.Notes for reviewers
t > 1never being exactly one group long, so it only reinterprets what was previously a cardinality error.mlxcel generate --videoexpands clips to frames through a separate path (expand_cli_videos_to_frames); this PR changes the server video path only.encode(text, false); the marker tokenization relies on the model's own tokenizer matching the reference<%.1f seconds>text.Checklist
docs/(user-facing behavior).envfiles committedHardening verification (2026-11-11)
The original submission's unmeasured integrated test and server-only scope note are superseded: native
mlxcel generate --videouses the shared server preparation path. Real Qwen3-VL-4B moving-square inference now describes a single square moving left to right; before/after outputs and reproduction are recorded inTECHNICAL_REPORTS/2910-qwen-video-timestamp-hardening-20261111.en.mdand its Korean companion.Commit 613247d removes timestamp arithmetic overflow and padding allocation hazards, rejects unordered indices and non-finite/negative times, and adds boundary regressions and upstream attribution. Local Metal tests passed (126 focused tests and 53 ffmpeg tests), as did Clippy and formatting; executed PR CI checks passed. Self-hosted fork-PR jobs were skipped. Llama vision fallback text was identical before/after but hallucinated content in both arms. Full workspace tests and additional Qwen-family checkpoints remain unmeasured.