feat(sft): multimodal (VLM) SFT#3075
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 084d489008
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Renderer-produced multimodal samples flow end to end through SFT: - SFTDataset keeps the renderer's MultiModalData: image-safe truncation (never cuts inside a placeholder run, drops truncated-out images, and skips samples whose EOS was truncated away), mm_token_type_ids kept aligned through EOS append and causal shift, video inputs rejected explicitly. _flatten_mm_items folds per-image processor outputs into model-forward kwargs (pixel_values, image_grid_thw, ...). - Cat packing concatenates mm_kwargs across samples in a pack and represents text-only spans as zeros in mm_token_type_ids; stack bucketing emits multimodal samples solo. Collates move mm tensors to CUDA and keep packed rows batch-size 1. - Local data_files normalization wraps string message content into typed content blocks under a multimodal flag so Arrow can unify text and image rows. - setup_processor loads the AutoProcessor and attaches it to the renderer; VLM SFT fails fast without one; weight checkpoints save the processor alongside the tokenizer. - Dense Qwen3.5 gains the composite VLM body (HF vision encoder + custom text model) with MRoPE positions from mm_token_type_ids and registers in _CUSTOM_VLM_MAPPING; VLM training requires a custom PrimeRL implementation. Both dense and MoE VLM bodies always run the vision encoder (dummy input on text-only microbatches, kept in the backward graph) for FSDP/EP collective symmetry. - Config: VLM SFT requires micro_batch_size=1; unfreezing the vision encoder is incompatible with LoRA; the renderer-vs-VLM rejection is gone (renderers own multimodal tokenization now). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
processor.save_pretrained rewrites tokenizer files from the processor's separately loaded tokenizer, clobbering the configured pad token and any custom chat template. Save the tokenizer last so it wins. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Without [model.vlm] the custom VLM class still loads and runs the vision tower on dummy input every microbatch, so its params carry zero — not None — grads and AdamW weight decay silently shrinks them. Freeze the tower when no VLM training is configured; mixed image/text batches cannot occur without [model.vlm], so collective symmetry is unaffected. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…l.vlm] The processor auto-loads for VLM checkpoints and mm samples would flow through packing and forward unvalidated (no mbs/attn/CP checks, no freeze policy). Require the explicit [model.vlm] declaration instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Enforce the documented VLM dtype contract while reducing misleading conversion, packing, and documentation noise. Co-authored-by: Cursor <cursoragent@cursor.com>
Validate MRoPE inputs only when positions must be computed and rely on the dataset's established token-type alignment invariant. Co-authored-by: Cursor <cursoragent@cursor.com>
Keep the VLM validation and packing changes aligned with the repository formatter used by CI. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
084d489 to
928f22d
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 56c34fcff1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
S1ro1
left a comment
There was a problem hiding this comment.
would be a bit more skeptical about the llm generated tests also, they dont seem to test a lot and just pollute code
also data part needs checking
| if self._is_vlm: | ||
| self.model = Qwen3_5VLMModel(config) | ||
| text_config = config.text_config | ||
| self._tied_weights_keys = {"lm_head.weight": "model.language_model.embed_tokens.weight"} |
There was a problem hiding this comment.
I think tying weights breaks things in a lot of cases, do we need it?
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7b7c64663e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
samsja
left a comment
There was a problem hiding this comment.
wondering if it wouldn;'t be easier to just not support lora for vlm, kinda trying to keep the lora path as is for now
samsja
left a comment
There was a problem hiding this comment.
and apply_ep
Before: apply_lora → freeze_moe_router → apply_fp32_moe_router →
freeze_sparse_indexer → apply_ep
After: apply_lora → apply_fp32_moe_router → apply_ep → freeze_all_except_lora →
freeze_moe_router → freeze_sparse_indexer
lets keep the otherd the same as before
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 2 total unresolved issues (including 1 from previous review).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit c8a54d9. Configure here.
# Conflicts: # README.md # configs/debug/multimodal.toml

Summary
Rebased replacement for #2945, now targeting
mainafter #3032 merged.mm_token_type_ids, multimodal processor outputs, and cat packing.The branch preserves #3032's explicit
seq_lenscontract: trailing padding is merged into the final sequence segment and no separate padding metadata is transported.Validation
23 passed— existing SFT dataset suite after the image-safe truncation fix32 passed, 5 deselected— focused non-GPU VLM model/forward/SFT tests using an import-onlyring_flash_attnshim; no attention kernels were exercised41 passed, 80 deselected— config unit tests excluding external-plugin config loading0.0138, step-0 KL0.00250.1145Post-rebase CI fixes: the exact
examples/vlm_sft_moe/sft.tomlconfig test and ansft --dry-runvalidation both pass. The nightly RL VLM config now setsimpl = "custom"in[trainer.model], and obsolete GPU tests for removed modeling-layer validation were dropped.Latest main merge: merged
mainat445392294; the README overlap retains the current multimodal docs anchor and keeps VLM CP marked unsupported, while main's obsolete debug-config deletion is preserved.Cleanup: multimodal CUDA transfer is inlined in the singleton SFT collate path; the single-use helper was removed.
API cleanup:
[model.vlm]now requiresmodel.impl = "custom". The dense and MoE custom VLM bodies accept token IDs only, share embedding accessors with their text-only bodies, and no longer carry unsupportedinputs_embedsfallbacks or redundant modeling-layer validation. Multimodal samples without[model.vlm]still fail at the lazy dataset boundary.Truncation follow-up: multimodal samples are now causal-shifted before image-safe truncation, keeping placeholder ranges, modality IDs, targets, and processor sidecars on the same model-input boundary.
Review follow-up: the multimodal processor is now loaded from
model.name, so overridingtokenizer.namedoes not discard model-specific image preprocessing.configure_trainable_parametersis limited to LoRA application and frozen-vision discovery; router transforms and EP remain explicit insetup_model. LoRA keeps its existing initial freeze and logging, while EP performs the same conditional re-freeze asmainafter replacing parameters with DTensors. The VLM-specific vision freeze runs afterward.Weight-tying follow-up: Qwen3.5 honors the checkpoint's standard tied-weight mapping. Checkpoints that omit
lm_head.weightload the shared language embedding through the existing tied-model path without custom loader handling.LoRA documentation follow-up:
freeze_vision_encodernow promises parameter freezing only. The docs explicitly note that the default Qwen3.5 LoRA targets do not match vision modules, without claiming that arbitrary user-supplied target patterns are filtered.Note
Medium Risk
Touches core training paths (model setup, forward, checkpoints, packing) and distributed VLM collectives; misconfiguration is mostly caught by validators, but multimodal truncation and vision/LoRA interactions need careful review.
Overview
Adds end-to-end multimodal SFT for Qwen3.5 dense and MoE when
[model.vlm]is set withmodel.impl = "custom".The SFT pipeline now carries renderer-produced
mm_kwargs,mm_token_type_ids, and image-safe truncation through packing and the training forward; it loads anAutoProcessorfrommodel.name, wires it into the renderer, and saves it with HF weight checkpoints. Config validation enforces VLM bfloat16 dtypes,micro_batch_size = 1, no CP, and rejectsfreeze_vision_encoder=falsewith LoRA; the old blocker that forbade VLM in renderer-only SFT is removed.Modeling: Registers dense
qwen3_5in the custom VLM map and extendsQwen3_5ForCausalLM/ MoE VLM bodies with HF vision + custom text, MRoPE from renderer metadata, and dummy vision passes on text-only steps for distributed collective symmetry. Trainability is centralized inconfigure_trainable_parameters(LoRA + optional vision freeze after EP).Docs and CI shift the multimodal story to Qwen3.5 (CP marked unsupported for VLMs); new example TOMLs cover dense and MoE VLM SFT.
Reviewed by Cursor Bugbot for commit 0c882df. Bugbot is set up for automated code reviews on this repo. Configure here.