Skip to content

H3 image/video-conditioned generation: wire the encoder vision tower to real weights (row/H3-CONDITIONED-E2E) - #86

Closed
localai-bot wants to merge 4 commits into
mainfrom
row/H3-CONDITIONED-E2E
Closed

H3 image/video-conditioned generation: wire the encoder vision tower to real weights (row/H3-CONDITIONED-E2E)#86
localai-bot wants to merge 4 commits into
mainfrom
row/H3-CONDITIONED-E2E

Conversation

@localai-bot

@localai-bot localai-bot commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Mission

Wire the MiniMax-H3 encoder vision tower into the real encoder path so image/video-conditioned generation (fl2va / ref2va) works on real weights — the lane's last supports gap vs vllm-omni.

Record reconciliation (done first)

Two prior lanes contradicted. Reading the code resolves it — both describe different halves:

  • The vision-tower math EXISTS as a CPU scalar reference (minimax_h3_encoder.cpp:572 + surround), gated only at reduced dims with SYNTHETIC weights (test_minimax_h3.cpp:4041).
  • It was NEVER wired to real weights: LoadMiniMaxH3EncoderFromGguf (minimax_h3_encoder_gguf.cpp:47) loads the text tower only and skips every visual.* tensor; the device forward is text-only; driver/server call only the text path.

Encoder-arm decision (measured, no download)

The on-box qwen3vl-32B-MiniMax-H3-Q4_K_M.gguf (14 GiB) carries the full vision tower (27 blocks Q4_K/Q5_K, patch_embed F16, pos_embed F16 48², merger, 3 deepstack mergers). Geometry: hidden 1152 / 16 heads / depth 27 / intermediate 4304 / out 5120 / merge 2 / pos 2304.

Landing (verified on GB10)

  • Record reconciliation (spec §8.8 + §8.2, STATUS/BENCHMARKS/FEATURES, state, benchmark-record, NOW)
  • GGUF visual.*Qwen3VLVisionWeights loader (LoadQwen3VLVisionFromGguf) + CPU gate (59 assertions)
  • Driver --prompt-image probe — vision tower RAN on real weights: [256,20480] all finite, merged rms 1.45 / maxabs 29.1 (Qwen massive-activation)
  • fl2va frame-sanity PASS — FL2VA GGUF + real first-frame (VAE-keyframe) → all 22 frames a coherent orange cat on a wooden table matching the conditioning frame
  • ref2va STILL GRIDS (honest) — landing the loader does not fix it; the tower is a loader+probe, NOT yet scattered into the DiT render-conditioning. Residual: scatter merged features into prompt_embeds + DeepStack inject into the device text tower, then re-test.

Pre-existing preflight red (check-fusion-consistency minimax_h3_video_vae_device) is not this row.

🤖 Generated with Claude Code

https://claude.ai/code/session_014fZAcg1WcU8V629k6HWKys

mudler added 4 commits August 7, 2026 00:01
…nthetic, never real-wired; encoder GGUF carries visual.* (row/H3-CONDITIONED-E2E)

Reconcile the contradictory vision-tower record before re-porting anything.
#26/W3 recorded "vision tower COMPLETE, only MM processor remains"; #77 recorded
"encoder vision tower still unported". Reading the code, both describe different
halves: the tower MATH exists as a CPU scalar reference
(minimax_h3_encoder.cpp:311/430/500/545/572) gated ONLY at reduced dims with
SYNTHETIC weights (test_minimax_h3.cpp:4041), but it was NEVER wired to real
weights — LoadMiniMaxH3EncoderFromGguf (minimax_h3_encoder_gguf.cpp:47) loads the
TEXT tower only and skips every visual.* tensor; the device forward is text-only;
the driver/server call only the text path.

Encoder-arm decision (measured, no download, disk floor honoured): the on-box
qwen3vl-32B-MiniMax-H3-Q4_K_M.gguf DOES carry the full vision tower (27 blocks
Q4_K/Q5_K, patch_embed F16 [16,16,6,1152], pos_embed F16 [2304,1152]=48², merger,
3 deepstack mergers). Geometry hidden 1152 / 16 heads / depth 27 / intermediate
4304 / out 5120 / patch 16 / temporal 2 / merge 2 / pos 2304 (== the Qwen3.6-27B
vision config). Reuse target: multimodal::Qwen3VLVisionForward + Qwen3VLImageProcessor.

Records: spec §8.8, STATUS/BENCHMARKS/FEATURES H3 rows, benchmark-record, NOW,
state (2026-08-07T01:45). Pre-existing preflight red (check-fusion-consistency
minimax_h3_video_vae_device) is not this row.

FOLLOWING_AGENTS_PROTOCOL
Assisted-by: Claude Code:claude-opus-4-8 [ClaudeCode]
… weights + driver probe

The one piece the record reconciliation (spec §8.8) found missing: LoadQwen3VLVisionFromGguf
reads the encoder GGUF's visual.* tower into the shared multimodal::Qwen3VLVisionWeights the
Qwen3-VL front end consumes, DEQUANTIZING each ggml-block projection (Q4_K/Q5_K) + F16
patch_embed/pos_embed to f32. Encoding-agnostic (per-tensor DequantGgufRowToF32), so the
ComfyUI ne0=256 reshape of the non-256-aligned rows is transparent — dequantizing the whole
flat buffer preserves the row-major [out,in] order the tower reads. MiniMaxH3EncoderVisionConfig
returns the measured H3 vision geometry (hidden 1152 / 16 heads / depth 27 / intermediate 4304
/ out 5120 / patch 16 / temporal 2 / merge 2 / pos 2304, 3 DeepStack mergers).

- New TU src/vllm/model_executor/models/minimax_h3_vision_gguf.cpp (+ CMake).
- CPU gate test_minimax_h3 "the encoder GGUF visual.* loader dequantizes the vision tower":
  builds a synthetic visual.* GGUF (Q8_0 projections + F16/F32), loads it, asserts every field
  is filled at the flat [out*in] size the tower reads, the pre/post-shuffle merger norm widths,
  Q8_0 dequant within block tolerance + non-degenerate, and the production config geometry. The
  loader is encoding-agnostic so Q8_0 exercises the exact path the shipped Q4_K/Q5_K takes.
- Driver examples/minimax_h3_gen --prompt-image: routes a real image through the shared
  Qwen3VLImageProcessor + Qwen3VLVisionForward on the GGUF-loaded weights and reports the
  conditioning feature stats (finite / rms / maxabs) — the real-weights proof. Scattering the
  merged/deepstack features into the DiT-conditioning path is the tracked residual (spec §8.8).

deepstack_visual_indexes is inferred ({8,16,24}); the weights-only ComfyUI GGUF does not carry
it. Flagged in the header + spec as needing upstream-config confirmation for a bit-correct
DeepStack inject. Pre-existing preflight red (check-fusion-consistency
minimax_h3_video_vae_device) is not this row.

FOLLOWING_AGENTS_PROTOCOL
Assisted-by: Claude Code:claude-opus-4-8 [ClaudeCode]
…sage check

The probe runs the vision tower only (from --encoder) and returns before the DiT
section, like --decode-latent/--roundtrip, so it must not require --dit/--video-vae/
--out. Add it to the usage-check bypass.

FOLLOWING_AGENTS_PROTOCOL
Assisted-by: Claude Code:claude-opus-4-8 [ClaudeCode]
… ref2va still grids

Deliverable-1 core VERIFIED on dgx: --prompt-image loaded the real visual.* tower and ran
Qwen3VLVisionForward → [256,20480] all finite + non-degenerate (merged rms 1.45 / maxabs 29.1
Qwen massive-activation; 3 deepstack finite). CPU loader gate passes standalone (59 assertions).

Deliverable-2 renders (frame-sanity, visually inspected):
- fl2va COHERENT: FL2VA GGUF --dequant-bf16 + real --first-frame (VAE-keyframe) +
  --partition fl2va, 512x512/22f → all 22 frames a coherent orange cat on a wooden table
  matching the conditioning frame, no grid. PASS.
- ref2va STILL GRIDS: Ref2VA NVFP4 --fp4-resident + real --ref-image + --partition ref2va,
  256x256/22f → every frame a patch grid. Honest vs the mission's expectation: landing the
  LOADER does not fix ref2va because the tower is a loader+probe, NOT yet scattered into the
  DiT render-conditioning. fl2va coherent (same session) ⇒ DiT/VAE/partition sound; the ref2va
  grid is specific to the ref2va conditioning assembly. Residual: scatter merged features into
  prompt_embeds + DeepStack inject into the device text tower, then re-test.

Records: spec §8.8 (+GB10-verified block) + §8.2 supports-audit row, STATUS/BENCHMARKS/FEATURES
H3 rows, benchmark-record (GB10 verification), state (2026-08-07T03:20), NOW. Box left clean.
Pre-existing preflight red (check-fusion-consistency minimax_h3_video_vae_device) is not this row.

FOLLOWING_AGENTS_PROTOCOL
Assisted-by: Claude Code:claude-opus-4-8 [ClaudeCode]
mudler added a commit that referenced this pull request Aug 7, 2026
…ision tower loads + runs (#86)

row/H3-CONDITIONED-E2E squash. Record reconciliation: the vision-tower
MATH existed and was gated at reduced dims with synthetic weights
(minimax_h3_encoder.cpp:572 + surround), but was never wired to real
weights — the GGUF encoder loader skipped every visual.* tensor and the
device text forward has no deepstack arg; #26 understated the gap, #77
was right in what matters. Wired now: LoadQwen3VLVisionFromGguf
dequantizes the on-box encoder's full visual.* tower (27 blocks + patch
embed + pos embed + merger + 3 deepstack mergers, shared Qwen3.6-27B
vision geometry) into the existing Qwen3VLVisionWeights reuse; CPU
loader gate + a driver --prompt-image probe (real 512x512 image ->
[256,20480] finite, the Qwen massive-activation signature).

Frame sanity on GB10: fl2va with a real first-frame renders a coherent
photorealistic scene MATCHING the conditioning frame (pose/table/
background) — the second conditioned modality works. ref2va still
grids, now for a PRECISE reason: the loaded tower is not yet scattered
into the DiT render-conditioning (the render never used it); fl2va
coherent in the same session proves DiT/VAE/partition sound. Residual =
vision->prompt_embeds scatter + DeepStack device injection + confirm
deepstack_visual_indexes {8,16,24} against upstream. Known flake: full
test_minimax_h3 SIGSEGVs at a pre-existing unrelated CUDA case that
passes standalone (cross-test resource accumulation).

FOLLOWING_AGENTS_PROTOCOL
Assisted-by: Claude Code:claude-opus-4-8 [ClaudeCode]
@localai-bot

Copy link
Copy Markdown
Collaborator Author

Landed on main as the squash (mudler authorship). The vision->DiT-conditioning scatter runs as the next box campaign — the ref2va grid's now-precise residual.

🤖 Generated with Claude Code

https://claude.ai/code/session_014fZAcg1WcU8V629k6HWKys

@localai-bot localai-bot closed this Aug 7, 2026
mudler added a commit that referenced this pull request Aug 7, 2026
…Stack→device text tower) + ref2va re-attribution

Closes the #86 framework residual and re-attributes the ref2va grid with a GB10
render A/B (row/H3-VISION-SCATTER).

deepstack_visual_indexes CONFIRMED (was #86-inferred): {8,16,24}, grounded in the
release config — MiniMax-H3's text_encoder/ IS Qwen3-VL-32B-Instruct, whose
vision_config.deepstack_visual_indexes = [8,16,24], depth 27, text num_hidden_layers
64 (→50). Same as vllm-omni Qwen3VLMoeVisionConfig default and public
Qwen/Qwen3-VL-30B-A3B. No value change; comment updated.

Deliverable 1 — DEVICE scatter+inject WIRED 1:1 + GATED. MiniMaxH3EncoderTextForwardDevice
now takes the optional visual_pos_mask + per-tap deepstack blocks and ADDS each block
into the masked visual rows after each of the first len(deepstack) decoder layers —
device mirror of the gated host reference and upstream _deepstack_process
(encoder.py:770-800). The merged masked_scatter into inputs_embeds stays the caller's
job (upstream _encode, encoder.py:1071). Text-only prompts pass the defaults and are
byte-identical. Gate: the device keep-quant encoder test now also runs WITH a visual
mask + two DeepStack blocks → device==host max|diff| 3.8e-4 (<=2e-3) AND DeepStack
moves the conditioning (scale 1.006->1.062).

Driver --cond-image routes a reference image through the encoder vision path (reuse
only: Qwen3VLImageProcessor -> Qwen3VLVisionForward -> merged + 3 DeepStack;
ExpandImagePlaceholders inserts nm image-pad tokens; masked_scatter; Qwen3VLGetRopeIndex
== H3 _get_rope_index for t==1, position math verified). Additive.

GB10 render A/B (256x256/22f/12steps):
* Deliverable 3 — fl2va WITH the encoder vision path = COHERENT + matching (PASS):
  frame 0 the keyframe cat, frame 21 the cat on a WINDOWSILL in warm sunlight (evolved
  toward the prompt). No grid. The vision conditioning is SOUND.
* Deliverable 2 — ref2va WITH the vision-enriched prompt STILL GRIDS (honest FAIL):
  same multicolour patch grid as #86's text-only ref2va.

RE-ATTRIBUTION (evidence): the "vision-enriched conditioning fixes the grid"
hypothesis is REFUTED. NOT the encoder conditioning — DiT math byte-exact (#74/#77),
the SAME vision path renders a coherent fl2va, and the grid is invariant to text-only
vs vision-enriched prompts. The only difference between coherent fl2va and gridding
ref2va: fl2va PINS output rows (keyframe), ref2va PREPENDS free-running reference rows.
Residual = the ref2va reference-row conditioning ASSEMBLY (MiniMaxH3EncodeReferenceImages
VAE-reference rows + minimax_h3_packed_sequence_ref2va_blocks noised-anchor layout +
un-pinned target-row denoise), NOT prompt_embeds and NOT the DiT forward.

Records: spec §8.9 + §8.2 row, STATUS/BENCHMARKS/FEATURES H3 rows, benchmark-record,
NOW, state.

FOLLOWING_AGENTS_PROTOCOL
Assisted-by: Claude Code:claude-opus-4-8 [ClaudeCode]
jefby pushed a commit to jefby/vllm.cpp that referenced this pull request Aug 7, 2026
…was vacuous

Self-reported violation and its prevention.

PR mudler#80 (Vulkan, merged 5397e91) landed EIGHT commits that touched src/ and
tests/ without updating docs/STATUS.md and docs/BENCHMARKS.md in the same commit,
reddening documentation-checkpoint for that push range: 9579f94, ba5ea0c,
196ea46, e32c5ed, 3bfa1f1, 34a3efe, 2c86f79, f4738bb. Each deferred its doc
update into a following record(...) commit.

WHY IT PASSED LOCALLY, which is the part worth fixing. check-doc-checkpoint.py
--staged inspects the STAGED paths, so it is VACUOUS when nothing is staged --
and nothing is staged after `git commit`, which is exactly when agent-preflight.sh
runs it. The gate reported OK on every one of those commits while checking
literally nothing. CI is diff-scoped over the pushed range and checks each commit
independently, so the failure only surfaced on main, where a diff-scoped range can
never be re-covered by a later run.

Preflight now runs --base origin/main --head HEAD whenever the branch is ahead.
That check is deliberately OUTSIDE the --staged block: my first attempt nested it
inside, which reproduces the identical hole one level up, since --staged is
precisely the flag you are not passing when the range is unchecked. Verified two
ways -- it prints "ok doc-checkpoint range" here, and pointed at the mudler#80 range it
raises 20 errors naming every offending commit. workflow.md carries the same
instruction with the reason.

NOT REPAIRED BY REWRITING HISTORY: main had already moved (mudler#86 landed on top) and
other sessions branch from it, so force-pushing to regroup eight commits' files
would cost more than the defect. Substance was never wrong -- STATUS, BENCHMARKS
and FEATURES on main all describe the shipped state with the llvmpipe-only and
no-speed-number caveats intact. What was violated is the per-COMMIT granularity
that keeps a bisect landing on a commit whose docs match its code.

Rule restated where it is enforced: a feature commit carries its OWN
STATUS/BENCHMARKS update; when the numbers are not yet known the honest line is
pending/void with the reason, which is what the gate's own message asks for.

FOLLOWING_AGENTS_PROTOCOL
Assisted-by: Claude Code:claude-opus-5 [Claude Code]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants