Repository navigation
[Perf] Support TeaCache for MammothModa2 - #5357
Conversation
c7ff7ca to
52f2e3c
Compare
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
7955bdc to
44b035b
Compare
hsliuustc0106
left a comment
There was a problem hiding this comment.
Thanks for the rework — the extractor, the hook branch separation, and the calibration write-up are solid, and all five issues from the July 30 round are resolved (the string cache_config path is now covered by main's _normalize_cache_config, and Cache-DiT reaches Mammoth via the request-scoped path). Two blockers remain before this can merge.
1. Rebase required — the branch is still based on pre-#7476 main (blocking)
The head's pipeline_mammothmoda2_dit.py still has supports_request_batch = False and the old single-request forward, while main (since #7476, merged Sep 25) ships the batched _denoise_group forward. GitHub currently reports MERGEABLE, but a textual merge would effectively revert #7476. Please rebase onto current main — ideally after #7954 lands, since it refactors the same file — and port the branch hints into the batched forward (see the inline comment on the pipeline hunk for the exact placement and failure mode). The new test file also targets the old forward and needs rework along the lines of test_pipeline_mammothmoda2_dit.py::_pipeline_shell (second inline comment).
After the rebase, please re-run the e2e suite on the new base SHA and add at least one batched run (max_num_seqs: 8, 2+ concurrent requests) with --cache-backend tea_cache — the current numbers are single-request-era, and with batching the whole batch shares one skip decision.
2. The PR description no longer matches the diff (blocking, quick fix)
The Implementation Approach describes plumbing that was dropped during the September rebase: the stage engine_args forwarding, the MammothModa2ForConditionalGeneration enable path, and the pipeline-stored backend + in-forward refresh. Those responsibilities now live in diffusion_model_runner.py (enable at startup, _refresh_cache_for_requests per batch — this path is complete on main, and TeaCacheBackend.refresh ignores num_inference_steps, so the extra-body steps resolution is safe). Please rewrite the section to match what the PR actually contains today: the MammothModa2 extractor, the calibrated coefficients + rel_l1_thresh=0.075 default, the MammothModa2DiTPipeline enabler (gen_transformer + transformer alias), the pipeline branch hints, hook teacache_branch support, tests, and docs.
Non-blocking notes
- The example change (
cache_config = {}fortea_cache) shifts the effective threshold for every model using the example (hardcoded 0.2 → per-model defaults, e.g. 0.17 for MiniMax-H3). Reasonable, but worth a sentence in the description. - The coefficients were fitted single-request; with request batching the whole batch shares one skip decision. A note in the recipe would help set expectations.
pipeline.transformer = transformerin the enabler follows the existing Bagel pattern — fine; just be aware the transformer then appears under two names instate_dict()(shared parameters, no extra memory).
| ar_image_hidden_states=ar_image_embeds, | ||
| ar_image_attention_mask=ar_image_attention_mask, | ||
| freqs_cis=self.gen_freqs_cis, | ||
| teacache_branch="positive", |
There was a problem hiding this comment.
This hunk is against the pre-#7476 single-request forward. After the rebase, the hints must move into the batched _denoise_group forward: teacache_branch="positive" on the conditional gen_transformer(...) call and teacache_branch="negative" on the unconditional one (main: pipeline_mammothmoda2_dit.py:801 / :815, shifted by #7954's refactor).
Without these, TeaCacheHook.new_forward falls back to do_true_cfg detection — MammothModa2's transformer doesn't set it — so both CFG branches land in the same teacache_positive state and CFG outputs are silently corrupted. That's exactly the failure mode the branch hints exist to prevent, so please make sure the rebased e2e run exercises CFG (text_guidance_scale > 1.0) with the cache enabled.
There was a problem hiding this comment.
Thanks, addressed after rebasing onto current main.
The branch hints now live in the batched _denoise_group forward:
pipeline_mammothmoda2_dit.py:810:teacache_branch="positive"on the conditionalgen_transformer(...)call (main:801)pipeline_mammothmoda2_dit.py:823:teacache_branch="negative"on the unconditional call (main:815)
With the hints present, the hook uses them instead of the do_true_cfg fallback, so the two branches keep separate teacache_positive / teacache_negative state. test_mammoth_moda2_teacache.py now drives _denoise_group with 1 and 2 requests and asserts the branch order for the non-CFG, CFG and partial cfg_range cases.
The rebased e2e runs all use CFG (text_guidance_scale=4.0, cfg_range=[0,1]) with TeaCache enabled, both sequential and batched (4 concurrent requests, max_num_seqs: 8). At the default rel_l1_thresh=0.075, compared with main without cache:
- sequential: SSIM 0.973, LPIPS 0.021, DiT 1.19x faster
- batched: SSIM 0.977, LPIPS 0.014, DiT 1.21x faster
See "Test Result → Sequential" and "Test Result → Batched" in the PR description.
| return (latents,) | ||
|
|
||
|
|
||
| def _build_pipeline(monkeypatch): |
There was a problem hiding this comment.
This fake targets the old single-request forward(req) -> DiffusionOutput. After the rebase the pipeline exposes forward(req) -> list[DiffusionOutput] via _parse_request → _denoise_group; please rework the shell the same way tests/diffusion/models/mammoth_moda2/test_pipeline_mammothmoda2_dit.py::_pipeline_shell does (patched FlowMatchEulerDiscreteScheduler / randn_tensor, etc.). Keep _cache_dit_runtime on the fake — _denoise_group calls _prepare_cache_dit_for_request.
There was a problem hiding this comment.
Thanks for your carefully review. The test now drives the real MammothModa2DiTPipeline.forward(req) -> list[DiffusionOutput] path (_parse_request → _denoise_group) instead of the old single-request fake, following test_pipeline_mammothmoda2_dit.py::_pipeline_shell:
_build_pipelinepatchesFlowMatchEulerDiscreteSchedulerandrandn_tensor, and keeps_cache_dit_runtime = RequestScopedCacheDiTRuntime(pipe)for_prepare_cache_dit_for_request. The transformer and VAE are replaced with fakes; the fake transformer records theteacache_branchof each call._run_pipelinecallspipe(DiffusionRequestBatch(requests)), asserts that it returns alistofDiffusionOutputwith one entry per request, and that every transformer call getstimestep.shape == (num_requests,).- The branch-order tests (non-CFG, CFG, partial
cfg_range) are parametrized overnum_requests=[1, 2].
See "Regression Tests" in the PR description: this file plus test_cache_backends.py and test_teacache_extractors.py give 87 passed, and test_pipeline_mammothmoda2_dit.py gives 55 passed.
760863b to
c237ea7
Compare
Signed-off-by: nagisa-kun <1434936049@qq.com> Signed-off-by: nagisa <1434936049@qq.com>
Signed-off-by: nagisa <1434936049@qq.com>
Signed-off-by: nagisa-kun <1434936049@qq.com> Signed-off-by: nagisa <1434936049@qq.com>
Signed-off-by: nagisa-kun <1434936049@qq.com> Signed-off-by: nagisa <1434936049@qq.com>
Signed-off-by: nagisa-kun <1434936049@qq.com> Signed-off-by: nagisa <1434936049@qq.com>
Signed-off-by: nagisa-kun <1434936049@qq.com> Signed-off-by: nagisa <1434936049@qq.com>
Signed-off-by: nagisa-kun <1434936049@qq.com> Signed-off-by: nagisa <1434936049@qq.com>
Signed-off-by: nagisa-kun <1434936049@qq.com> Signed-off-by: nagisa <1434936049@qq.com>
Signed-off-by: nagisa <1434936049@qq.com>
Signed-off-by: nagisa <1434936049@qq.com>
Signed-off-by: nagisa-kun <1434936049@qq.com> Signed-off-by: nagisa <1434936049@qq.com>
Signed-off-by: nagisa <1434936049@qq.com>
c237ea7 to
cf4548e
Compare
|
@hsliuustc0106 Thanks for the careful review! Sorry about these issues: I didn't anticipate how #7476 changed the MammothModa2 DiT interfaces, so the previous revision still targeted the old single-request forward. The branch is now rebased onto the latest main (
I also re-ran the comparison between this PR and current main and updated the PR description. Could you please take another look at the changes? Thanks! |
Omni ReviewBot routing recordAssigned Strict on cursor (cursor-grok-4.6-high) under experiment |
Purpose
part of #1217
Related: #7106
Add TeaCache support for the MammothModa2 DiT stage (text-to-image).
Implementation Approach
The MammothModa2 DiT stage already runs in the shared diffusion runtime, so cache lifecycle is handled by
diffusion_model_runner.pyon main: the backend is enabled once at startup and refreshed per batch through_refresh_cache_for_requests. This PR does not add any stage- or model-specific cache plumbing; it only adds what TeaCache needs to understand MammothModa2's transformer.teacache/extractors.py):extract_mammoth_moda2_context, registered forMammothModa2Transformer2DModel. It reuses the transformer's own_validate_inputs/_prepare_embeddings/_apply_refinershelpers, builds the joint text+image sequence, and useslayers[0].norm1(...)as the modulated input. The transformer layers andnorm_out+ unpatchify are exposed asrun_transformer_blocks/postprocess.teacache/config.py): fitted MammothModa2 coefficients and a model-specific defaultrel_l1_thresh=0.075(see Coefficient Fitting).teacache/backend.py):enable_mammoth_moda2_teacache, registered inCUSTOM_TEACACHE_ENABLERS["MammothModa2DiTPipeline"]. It hooksgen_transformerand aliases it aspipeline.transformer, following the existing Bagel pattern. The transformer then appears under two names instate_dict(); the parameters are shared, so no extra memory is used._denoise_groupforward passesteacache_branch="positive"on the conditionalgen_transformer(...)call andteacache_branch="negative"on the unconditional call. MammothModa2's transformer does not setdo_true_cfg, so without these hints both CFG branches would share the same TeaCache state. The transformerforwardaccepts and drops the kwarg.teacache/hook.py):TeaCacheHook.new_forwardprefersctx.extra_states["teacache_branch"]when present (validated to bepositive/negative), and falls back to the existingdo_true_cfg/ CFG-parallel detection otherwise. Other models are unaffected.text_to_image.pynow passescache_config = {}fortea_cache, so TeaCache selects each model's own default threshold and coefficients. This changes the effective threshold for every model using the example, from the previously hardcoded0.2to the per-model default (e.g.0.075for MammothModa2,0.17for MiniMax-H3,0.2for models without a specific default).docs/user_guide/diffusion_features.md; recipe documents the TeaCache command and the default threshold.Note on request batching: the coefficients were fitted on single-request traces. With request batching (#7476) the whole DiT batch shares one skip decision per step. The batched e2e run below shows quality comparable to the single-request run at the default threshold, but quality under batching can still vary with the batch composition.
Test Plan
Hardware and runtime:
595.58.030.30.0, PyTorch2.13.0+cu130enforce_eager), single GPU, diffusion attentionCUDNN_ATTNbytedance-research/MammothModa2-Preview, snapshotef5a5e41dbf0de1ef6275586b7580f0d4248b4c6vllm_omni/deploy/mammoth_moda2.yaml(DiT stagemax_num_seqs: 8)Sources:
ee8fdab1dc26ea8f09d7910cdc98a17c4b0ea02a(current main)c237ea745678de5f3e2c3463492c16343526a337(rebased ontoee8fdab1d)rel_l1_thresh= 0.075 / 0.10 / 0.15 / 0.20Request settings for all runs:
Prompts:
Method:
Omni(...)withcache_backend="tea_cache", cache_config={"rel_l1_thresh": <thresh>}(or no cache). Model loading is excluded from all timings, and one full warmup request is discarded.generate()call, 2 repeats. The DiT stage actually formed a batch of 1 followed by a batch of 3 (the first AR request finishes slightly earlier), so the batched run covers several requests sharing one TeaCache skip decision.stage_durations(stage_0_gen_ms= AR,stage_1_gen_ms= DiT).peak_memory_mb, plusnvidia-smisampled every second for the whole process.Equivalent CLI (TeaCache, default threshold):
Test Result
Correctness without cache
With the cache disabled, this PR produced output pixel-identical to main for all 4 prompts in both sequential and batched mode. Latency also matched main (E2E 1.002x, DiT 1.001x). Every arm was deterministic: the two repeats of each prompt were pixel-identical, in both modes.
Sequential (single request) — speed and quality vs main
Mean of 4 prompts x 2 repeats.
E2E standard deviation was about 0.2-0.5 s per arm, and DiT standard deviation was at most 0.1 s. The AR stage (~61 s) dominates end-to-end latency on this GPU and is not affected by TeaCache, so the useful signal is the DiT-stage speedup.
Per-prompt results at the default threshold (0.075):
Per-prompt threshold sweep (sequential):
Batched (4 concurrent requests, DiT
max_num_seqs: 8) — speed and quality vs mainEach repeat submits all 4 prompts at once. Results are the mean of 2 repeats. Quality is compared with main's batched output for the same prompt.
At the default threshold, batched quality (SSIM 0.977, LPIPS 0.014) is in line with the sequential run (SSIM 0.973, LPIPS 0.021). The shared per-batch skip decision did not degrade quality in this sample. The CFG positive/negative branches stayed separated: outputs were deterministic across repeats and close to the no-cache baseline.
Note: on main itself, batched output differs from sequential output for the same prompt and seed (mean SSIM 0.87 on this set). The difference already appears for the request that ran its DiT step alone, so it comes from AR-stage batching, not from the DiT or this PR. For that reason, batched TeaCache output is compared with batched main output.
GPU memory
nvidia-smiprocess peak (MiB)The engine-reported value is the sequential DiT peak. The
nvidia-smipeak covers the whole process, including AR KV-cache pre-allocation and batched DiT. TeaCache adds at most 168 MiB (+1.6% of the reported peak), and adds nothing at the default threshold.Coefficient Fitting
The bundled MammothModa2 TeaCache coefficients were fitted from full-compute traces at 1024x1024 with 50 denoising steps. A 70-prompt calibration set was collected with the TeaCache hook installed but forced to full compute, recording each CFG branch separately. A degree-4 polynomial was then fitted, mapping the adjacent-step relative L1 distance of the modulated input to the relative L1 distance of the transformer output.
Fitted coefficients (default in
vllm_omni/diffusion/cache/teacache/config.py):Fitting data:
The default
rel_l1_thresh=0.075gives about 1.19-1.21x DiT speedup with SSIM about 0.97-0.98 and LPIPS about 0.01-0.02. Higher thresholds trade quality for speed, as shown in the sweeps above.Regression Tests
test_mammoth_moda2_teacache.pydrives the batchedforward(req) -> list[DiffusionOutput]path (_parse_request->_denoise_group) with 1 and 2 requests. It checks:cfg_rangecases;BEFORE SUBMITTING: read CONTRIBUTING.md and run the precheck-pr skill with the code agent for a self-check against project conventions.