Repository navigation
[diffusion] Name the quality tiers after what they guarantee - #42370
Merged
mickqian merged 17 commits intoOct 7, 2026
Merged
Conversation
`quality` had "lossless" for the bit-exact reference path and "extra-high" for the tier that adds kernel fusions, which reads as if the faster tier gives up quality. It does not: those fusions compute the same function, and their rounding is no further from the exact value than the reference's own. Bit-identical output describes reproducibility -- same seed and same environment giving the same bytes -- and the reference path is itself only one sample of the rounding, which already moves with the cuBLAS version, the GPU model and the parallel layout. The tiers are now named after their guarantee: "exact" (bit-identical in the same environment, still the default), "lossless" (same math, rounding may move) and "high" (may change what is computed). "extra-high" is kept as an alias of "lossless". Note the one name that changes meaning rather than spelling: a request that passed quality="lossless" for bit-identical output has to pass quality="exact" now. Each request-gated fusion declares the lowest tier that may mount it, so a request mounts the fusions of its own tier and of every stricter one, instead of one boolean for "any fusion". Three of the thirteen do not belong where that boolean put them and move to "high": FLUX.2's NVFP4 FC1+SwiGLU+quant quantizes FC2's input before the reference BF16 intermediate exists, Ideogram's BF16-native gate RMSNorm rounds norm statistics the reference keeps in fp32, and SANA-Video's BF16-input linear attention feeds BF16 to a GEMM the reference promotes to FP32. The other ten keep the reference's math and precision and stay in "lossless". No fusion moves into "exact": the default mounts nothing it did not mount before, so its kernels and its speed are unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Each request-gated fusion justified its tier with its own hand-picked atol/rtol, which cannot separate the two cases the tier is about: a reassociated reduction and a kernel that quietly dropped mantissa bits both land "close" to the reference. The template scores every implementation against an fp64 evaluation of the same math and judges the candidate against the path the model runs today, which is the comparison the tier actually claims: no more than 1.5x the reference path's RMS and max error, and no systematic sign in its error, since a biased rounding accumulates over layers and steps where a symmetric one cancels. It also checks that NaN, Inf and non-contiguous inputs behave as before -- a fast path that swallows a NaN is a defect, not a tier -- and exposes a flip rate for the case where a quantizer or a threshold reads the output and one ulp stops being a rounding difference. The static conditions stay a review gate rather than a measurement: an accumulation below fp32, a lowered operand precision or a less stable formulation makes a path approximate by construction, and no tolerance admits it. The end-to-end drift band needs a server run and stays with the model's consistency suite. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
torch.backends.cudnn.allow_tf32 truncates fp32 convolution inputs to 10 mantissa bits and allow_bf16_reduced_precision_reduction lets a bf16 GEMM accumulate split-K partials below fp32. Both change what is computed, so both sit at the "high" tier -- and the runtime never set either, leaving the numerical contract of every tier to whichever PyTorch is installed. The fp32 VAE decoders are the ones that notice. Each worker now sets both from the server args and logs what it runs, so the contract is auditable. They stay process-global because they are: a server mixes tiers across concurrent batches, so they cannot follow a request. The defaults keep PyTorch's own values. Turning TF32 off moves fp32 convolutions off tensor cores, so making the strict setting the default would slow the exact tier down to buy a guarantee the tier does not currently promise; a deployment that wants it passes both flags as false. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The bias example was so one-sided it tripped the magnitude gate first, which proved nothing about the bias check; it now stays inside the magnitude budget so only the bias gate can reject it. The exact-tier test asserted an unmount from a stage that was already exact, where the early return correctly does nothing; it now downgrades from lossless, which is the transition that matters -- an exact request must not keep running the previous batch's fusion. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…kill BitExactFusionGate now says which tier it serves: it backs a path that is bit-exact by construction, so it is the exact tier's mechanism and the first-sight check only catches a dispatch that changed underneath it. A path whose rounding differs uses QualityGatedFusion and the tier declared in _QUALITY_FUSION_HANDLERS instead. The denoise benchmark's quality/BCG matrix measured "lossless, extra-high, high"; it now measures exact, lossless and high under the same names the runtime uses. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
mickqian
requested review from
AgainstEntropy,
BBuf,
HaiShaw,
ping1jing2 and
yichiche
as code owners
October 3, 2026 09:26
mickqian
requested review from
JustinTong0323,
kevin-mii,
niehen6174,
sogalin,
wisclmy0611,
yingluosanqian and
zijiexia
as code owners
October 3, 2026 09:26
The tier each request-gated fusion declares was read off what it does to the numbers. This measures it: the transformations the lossless-tier fusions perform pass the admission gate, and the three this change moves to "high" fail it, on activations carrying the outlier channels that decide whether a reassociated reduction stays within the reference's error. On real FLUX.1-dev MLP activations the groups separate by three to six orders of magnitude -- fused linear+GELU lands at 0.66x the reference path's RMS error (the epilogue rounds once where the reference rounds twice) and the per-token gated residual at 1.01x, while bf16 norm statistics, a BF16-fed attention GEMM and a quantized FC2 input land at 1e4 to 1e6 times it. The verdicts are not sitting near the 1.5x threshold. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Running it against the real fused linear+GELU rejected the kernel on a NaN input, and the kernel was fine: NaN positions matched and the NaN stayed in its own row in both paths. What differed was the magnitude of the finite values, by 3.1e-02 where one bf16 ulp at that scale is 3.8e-01 -- ordinary rounding. The gate compared them with torch.allclose defaults (rtol=1e-5), which no bf16 fast path can meet, so it rejected every path it exists to admit. The gate now asks what it is for: same NaN positions, same infinities with the same signs, same shape, same exception. Magnitude is the operator-level gate's question, asked on real activations rather than on edge-case inputs -- including a non-contiguous one, which is where a stride or aliasing bug surfaces, since reading the wrong elements is a magnitude error and not a structural one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`.flatten()` on a non-contiguous tensor copies, so the stand-in for a stride-ignoring kernel was quietly computing the right answer and the test asserted a rejection that never came. `as_strided` over the same storage with packed strides is what a kernel that takes a data pointer and assumes packed rows really does. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two conflicts in sampling_params.py, both from main adding the x264 preset list and field next to the quality block this branch rewrites; kept both sides. The text merge also left two places using the old tier names that no conflict could have flagged: kernels/ops/diffusion/README.md, which main rewrote in sgl-project#42391 while this branch renamed the tiers elsewhere, and a CLI-parsing test this branch had missed. Both now name the tiers the runtime uses, and the README's rule for a new non-bit-exact kernel points at the tier declaration and the admission template instead of at "extra-high and high". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The default already ran 58 unconditionally-mounted fused kernels; what it withheld were the 13 request-gated ones, which keep the reference math and every operand's precision and only move the rounding. Measured on FLUX.1-dev, that costs 34.5 dB against the exact tier while the same path moves 32.5 dB between one GPU and two -- so the default was paying 4-5% of denoise time for a difference smaller than the one a parallel-layout change already makes, and nobody treats TP2 as a quality setting. "exact" stays one request away for anyone who needs bit-identical output. Flipping it required fixing a silent hole first. A breakable CUDA graph bakes in whichever fusions were mounted at capture, warmup captures at the default level, and the capture signature only covers tensors -- so once the default mounts fusions, an "exact" request would unmount them and then replay the fused graph anyway. The old guard could not catch that: it fired on "something is mounted", which is exactly what an exact request is not. The runner is now keyed by (module, level), so a level that warmup did not capture finds an empty runner and takes the eager fallback the runner already has. That also removes the error the old guard raised, which had made quality + BCG mutually exclusive. Known cost, not fixed here: the 79 server cases that take the default now compare against goldens recorded on the exact path. The image thresholds are PSNR 28 / SSIM 0.95 and the measured drift is 34.5 dB, so they pass, but the regression budget is thinner until the goldens are refreshed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
It builds the stage with __new__ and calls _maybe_get_bcg_runner directly, so the level the key now carries has to be set on it. What it checks is unchanged: two modules, two runners. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The docs still called `exact` the default. They now say `lossless`, note that
a deployment which never set `quality` moves off the bit-exact path, and give
the number behind the choice: 34.5 dB between the tiers against 32.5 dB
between one GPU and two on the same tier.
The earlier mechanical rename also collapsed two tier names into one in five
places -- `--quality {lossless,lossless,high}` in the CLI reference, the same
list in the OpenAI reference and the benchmark skill, and "keep it off for
lossless and mount it at lossless" in qwen_image. Each of those was the old
`lossless` and `extra-high` landing on the same word; they name `exact` and
`lossless` now.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…corded on Making lossless the default put every consistency case on a path its golden does not describe, and CI showed what that costs: wan22_modelopt_nvfp4_t2v came back at SSIM 0.9114 against a 0.92 threshold -- PSNR 32.1 against 24.0 passed comfortably, so the binding metric was the one the earlier estimate in this branch never measured. A golden records one path. These were recorded on the reference path, so the cases name it instead of inheriting whichever level the server defaults to; otherwise the check answers "did the code regress" and "how far is the default from the goldens" at once, and spends its whole budget on the second. A case that names a level keeps it, so the alias coverage on qwen_image_t2i_2_gpus_extra_high is untouched. This is the interim half of the change the flip implies: refreshing the goldens onto the default level is what gives the default its own coverage and removes the pin. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Making lossless the default mounted this fusion on every LTX-2.5 request and every denoising step then raised "scale and shift must be contiguous [B, 1, D] tensors". The fusion was not at fault and neither was the model: the site guarded on dtype and width while the launcher also requires the modulation rows to be contiguous [B, 1, D], and LTX-2.5's are not. The gap opened when sgl-project#42391 moved validation into the launchers and dropped the site-level predicate that had checked the tensors; nothing noticed, because the fusion only mounted above the default level. The launcher raising is right for a caller that has committed. A request-gated caller has not, so it now asks first: can_use_fused_rmsnorm_modulation mirrors the launcher's own checks and returns a bool, and the LTX-2 site takes the reference chain when it says no. The two ERNIE call sites share the narrow guard but wrap the call in the BitExactFusionGate's try/except, so a raise there disables the fast path instead of failing the request; they are left alone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Oct 7, 2026
This was referenced Oct 7, 2026
This was referenced Oct 9, 2026
cursor Bot
pushed a commit
to cipheraxat/sglang
that referenced
this pull request
Oct 9, 2026
…gl-project#42370) Co-authored-by: Mick Qian <mickqian@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This was referenced Oct 10, 2026
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.
Why
qualitynamed its strictest tierlosslessand the tier above itextra-high, which reads as if the faster tier gives up quality. It does not.The fusions that tier mounts compute the same function; their rounding is no
further from the exact value than the reference's own rounding is, and the
reference path is only one sample of that rounding — it already moves with the
cuBLAS version, the GPU model and the parallel layout, so a TP2 run can never
be bit-identical to a single-card HF reference anyway. Bit-identical output
describes reproducibility, which is worth keeping as its own tier, not quality.
What
Tiers are named after what they guarantee, and each level includes the ones
before it:
exactlossless(default)highextra-highis kept as an alias oflossless. Two things change forexisting callers. A request that passed
quality="lossless"forbit-identical output has to pass
quality="exact"now: that name is reused,not just renamed. And
losslessis the default, so a deployment that neverset
qualitymoves off the bit-exact path onto it. In-repo callers areupdated.
The mechanisms now match the tiers
BitExactFusionGatebacks paths that are bit-exact by construction and says so— that is the
exacttier's mechanism, and its first-sight check exists tocatch a dispatch that changed underneath it.
QualityGatedFusionmounts perrequest, and every entry in
_QUALITY_FUSION_HANDLERSnow declares the lowesttier that may mount it, instead of one boolean for "any fusion".
Three of the thirteen do not belong where that boolean put them and move to
high:BF16 intermediate exists.
(
F.rms_norm) keeps in fp32.promotes to FP32.
The other ten keep the reference's math and precision and stay in
lossless.No fusion moves into
exact: the default mounts nothing it did not mountbefore.
One admission template instead of per-case tolerances
test/quality_tier_admission.py. A hand-pickedatolcannot separate the twocases the tier is about — a reassociated reduction and a kernel that dropped
mantissa bits both land "close". The template scores every implementation
against an fp64 evaluation of the same math and judges the candidate against
the path the model runs today: no more than 1.5x its RMS and max error, and no
systematic sign in the error, since a biased rounding accumulates over layers
and steps where a symmetric one cancels. It also pins NaN/Inf/non-contiguous
behavior and exposes a flip rate for the case where a quantizer or threshold
reads the output.
The static conditions stay a review gate: an accumulation below fp32, a lowered
operand precision or a less stable formulation is approximate by construction
and no tolerance admits it. The end-to-end drift band needs a server run and
stays with the model's consistency suite.
The two library defaults underneath the tiers
torch.backends.cudnn.allow_tf32andallow_bf16_reduced_precision_reductionare approximate by the definitionsabove, and the runtime never set either, leaving every tier's contract to
whichever PyTorch is installed. Each worker now sets both from
--allow-cudnn-tf32/--allow-bf16-reduced-precision-reductionand logs whatit runs. They stay process-global because they are: a server mixes tiers across
concurrent batches.
The defaults keep PyTorch's own values. Turning TF32 off moves fp32
convolutions — the VAE decoders that default to
vae_precision="fp32"— offtensor cores, so making the strict setting the default would slow
exactdownto buy a guarantee it does not currently promise. A deployment that wants it
passes both as false.
Validation (1xB200, FLUX.1-dev 1024x1024, 20 steps, seed 42)
The default tier has to be no slower than it was, so it is checked for being
the same computation, interleaved main/PR/main/PR on one box:
origin/main, default (lossless)f1e7e33bd68a9558…exact)f1e7e33bd68a9558…origin/main, default, repeatf1e7e33bd68a9558…f1e7e33bd68a9558…Bit-identical output across the four arms: the default runs the same kernels,
and its denoise stage lands at 1.80–1.84 s either way. The tier system itself
works on the same build —
--quality losslessmounts the fused linear+GELU andmoves to 66 ms/step with a different md5 at 34.5 dB against the exact output
(rounding-level, as the tier claims), and
--quality extra-highreproduces thelosslessoutput bit for bit, so the alias resolves.Why
losslessis the defaultThe default already ran 58 unconditionally-mounted fused kernels; what it
withheld were the 13 request-gated ones. Measured on FLUX.1-dev, 1xB200,
interleaved, same seed:
exact)fb4c78b8…lossless)f3bf24b8…--quality exactfb4c78b8…— bit-identical to the old defaultfb4c78b8…f3bf24b8…--quality exactfb4c78b8…The default gets faster on both paths, and
exactstill reproduces the olddefault bit for bit, so the strict contract costs a request parameter rather
than being lost.
Flipping it required closing a silent hole first. A breakable CUDA graph
bakes in whichever fusions were mounted when it was captured, warmup captures
at the default level, and the capture signature covers only tensors. Once the
default mounts fusions, an
exactrequest would unmount them and then replaythe fused graph anyway. The old guard could not catch that: it fired on
"something is mounted", which is exactly what an
exactrequest is not. TheBCG runner is now keyed by
(module, level), so a level warmup never capturedfinds an empty runner and takes the eager fallback the runner already has. The
last row above is that fix under test — without it the md5 there would read
f3bf24b8…. Removing the old guard also ends the rule thatqualityand BCGwere mutually exclusive.
Known cost, deliberately not addressed here: the 79 server cases that take
the default now compare against goldens recorded on the exact path. The image
thresholds are PSNR 28 / SSIM 0.95 and the measured drift is 34.5 dB, so they
pass, but the regression budget is thinner until the goldens are refreshed.
The tier assignments, measured
Each fusion's tier above was read off what it does to the numbers. The
admission gate then scored those transformations on real FLUX.1-dev MLP
activations captured at the fused GELU site (4096x3072 bf16, max/p99 = 8.1,
so the outlier channels are present), against an fp64 evaluation of the same
math and relative to the path the model runs today:
losslesslosslesslosslesshighhighhighThe GEMM epilogue comes out more accurate than the reference chain, which is
what the tier predicts: it rounds once where the reference rounds the GEMM
output and then the GELU output. The gated residual is the case that could not
be settled by reading the code -- it moves one rounding earlier, so the
taxonomy only admits it if it measures that way, and at 1.01x it does.
The two groups separate by three to six orders of magnitude, so none of these
verdicts sits near the 1.5x threshold.
test/unit/test_quality_tier_boundary.pykeeps them honest in CI on DiT-shaped activations.
The end-to-end gate: the tier moves the pixels less than the GPU count does
The operator-level numbers above say the rounding is small. Whether that is
small enough is the tier's end-to-end question, and the standard for it is
the drift the reference path already shows between two legal environments.
Measured on the same box, FLUX.1-dev, same seed:
exactTP1 vsexactTP1 againexactTP1 vsexactTP2exactvslossless, TP1exactvslossless, TP2exactTP1 vslosslessTP2Higher PSNR is a smaller difference, so switching tier (34.5 / 37.3 dB) changes
the image less than moving the same tier from one GPU to two (32.5 dB) — and
nobody treats TP2 as a quality setting. The control reruns bit-identically, so
this is not run-to-run noise. The tier's drift is inside the band, which is
what the end-to-end gate asks.
Running the gates against the real kernels found one bug — in the gate
assert_special_values_matchrejected the real fused linear+GELU on a NaNinput. The kernel was fine: NaN positions matched and the NaN stayed inside
its own row in both paths. The finite values differed by 3.1e-02 where one
bf16 ulp at that magnitude is 3.8e-01 — ordinary rounding — and the gate was
comparing them with
torch.allclosedefaults (rtol=1e-5), which no bf16 fastpath can meet. It now checks what it is for (NaN positions, signed infinities,
shape, exception type) and leaves magnitude to the operator-level gate. Both
real kernels pass all ten special-value cases after the fix.
Unit tests: the quality, fusion-gating, precision, admission, OpenAI-API and
partitioning tests pass (163), plus the new coverage for the taxonomy, the
alias, per-tier mounting, the admission template and the numerics policy.
🤖 Generated with Claude Code
CI States
Latest PR Test (Base): ❌ Run #37465408464
Latest PR Test (Extra): ❌ Run #37465408108
Latest PR Test (AMD ROCm 10): ❌ Run #37465408423