Repository navigation
[Fix] diffusion: Keep dynamic LoRA deltas on fused linear-GELU sites - #45943
Merged
Merged
Conversation
The mounted linear+GELU epilogue multiplies linear.weight directly. A dynamic (unmerged) LoRA wrapper passes the static guards -- it has no quant_method of its own -- so the site skipped the wrapper's forward and dropped the adapter delta. With quality="lossless" as the default this hits every FUSED_LINEAR_GELU model serving a dynamic LoRA, including LTX-2 two-stage stage 2, whose distilled LoRA is dynamic in the default device mode (ff.proj_in ran without it). Take the module forward while the wrapper adds an adapter output; a disabled or merged adapter keeps the fused path. The static guards now judge a wrapper by the layer it wraps, so the wrapped layer's quantization, skip_bias_add and multi-rank gather_output are no longer hidden behind it. The module docstring still described the pre-lossless quality="high" gating. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every fusion in _QUALITY_FUSION_HANDLERS is now either exercised on a site whose linears carry a dynamic adapter -- mounted output must equal the unfused forward and differ from the base weight alone -- or listed with the reason its sites cannot drop one, so a new fusion has to pick a side. Also pins the linear-GELU guard to the adapter state (dynamic delta takes the forward; disabled or merged keeps the epilogue) and its static guards to the wrapped layer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every consistency-checked case requests quality="exact", so no server test ran a LoRA through the lossless-tier fusions, the path serving takes by default. wan2_1_t2v_1_3b_dynamic_lora_default_quality_1gpu serves the Arcane-Jinx adapter unmerged on Wan2.1-T2V-1.3B (480x272, 9 frames, 8 steps) and checks the default-quality output against the same server instead of a golden: it must match the quality="exact" request and differ from the request with the adapter turned off. Two DiffusionTestCase fields express these comparisons for any case: reference_quality and run_lora_effect_check. The case is skipped on AMD, whose PR lane parametrizes the same list: its thresholds are calibrated on NVIDIA only. The case's h100 perf baseline is seeded from its first GPU run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mickqian
requested review from
AgainstEntropy,
BBuf,
HaiShaw,
kevin-mii,
niehen6174,
ping1jing2,
yichiche and
yingluosanqian
as code owners
October 23, 2026 02:25
Collaborator
Author
|
/tag-and-rerun-ci |
This was referenced Oct 23, 2026
Merged
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 23, 2026
Merged
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.
Motivation
FUSED_LINEAR_GELUis in the"lossless"tier, andqualityhas defaulted to"lossless"since #42370, so the fused linear+GELU epilogue mounts by default. With a dynamic (unmerged) LoRA, the site's linear is aBaseLayerWithLoRA. Its.weightand.biasforward to the base layer, and it passed every duck-typed guard. Sofused_linear_gelu_tanh(x, proj_in.weight, proj_in.bias)multiplied the base weight only and silently dropped the adapter's delta.This affects every GELU site that serves a dynamic LoRA:
originaldevice mode, which is the default on H100/A100;CI did not catch it:
quality="exact";nn.Linear;Modifications
fused_linear_gelu_site.py:can_use_linear_gelutakes the module forward while a LoRA wrapper adds output (lora_active). A disabled adapter, or a merged one without an output offset, keeps the fused path, because the base weight the epilogue reads already holds the update.skip_bias_addand multi-rankgather_output.quality="high". It now describes the lossless tier.Tests:
test/unit/test_quality_fusion_lora.py: every fusion in_QUALITY_FUSION_HANDLERSeither runs on a site whose linears carry a dynamic adapter, or is listed with the reason it cannot drop an adapter's output. So a new fusion has to pick a side. On a site, the mounted output must equal the unfused forward and differ from the base weight alone. The file also pins the per-call guard to the adapter state and the static guard to the wrapped layer.wan2_1_t2v_1_3b_dynamic_lora_default_quality_1gpu: Wan2.1-T2V-1.3B serves the Arcane-Jinx adapter unmerged, at 480x272, 9 frames, 8 steps. The adapter carriesffn.0(=ffn.fc_in) LoRA weights on all 30 blocks. The case needs no golden: the default-quality output must match aquality="exact"request to the same server, and differ from a request with the adapter off. TwoDiffusionTestCasefields express these checks for any case:reference_qualityandrun_lora_effect_check.perf_baselines/h100.json. Its first CI run fails and prints the block to add (the usual two-step seeding).This PR and "[Fix] diffusion: Keep the linear-GELU verdict cache out of torch.compile traces" both edit
can_use_linear_gelu. They merge cleanly in either order. The adapter check sits above that PR's compile branch, so it also applies while tracing; keep that order if either one is rebased.Accuracy Tests
LTX-2 two-stage on 2xH200, against a stage-2-only exact reference: stage 1 runs at
losslessand stage 2 is forced toexact. Stage-1 latents are bitwise identical across arms.quality="exact"output is bitwise unchanged.New server case on H200, per key frame. The case passes at mean_abs_diff ≤ 3.0 and PSNR ≥ 30 dB against
quality="exact", and mean_abs_diff ≥ 12.0 against LoRA off.quality="exact"The case takes 79–88 s on H200 (also re-run on this branch merged with the compile-guard PR: same numbers, pass).
Unit tests:
test_quality_fusion_lora.py5/5 pass on GPU. On main,test_linear_gelu_static_guard_reads_the_wrapped_layerfails: the wrapper hidesgather_outputon a TP2 layer.Speed Tests and Profiling
Only requests with an active dynamic LoRA change path: their GELU sites run unfused. LTX-2 two-stage e2e on 2xH200 went from 7.34 s to 7.51 s. Requests without a LoRA, or with a merged one, keep the fused epilogue.
Checklist
🤖 Generated with Claude Code
CI States
Latest PR Test (Base): ✅ Run #39910057241
Latest PR Test (Extra): ❌ Run #39910057315
Latest PR Test (AMD ROCm 10): ❌ Run #39910057320