[Feature] Multimodal (image+text) DFlash training via server-side capture (Qwen3.5) - #730
Open
curnane-lab wants to merge 14 commits into
Open
[Feature] Multimodal (image+text) DFlash training via server-side capture (Qwen3.5)#730curnane-lab wants to merge 14 commits into
curnane-lab wants to merge 14 commits into
Conversation
Port of the DFlash draft-model changes from sgl-project/SpecForge PR sgl-project#585: partial rotation in apply_rotary_pos_emb (rotary_dim < head_dim, for Qwen3.5/Qwen3.6 partial_rotary_factor=0.25) and Qwen3InterleavedMultiRotaryEmbedding selected by rope_scaling.mrope_interleaved. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> (cherry picked from commit 9323a51; scoped to the draft model only, draft configs land with the recipe)
Follow-up adaptation of 9323a51 to the server-only architecture: - specforge/modeling/target/target_utils.py: add QWEN3_VL_MODEL_TYPES and resolve_target_weight_keys(); TargetEmbeddingsAndHead.from_pretrained now auto-selects model.language_model.embed_tokens.weight for VLM targets (Qwen3-VL / Qwen3.5 / Qwen3.6) when the embed key is unset or left at the LLM default. Explicit keys are honored as-is. - specforge/algorithms/model_providers.py: populate_dflash_generated_config reads the language-model depth via the text_config fallback so VLM draft config auto-generation uses the correct num_hidden_layers. (cherry picked from commit e2280f4)
Implements end-to-end VLM DFlash training on the server-only runtime:
- contracts/providers: dflash registers a STREAMING 'multimodal'
FeatureContract ({input_ids, loss_mask, hidden_states, position_ids}) and a
ServerStreamingProvider with a VLM ServerInputAdapter;
ServerCaptureLayout/ServerCaptureSchema gain position_ids_feature.
- data: specforge/data/vlm_preprocessing.py renders conversations with the
target's own chat template (image attached to the first user turn) and
expands the image region in id space via the target's HF processor, yielding
expanded input_ids/loss_mask (trainer + passthrough) and collapsed
request_input_ids + base64 image_data (capture request). One image per
sample (v1); text-only samples work in the same run.
- inference: the capture request's features map gains 'position_ids'; the
regenerated sglang v0.5.14 spec-capture patch writes the request's mRoPE
positions (1, L, 3) int64 into Mooncake from
req.multimodal_inputs.mrope_positions (arange fallback for text prompts).
Managed capture servers set SGLANG_MM_AVOID_RETOKENIZE=1 for
input_modality=multimodal so the server re-expands placeholders in id space
with no retokenization drift.
- training: build_vlm_collator pads position_ids; OnlineDFlashModel.forward
accepts optional (B, S, 3) position ids and gathers 3D mRoPE positions for
context + anchor-offset draft slots; DFlashTrainStrategy passes the tensor
through. Text paths are unchanged.
- recipe: examples/configs/qwen3.5-4b-vl-dflash-multimodal-disaggregated.yaml
+ configs/qwen3.5-4b-vl-dflash.json (Qwen3.5-4B VLM draft geometry).
- tests: new tests/test_algorithms/test_dflash_multimodal.py; update the
text-only assertions in test_builtin_providers / test_server_only_online /
test_unified_feature_reachability / test_launch_topology /
test_example_draft_config_wiring for the new modality and recipe.
GPU/NPU end-to-end validation (live ViT capture + training) is pending and
tracked in docs/advanced_features/vlm_dflash.md.
Contributor
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
curnane-lab
marked this pull request as ready for review
July 29, 2026 15:40
curnane-lab
requested review from
FlamingoPg,
FrankLeeeee,
shuaills,
sleepcoo and
zyksir
as code owners
July 29, 2026 15:40
Contributor
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
curnane-lab
marked this pull request as draft
July 30, 2026 10:22
curnane-lab
force-pushed
the
vl-dflash-pr
branch
from
July 30, 2026 11:55
9e0d57c to
95ecc24
Compare
curnane-lab
marked this pull request as ready for review
July 31, 2026 14:12
Contributor
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
Image-text DFlash training is a long-standing request (#583, #461) and a high-priority item on the 2026 roadmap (#374: "General VLM training"). The pre-#678 HF-backend stack had community VLM prototypes (#585, #495, #505); after the unified-runtime consolidation (#678), training runs exclusively on server-side capture, and none of those prototypes apply anymore.
This PR brings multimodal DFlash training to the current architecture end-to-end: conversations with images are expanded client-side, captured by a patched SGLang server (aux hidden states and mRoPE position ids written straight to Mooncake), and trained with 3D mRoPE positions in the DFlash draft. Text-only runs are byte-identical to before.
Modifications
algorithms/dflash/providers.py,common/providers.py): DFlash registers a STREAMINGmultimodalFeatureContract ({input_ids, loss_mask, hidden_states, position_ids}) and aServerStreamingProviderwith a VLMServerInputAdapter(the modality seam already present incommon/providers.py).ServerCaptureLayout/ServerCaptureSchemagainposition_ids_feature.data/vlm_preprocessing.py,common/vlm_input.py): ShareGPT-style JSONL with an optionalimage/image_path/imagesfield (path or base64;imagestakes the single list element). The target's own chat template renders the image into the first user turn; the target's HF processor expands the image region in id space, yielding expandedinput_ids/loss_mask(trainer + passthrough) and collapsedrequest_input_ids+data:-URIimage_data(capture request — accepted by all supported SGLang builds). One image per sample (v1); text-only samples can mix in the same run. Unreadable images and multi-image samples fail loudly (ImageDataError), never a silent text-only downgrade.inference/adapters/server_capture.py,training/disaggregated.py,launch_plan.py,patches/sglang/v0.5.14/spec-capture.patch): the capture request'sfeaturesmap gainsposition_ids; the regenerated patch writes the request's mRoPE positions(1, L, 3)int64 into Mooncake fromreq.multimodal_inputs.mrope_positions(arange fallback for text prompts). Managed capture servers setSGLANG_MM_AVOID_RETOKENIZE=1forinput_modality=multimodalso the server re-expands placeholders in id space with zero retokenization drift (for externally managed servers viaserver_urls, set it in the server environment manually — noted in the recipe comments); client/server expansion mismatch fails loudly at the existing seq-len check. On Ascend hosts the capture servers default to--mm-attention-backend ascend_attn(fused vision attention) for non-text modalities — the sdpa vision backend materializes [heads, N, N] scores and OOMs on large images;model.sglang_mm_attention_backendoverrides.modeling/draft/dflash.py, ported from [Feature] VLM DFlash Training: Multi-Model Support for Qwen3-VL / Qwen3.5 / Qwen3.6 #585 by @zyk42, authorship preserved): partial rotation inapply_rotary_pos_emb(rotary_dim < head_dim, for Qwen3.5/3.6partial_rotary_factor=0.25) andQwen3InterleavedMultiRotaryEmbeddingselected byrope_scaling.mrope_interleaved.modeling/target/target_utils.py,algorithms/model_providers.py): auto-selectmodel.language_model.embed_tokens.weightfor VLM targets; language-model depth via thetext_configfallback for draft config auto-generation.common/dflash_family_{data,model}.py,training/strategies/base.py):build_vlm_collatorpadsposition_ids;OnlineDFlashModel.forwardaccepts optional(B, S, 3)position ids and gathers 3D mRoPE positions for context + anchor-offset draft slots;DFlashTrainStrategypasses the tensor through.examples/configs/qwen3.5-4b-vl-dflash-disaggregated.yaml+configs/qwen3.5-4b-vl-dflash.json(mirrors the Qwen3.5-4B target geometry: head_dim 256, mrope_section [11,11,10], partial_rotary_factor 0.25).tests/test_algorithms/test_dflash_multimodal.py; text-only assertions intest_builtin_providers/test_server_only_online/test_unified_feature_reachability/test_launch_topologyupdated for the new modality and recipe; status docdocs/advanced_features/vlm_dflash.md.Scope notes (deliberate): DFlash only (Domino/DSpark can mirror the same registration later); one image per sample; offline (precomputed) multimodal capture not included; #585's wider config set (Qwen3-VL-8B/30B-A3B, Qwen3.5-9B/35B-A3B) deferred to a follow-up once more targets are validated.
Related Issues
Accuracy Test
End-to-end (Ascend NPU, first live run — PASSED): Qwen3.5-4B target + VLM DFlash draft, disaggregated managed run (5 capture servers + 5 trainer ranks, 32k context). Training runs end-to-end with healthy numerics (curves below). Two compatibility issues found during bring-up are fixed in this PR (data-URI image transport for older SGLang image loaders;
ascend_attnas the default vision attention backend on NPU — the sdpa vision backend OOMs on large images).Data & training configuration
configs/qwen3.5-4b-vl-dflash.json, 5 layers, block_size 16)Training curves of the NPU end-to-end run (~1.2k steps): draft loss decays from ~5.7 to ~1, draft token accuracy rises from ~0.10 to ~0.65+, grad_norm stays bounded (0.4–1.1), and lr follows the cosine decay.
accuracy_denom(masked label tokens per logged window) oscillates with the image/text batch composition — expected for mixed VLM batches.Unit/integration (CPU): registration parity and provider gates, request/payload construction, image-expansion math,
images-field resolution and loud image errors, data-URI transport, collator, golden topology/recipe tests —tests/test_algorithms/test_dflash_multimodal.pyplus updatedtest_configsuites pass (87 passed, 2 skipped locally; the only failure intests/test_configistest_recipe_readme, which fails on upstreammainas well — unrelated pre-existing issue).Patch integrity: the regenerated
spec-capture.patchapplies cleanly both ways to pristine sglang v0.5.14 (git apply --check/--reverse --check), and stacks cleanly with the Ascend companion patch.Benchmark & Profiling
Checklist