Repository navigation
idea: H3 audio-hold in the denoise loop + refine pass after latent upscale (follows #471/#500) #598
Description
Idea, not approved work. Recorded from a comparative review; needs a decision before any implementation.
Gap
#471 shipped the H3 latent upscale (dw/tasks/h3_latent_upscale.py), but the refine pass was deferred (docs/proposals/complete/h3-latent-upscale-complete.md §Deferred), and the #500 gate judged upscale-only output "close but soft".
Idea
Two linked pieces:
- Audio-hold in the H3 denoise loop ("audio drive"). VAE-encode a fixed audio track (resample to the audio VAE rate), fit it to the template audio latent (crop/pad), and keep it un-denoised: per-stream mask of 1 for video, 0 for audio, so only video is sampled to the fixed audio. Mux the original waveform at the end, not the VAE round-trip.
- Refine pass on the upscaled latent. Upscale (pixel or latent), concat the stage-1 audio latent held as in (1), re-noise to a low sigma and run a few steps. Their shipped settings: denoise ~0.2, 4-5 steps; stage 1 at 0.4-0.5 MP with the turbo LoRA. A 3-pass variant repeats stage 2 at 2 MP.
Possible value
- Closes the "soft" gap that kept the #471 stage 2: minimax/upscale-preview template + skill line (gated on Don's stage-1 comparison) #500 path from shipping.
- Audio-hold on its own makes music-video and dialogue shots generate to the real track. Today
chain-matched-to-audioonly passes the slice as a reference and H3 still generates its own audio latent. That should tighten lip sync.
Costs / open questions
- Needs a dw-owned denoise block: diffusers'
modular_pipelines/minimax_h3/denoise.pystepsaudio_latents[num_condition_audio_rows:], so it must either skip the audio scheduler step or re-impose the encoded audio after each step. - Cost estimate for the refine stage (the validate_workflow: templates/minimax/music estimate drops to basis unknown when audio_duration (a ceiling) is lowered #440 cost-driver story).
What would unpark it
A lem A/B on the #500 workflows: upscale-only vs upscale + 5-step 0.2 refine.
Their files: VRGDG_MiniMaxH3AudioDrive.py, Workflows/UsedForUIDoNotTouch/minimax_*2pass*/3pass*_api.json, scripts/build_minimax_h3_ref2va_2pass_audio_api.py.
Source: concept observed in vrgamegirl19/comfyui-vrgamedevgirl (assessed 2026-10-05). That repo is under a source-available license that is incompatible with our Apache-2.0: re-implement from the concept only, do not copy code, prompt text, or presets verbatim. File references below are to their repo, for understanding the mechanism.
Activity
dkackman commentedon Oct 5, 2026
Approved by Don (2026-10-05) for a full build: plan both pieces, audio-hold in the H3 denoise loop and the refine pass after the latent upscale, as one feature. No A/B gate before planning. The lem A/B against upscale-only (and #612's LMS upscaler, if it lands first) belongs in the plan's acceptance cases. Handing to the feature lead for a plan.
dkackman commentedon Oct 5, 2026
Plan v4: H3 audio-hold + refine pass after latent upscale (#598)
Written by model
claude-opus-5-5via provideranthropic. v1 was a design session; v2 a decompose session (folds in Don's approval of v1); v3 a design session (2026-10-06), which folded in Don's choice of refine schedule (B) from #620, recorded what stages 1 and 4 shipped, and corrected stage 2 against installed diffusers. v4 is a decompose session (2026-10-06): it folds in Don's approval of v3, turning Q1 and Q2 into decisions D7 and D8. Scope is unchanged: no new surface, and the remaining stages are the same two.Status of the stages
hold_audio): shipped and verified.AudioReference,hold_audiois opt-in engine surface, and the guide, TASKS.md and SKILL say so with the numbers. D4 is superseded, and D7 makes the fallback final (see Decisions).upscale-refine+ A/B gate): not built.Verdict: build the remaining two stages, with the refine template still gated on its A/B
h3_blocks.py, the one place dw modifies a modular pipeline (D1), covered by the anchor-pin test;refine_strength. There is no MCP tool, soSURFACE_BUDGETis untouched;COMPACT_BUDGET(tests/test_catalog_structure.py:431, 9_550) has about 90 tokens of headroom and needs a raise of about 100;set_timesteps(sigmas=...)applies no shift. It reads the shift from the loaded scheduler and holds no constants.refine_strengthis opt-in. A run that doesn't name it is unchanged, and the no-op insertion keeps the cache key. If the gate fails, the template goes and the engine stays as guide surface, as with #471 stage 1: upscale_h3_latents + decode_h3_latents tasks, no refine (the measurement) #499.PrepareLatentsSteptreats a passedlatentsas pure noise, andSetTimestepstakes no strength.denoise, which starts near σ 0.6 at shift 6 and re-noises far more than detail.h3_blocks.pyand the anchor-pin test (Don's ordering). If #598 stage 2: H3 refine schedule after latent upscale (refine_strength) #620 is declined, #611 stage 1: H3 multi-frame video guides (guides argument, layout step) #648 goes ahead on #598 stage 1: H3 audio-hold blocks in the denoise pipeline (hold_audio) #618's seam alone.Proposed follow-up (Don decides): none.
Problem and non-goals
Problem. H3 can't re-denoise an upscaled latent from a low sigma. (Generating to a fixed soundtrack is solved: #618.)
Non-goals:
downloads_required;Design (stage 2, corrected)
Correction (v3): plan v2's schedule could not run. v2 said the refine runs "the grid points below σ₀", about 4-5 evaluations at 9 steps, shift 6, strength 0.2. Against the installed
MiniMaxH3Scheduler, the 9-point shift-6 grid is1, .977, .947, .909, .857, .783, .667, .462, 0. No point lies in (0, 0.2], so v2's schedule gave zero evaluations (#620, build session).The schedule (D6, Don's option B). Start at exactly σ₀ =
refine_strength. Takenum_inference_stepspoints from σ₀ down to 0, spaced by the scheduler's shift: linear in the unshifted coordinate u, from u₀ = σ₀ / (shift − (shift−1)·σ₀) down to 0, mapped through σ = shift·u / (1 + (shift−1)·u). That givesnum_inference_steps − 1evaluations. Some values:num_inference_steps0.2, .157, .109, .057, 00.2, .166, .129, .089, .046, 0What the installed diffusers requires (from the v3 sweep):
set_timesteps(sigmas=...)applies no shift and appends nothing. It raises unless the σ list is strictly decreasing and ends at exactly 0.0 (scheduling_minimax_h3.py:160-167; timesteps = 1 − σ[:-1]). So the block applies the shift itself, appends the 0, and refuses a list that collapses (num_inference_steps< 2).denoise.py:222-241callsaudio_scheduler.stepeven when every audio row is held. It needsaudio_timestepsthe same length as the video's, and an exactindex_for_timestepmatch. The block therefore resets the audio scheduler with the same σ list. The values don't matter, because no audio row is stepped, and the block asserts that every audio row is a held condition row before the loop runs.SetTimestepswritestimesteps,audio_timestepsandrow_timestep_plan(before_denoise.py:1245-1266). The block rebuilds all three, the plan throughbuild_row_timesteps(:1197-1236), so condition rows keepmax(t, keyframe_noise_aug).latents(before_denoise.py:1030, 1109-1121). The block re-noises onlylatents[num_condition_video_rows:]: x = (1−σ₀)·x₀ + σ₀·ε, with ε from the step's generator so a run is seeded.latents: a (1, 24, F, H, W) tensor skips the noise draw (before_denoise.py:865-879). That matchesupscale_h3_latents' output, provided the step's width and height equal the upscale target.Placement. The block runs after
set_timestepsin each of the three core-denoise sequences, so it goes in just beforedenoise.insert_audio_hold(h3_blocks.py:325-354) inserts before an anchor, so the refine block anchors on the denoise block. Three things change with it:blocks()(:283-288) returns three blocks and caches them, and:333changes from unpacking two;:339) covers the refine block too;introspection.py:361-380andcomponents.py:634-636already callinsert_audio_hold, soget_pipeline_signatureshowsrefine_strengthwith no further change.It does nothing unless
refine_strengthis set (D2).Validation. The static checks sit beside hold's, in
dw/hold_audio.py:68-91anddw/validation.py:315-317; the runtime check sits beside_with_held_audioinpipeline.py:615-639. A step is refused when:refine_strengthis not a number in (0, 1);latents;hold_audio;num_inference_stepsis below 2.Cost (corrected from v2). Refine time scales with
num_inference_steps, not independently of it.dw/plan.py:398-409resets a catalog cost to unknown only for a declared cost driver. So the stage 3 template declaresrefine_strengthin itscost_drivers, along withnum_frames,num_inference_steps,widthandheight. Otherwise a changed strength would be quoted at the template's measured cost. Progress needs no change: the total comes fromlen(timesteps)(progress.py:87-144), and N points report N−1 steps, which is what the docs already say.Surfaces decided here:
refine_strength(D3).docs/ARCHITECTURE.md:95changes "two blocks go in" to three.Stages remaining
Build order: 2 → 3. Each stage is blocked by the one before it. No cross-feature blockers. #620 blocks #611's #648.
Stage 2 (#620): refine schedule (engine), est. $6-9, deploy: server
DwH3RefineScheduleStepas designed above, inserted beforedenoisein all three core-denoise sequences;ARCHITECTURE.mdrow.0.2, .157, .109, .057, 0to 3 dp, and is strictly decreasing and ends at 0;set_timesteps(the block runs after it);refine_strengthleaves outputs identical (CPU tiny-tensor).WORKFLOW_GUIDE.mdPromoting an H3 take to 768p in latent space:upscale_h3_latents→ H3 at 1344x768 withlatents: previous_result:up,refine_strength: 0.2,num_inference_steps: 5,hold_audio: previous_result:base.audio;test_modular_output_properties.py:96-102reads the first JSON block after the heading;upscale_h3_latents,pair_audio,previous_result:base.videosand"fps": 24in the section, fortest_server_guides.py:263-278;num_inference_steps − 1.get_guideserves it, and it validates and completes at 1344x768 with 124 frames. The output audio is the base pass's track.get_job_eventsshow 4 denoise steps in the refine step atnum_inference_steps: 5, and 5 at 6.get_output_frames.refine_strengthof 0, 1.0, −0.2, 1.5 or"0.2";latents;hold_audio;num_inference_steps: 1withrefine_strength.Stage 3 (#621):
templates/minimax/upscale-refine+ the A/B gate, est. $5-7, deploy: server (catalog) + pluginBuilds:
upscale_h3_latents, then refine at 1344x768 with the 768p LoRA atvideo_shift6,num_inference_steps5 andrefine_strength0.2 (D8), holding the base audio;test_h3_schedule.py:scheduler.shift=variable:video_shift(12 or 6),audio_scheduler.shift=variable:audio_shift(3), LoRA alpha =variable:lora_alphadefaulting to null, and no 20 steps;cost_driversincludingrefine_strength;costandvram_estimate, from arm (b);COMPACT_BUDGETraised by about 100;minimax-h3SKILL row. The SKILL is at 10,872 of 12,288 bytes.Acceptance intent: the A/B, unchanged from v2. The #471 stage 2: minimax/upscale-preview template + skill line (gated on Don's stage-1 comparison) #500 crowd-faces prompt, seed 42, 124 frames, on lem, with these arms:
video-with-audio-768p;For each arm the tester records minutes and face crops. (b)'s minutes must be within 50% of the template's
cost.The gate is Don's call. If he says no, the template and SKILL row are reverted before close-out.
Risks
Decisions
ARCHITECTURE.mdrow namingh3_blocks.py.hold_audioandrefine_strength.refine_strength, thennum_inference_stepsshift-spaced points to 0, which isnum_inference_steps − 1evaluations. Refine cost scales withnum_inference_steps.hold_audiois opt-in. A re-measure of hold is out of this feature; it would come back as its own idea if a field report shows reference lip sync failing where hold might help.num_inference_steps5 (4 evaluations) andrefine_strength0.2. The guide example matches.Questions
None open. #621's template stays gated on its A/B, and that gate is Don's call.
dkackman commentedon Oct 5, 2026
Plan v1 approved by Don (2026-10-05).
hold_audioandrefine_strength;AudioReference.Two corrections from checking installed diffusers (
modular_pipelines/minimax_h3):prepare_latents→set_timestepsexist only inside the three core-denoise sequences:MiniMaxH3CoreDenoiseStep,MiniMaxH3FL2VACoreDenoiseStepandMiniMaxH3Ref2VACoreDenoiseStep, the branches ofMiniMaxH3AutoDenoiseStep. The hold block has to go into each of them, betweenprepare_latentsandset_timesteps, not ontoMiniMaxH3Blocks. The anchor-pin test should cover all three. Ref2VA's prepare-latents also runs a reference-row count check (before_denoise.py:1113). Extend the count after that check, never before it.MiniMaxH3SetTimestepsStep.__call__callsscheduler.set_timesteps(num_inference_steps)itself (before_denoise.py:~1245), so a tail schedule set before it is overwritten. The refine block must run afterset_timesteps. It has to:sigmas=;timestepsandrow_timestep_plan;zip(timesteps, audio_timesteps)pairing. The shortened video schedule pairs with the head of the full audio schedule. That is harmless only while every audio row is a held condition row, so assert it rather than relying on it.Confirmed while checking: target audio rows at t=1.0 are never stepped (
denoise.py:235), and the checkpoint is guidance-distilled with no unconditional pass to drop them, so the "no replacement loop" correction stands.56 remaining items
Merge branch 'docs/598-record-fix-forward' into develop (#598)
Merge branch 'docs/598-record-fix-forward' into develop (#598)
dkackman commentedon Oct 7, 2026
Close-out: fix-forward #664 closed; handing the parent to the tester for the final check. Written by model
claude-opus-5-5via provideranthropic(close-out session, 2026-10-07).Closed: stages #618, #619, #620 and #621, plus fix-forward #664 (the
upscale-refinetemplate was reverted in125818e7, verified 2026-10-07). The engine half of refine (refine_strength) stays as opt-in surface.Design record:
docs/proposals/complete/h3-audio-hold-refine-complete.md, which now has a Fix-forward: #664 section, andtodo.mdis updated. Ondevelop@ 6ec9a77. These are docs only, with no deploy.verify: run every case whose
source:names one of this feature's stages, and close the parent if they all pass. Per Don's decision 2 (2026-10-06), the GPU cases are credited from their stage verifies and are not re-run. The final check is free only:templates/minimax/upscale-refineis gone fromlist_workflows;minimax-h3skill no longer names it;Non-blocking, per Don: M-F077 (no fixture), the M-F079(a) fixture ruling, harnest#62/#66, the M-F086 wording request, and retiring M-F085/M-F086/M-F099.
dkackman commentedon Oct 7, 2026
Verified (parent final check). Tester: claude-opus-5-5 via anthropic, against lem develop @ 7f524c4. Free calls only, following Don's decision 2: the GPU cases count from their stage verifies (#618, #619, #620, #621) and were not re-run.
upscale-refineis gone from the catalog.list_workflows(shape="shot", traits="has-audio")lists 34 workflows, and none istemplates/minimax/upscale-refine. The minimax entries that remain are the existing templates, includingvideo-with-audioandvideo-with-audio-768p.minimax-h3skill no longer names it. I loaded the skill from the plugin tree at develop. It doesn't mentionupscale-refineor a refine template anywhere.hold_audiois still described as opt-in in its Hard rules.get_guide("workflows", section="Refining the upscaled latents")returns "Refining the upscaled latents: refine_strength", and it doesn't reference the template. I passed its JSON example tovalidate_workflowunchanged:valid: true, no errors, no warnings, 3 steps, estimate basisunknown. The engine surface stays as Don ruled:refine_strength,hold_audio: previous_result:base.audio, and the refusal list.Not blocking (named by Don): M-F077 (no fixture), the M-F079(a) fixture ruling, harnest#62/#66, and the M-F086 wording request.
Side note, filed separately: the guide's prose doesn't match its own example. The prose says "
5points at shift 6 and strength 0.2 are sigma0.2, 0.157, 0.109, 0.057, 0". But the example's refine step takesscheduler.shift: variable:video_shift, which is 12.0, with the fl2v 8-step LoRA. That reads like a leftover from the reverted template, which used the 768p LoRA at shift 6. It's a doc nit and doesn't block this close.docs(proposals): dkackman#598 close-out - design record for H3 audio-…
docs(proposals): dkackman#598 - record Don's no on the refine gate; t…
docs(proposals): dkackman#598 - record the dkackman#664 fix-forward r…
docs(proposals): dkackman#598 - name the reverted template by catalog…