fix(minimax-h3): keep Qwen3VL vision pos-embed interpolation on the compute device - #198
Open
lstein wants to merge 2 commits into
Open
fix(minimax-h3): keep Qwen3VL vision pos-embed interpolation on the compute device#198lstein wants to merge 2 commits into
lstein wants to merge 2 commits into
Conversation
…ompute device Qwen3VLVisionModel.fast_pos_embed_interpolate derives its working device from self.pos_embed.weight.device and builds every tensor there. Under partial loading, nn.Embedding is autocast-wrapped, so that weight may legitimately sit on the CPU while the model computes on CUDA - the per-op autocast casts weights to each input's device, but this function manufactures its own inputs on the weight's device, so the interpolation lands on the CPU and forward's 'hidden_states + pos_embeds' dies with 'Expected all tensors to be on the same device ... cuda:0 and cpu'. Hit in the wild when two simultaneous MiniMax H3 jobs partial-loaded the 27GB text encoder and a Ref2VA reference image sent a prompt through the vision tower (rig queue item 10352). Unreproducible on a fully loaded model, hence the intermittency. Fix: a minimal, idempotent class-level patch installed by both text encoder load paths. forward() records the compute device from its hidden_states input; fast_pos_embed_interpolate runs unchanged and only its result is moved to that device. Weight resident on the compute device -> exact upstream behavior; weight on CPU -> the small interpolation runs on CPU and one tensor crosses the bus. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014xFbHnmFTLsHE9e1PAdvme
lstein
marked this pull request as ready for review
September 4, 2026 18:24
lstein
requested review from
JPPhoto,
Pfannkuchensack and
blessedcoolant
as code owners
September 4, 2026 18:24
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.
The crash (rig queue item 10352)
Hit once when two simultaneous MiniMax H3 jobs ran and a Ref2VA reference image sent a prompt through the text encoder's vision tower; never reproduced since.
Root cause
Qwen3VLVisionModel.fast_pos_embed_interpolate(transformers 5.5.4, line 707) derives its working device fromself.pos_embed.weight.deviceand builds all of its tensors there. Under partial loading,nn.Embeddingis autocast-wrapped (CustomEmbedding), so that weight may legitimately sit on the CPU while the model computes on CUDA. The per-op autocast casts weights to each input's device — which keeps every normal layer on the compute device — but this function manufactures its own inputs on the weight's device, so the whole interpolation lands on the CPU and the add inforwarddies.That's why it's intermittent: it needs (a) the 27 GB TE partial-loaded under VRAM pressure with
visual.pos_embed.weightspecifically among the CPU-resident keys, and (b) an image in the prompt. A fully loaded TE can never hit it (autocast disabled, weight resident). Same anti-pattern class as diffusers invoke-ai#9373 ("don't infer the compute device from current param residency").Fix
Minimal, idempotent, class-level patch (mirroring
contiguous_attention), installed by both TE load paths:forwardrecords the compute device from itshidden_statesinput (the very tensorpos_embedsis added to);fast_pos_embed_interpolateruns unchanged, and only its result is moved to the recorded device.Weight resident → exact upstream behavior (move is a no-op). Weight on CPU → the small interpolation (a few thousand rows) runs on CPU and one tensor crosses the bus.
Testing
meta, unset-attr behavior, idempotence); fulltests/backend/minimax_h3/suite passes (159).rot_pos_emb'sinv_freqlooks like a sibling pattern but is a non-persistent buffer, which_move_non_persistent_buffers_to_device()unconditionally pins to the compute device on every load pass — not offloadable, safe; (2) Krea2'sQwen3VLEncoderLoaderdoesn't install the patch, but its encoder path is text-only today (vision tower never runs) — if Krea2 ever grows image prompts, its loader should callapply_qwen3vl_vision_pos_embed_device_patch()too.Rig test
Hard to trigger on demand (needs the TE partial-loaded with that specific weight off-device). Running image-conditioned Ref2VA prompts while both GPUs are busy is the closest recipe; mostly this rides along until the stars align again — at which point it should just work instead of failing the job.
🤖 Generated with Claude Code
https://claude.ai/code/session_014xFbHnmFTLsHE9e1PAdvme