Repository navigation
feat: cache_dit warm-up steps, per-step flow shift, per-request low-noise guidance - #58
Conversation
--cache-dit-max-warmup-steps sets how many leading steps cache_dit always computes before reusing (on Wan 2.2 A14B, the high-noise expert's). Under Wan's official 40-step/shift-12 schedule the default 4 lets the cache reuse layout-deciding steps (extra objects, broken limbs in review); 8 restores uncached-level fidelity at 193 s/clip on 1xB200. The video handler now takes guidance_scale_2 from the request's nvext, which the frontend already carries, falling back to the config default as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The right shift depends on the step count. Measured on Wan 2.2 A14B (1xB200, 720p): shift 12 at 40 steps gives the reviewed look (saturation 0.26); at 27 steps the same shift starves the high-noise expert (18 of 27 steps) and colour drifts back toward the old production look (0.39); shift 20 at 27 steps restores most of it (0.33) and was picked by review. --flow-shift-by-steps '27:20,40:12' rebuilds the scheduler only when the request's step count maps to a different shift, and refuses unmapped counts rather than run an untested schedule. The handler's generate lock makes the swap safe. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Thachnh
left a comment
There was a problem hiding this comment.
Sauron · Review of #58 together with deepinfra/backend#6590
Verdict: the code looks right. The rollout order in backend#6590 is broken because of how this PR rejects step counts. Fix the order before step 2 runs.
Blocking (rollout, not code): unmapped step counts fail every request during the config-switch window
_use_flow_shift_for raises ValueError for any step count missing from the map (diffusion_engine.py, in generate()). The merge gate in backend#6590 says:
- Model config: new image,
--flow-shift-by-steps=27:20,40:12… Until deepapi deploys, the worker still gets 50 steps (226 s at the new schedule).- Then merge this PR and deploy deepapi.
But deepapi on main hardcodes Wan22T2VIn.num_inference_steps = 50 and always sends it in nvext. Between step 2 and step 3, every Wan2.2 request becomes --flow-shift-by-steps has no shift for 50 steps. It does not run at 226 s. The worker's own fallback, --default-num-inference-steps=50 (baked in redis_model_manager.py), hits the same refusal.
Fix: in step 2, map 50 as well, e.g. --flow-shift-by-steps=27:20,40:12,50:12 (or 50:3 if you want the old look until cut-over). Drop 50: in a step 4, after deepapi deploys. Separately, the baked --default-num-inference-steps should move to 27 (I left a comment on backend#6590).
Checked and fine
- Scheduler swap is serialized.
engine.generateruns only insideasync with self._generate_lock→asyncio.to_thread, soapply_flow_shiftreplacingpipeline.schedulercan't race another denoise loop. - Warm-up is unaffected. VisualGen's warm-up runs inside
loader.load(), not throughDiffusionEngine.generate, so the step-count map can't fail worker init. With no--flow-shift,_scheduler_shiftstarts asNoneand the first request always applies its mapped shift. guidance_scale_2is carried end to end. It's inVideoNvExt(common/protocols/video_protocol.py:46) and in the Rust frontend nvext (lib/llm/src/protocols/openai/videos/nvext.rs:62), so the field survives the frontend. Falling back to--default-guidance-scale-2keeps today's behaviour for callers that don't send it.max_warmup_stepsis passed through like the other cache_dit overrides, andNonekeeps VisualGen's default.
Nits (non-blocking)
parse_flow_shift_by_steps("27:20,27:12")silently keeps the last value, and it accepts27:0or negative shifts. A one-line duplicate check and ashift > 0check would turn an operator typo into an init error instead of a quiet schedule change.- An unmapped step count raises a
ValueErrorfrom inside the engine. That's fine while deepapi is the only caller and enforcesLiteral[27, 40], but the handler returns it as a generic generation failure. If a non-deepapi caller ever appears, a 4xx-shaped error would be kinder.
Not done: I did not run the test suite (no TRT-LLM image here). The review is from reading the diff plus the surrounding handler/engine/protocol code at cf702bbb.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
temirulan
left a comment
There was a problem hiding this comment.
Sauron · Second pass at ea9bd1fe. Not approving yet.
The only new commit since my last review is ea9bd1fe (black formatting, no behaviour change), and it looks fine. The one blocking item from the first pass is still open. It is a rollout issue, not a code defect in this PR:
- Blocking (rollout):
_use_flow_shift_for()raisesValueErrorfor any step count missing from the map. Deepapi on main still hardcodes 50 steps for Wan2.2 (Wan22T2VIn.num_inference_stepsreturns 50 indeepinfra/deepapi/inf_models/ttv.py). Step 2 of backend#6590's merge gate sets--flow-shift-by-steps=27:20,40:12, so from then until deepapi deploys, every Wan2.2 request fails. The PR text assumes those requests keep running at 50 steps / 226 s. Two ways to fix it: add50:12(or50:3) to the map for that window and drop it once deepapi is live, or reorder the gate. - Nit (unchanged, non-blocking):
parse_flow_shift_by_stepssilently accepts duplicate step keys (last one wins) and non-positive shifts.
I'll approve once the rollout plan in backend#6590 (or the map used during the transition) no longer breaks the 50-step window.
…hift-by-steps An operator typo (27:20,27:12 or 27:0) now fails worker init instead of silently changing the schedule. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks. Both items are addressed:
|
temirulan
left a comment
There was a problem hiding this comment.
Sauron · Third pass at 26d503b9. Approving. Everything from the earlier rounds is resolved:
- Rollout: the transition map is now
27:20,40:12,50:12. The worker keeps serving the 50 steps that deepapi on main sends, and50:12is dropped after backend#6590 deploys. I traced each phase of the merge gate and every step count deepapi sends is mapped in each one. That includes the baked worker default (50 before backend#6590 merges, 27 after). - Parser: duplicate step counts and shifts ≤ 0 now fail at init. I checked this against the edge cases (
27:20,27:12,27:0,27:-3and the malformed forms all raise). - Tiny non-blocking nit:
27:nanand27:infstill parse, becausefloat()accepts them andnan <= 0is False. Amath.isfinite(shift)check would close that off if you want it, but it's an operator-typo edge case and not worth another round on its own.
Agreed on leaving unmapped step counts as a generic failure while deepapi is the only caller.
What
--cache-dit-max-warmup-steps(envDYN_TRTLLM_CACHE_DIT_MAX_WARMUP_STEPS): passed toCacheDiTConfig(max_warmup_steps=…), the leading steps cache_dit always computes before it starts reusing. On Wan 2.2 A14B this is the high-noise expert's warm-up (the low-noise expert keeps VisualGen's own). Unset keeps VisualGen's default (4).Per-request
guidance_scale_2: the video handler now reads it from the request'snvext(the frontend already carries the field) and falls back to--default-guidance-scale-2as before.guidance_scalealready worked this way.--flow-shift-by-steps(envDYN_TRTLLM_FLOW_SHIFT_BY_STEPS), e.g.27:20,40:12: each request runs with the shift mapped to its step count. The scheduler is rebuilt only when the shift changes, and an unmapped step count is refused (ValueError) rather than run on an untested schedule. Duplicate step counts and shifts ≤ 0 fail worker init. The swap is safe because the video handler serializesgenerate()behind its lock.Why the shift must follow the step count
Saturation tracks how many steps the high-noise expert gets (6 review prompts, mean HSV saturation; the reviewed look is 40 steps / shift 12):
So one fixed shift can't serve both 27 and 40 steps. deepapi offers exactly these two step counts.
Why
Under Wan's official A14B settings (40 steps, shift 12) the high-noise expert gets 26 of 40 steps, and with the default warm-up of 4 cache_dit reuses the steps that decide layout. In review that produced an extra milk jug, extra hands and impossible legs. LPIPS against the uncached render, 1xB200, 720p/81 frames:
8 is the chosen setting; reviewed by eye. It holds at 27 steps too (160 s/clip, 0.15 vs uncached 27 steps). One server alternating 27/40/50-step requests ran each at its steady-state time, so the step count does not trigger recompiles.
Per-request dual guidance lets deepapi expose both guidance values (e.g. Artificial Analysis sends 3.5 / 4).
Tests
TestCacheDitOverridescovers the new override;test_request_guidance_overrides_config_defaultscovers the handler. 84/84 pass in the…-swenc-ea1a31baimage, including the async handler tests, which needpytest-asyncio(the image doesn't ship it, so these tests didn't run before). Mutation-checked: removing the override line, or restoring the config-onlyguidance_scale_2, each fails exactly its test.TestFlowShiftByStepscovers parsing, switching only on change, and refusing unmapped step counts (mutation-checked: dropping the per-request call or the change guard fails it).Not enabled by this PR; the Wan config gets
--cache-dit-max-warmup-steps=8 --flow-shift-by-steps=27:20,40:12,50:12with the rollout;50:12covers the window before deepapi stops sending 50 steps and is dropped after (see the merge gate in deepinfra/backend#6590).🤖 Generated with Claude Code