Skip to content

[diffusion] Name the quality tiers after what they guarantee - #42370

Merged
mickqian merged 17 commits into
sgl-project:mainfrom
mickqian:mick/quality-tier-exact-lossless
Oct 7, 2026
Merged

mickqian merged 17 commits into
sgl-project:mainfrom
mickqian:mick/quality-tier-exact-lossless

Conversation

@mickqian

@mickqian mickqian commented Oct 3, 2026 •

Copy link
Copy Markdown
Collaborator

Why

quality named its strictest tier lossless and the tier above it
extra-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:

tier guarantee admits
exact bit-identical to the reference path in the same environment scheduling, placement, data movement, reuse of strictly invariant values
lossless (default) the same math and the same operand and accumulator precision; only the place or order of the rounding moves fusions, other kernels, reassociated reductions, higher intermediate precision
high may change what is computed lower precision, sparsity, reuse across steps

extra-high is kept as an alias of lossless. Two things change for
existing callers.
A request that passed quality="lossless" for
bit-identical output has to pass quality="exact" now: that name is reused,
not just renamed. And lossless is the default, so a deployment that never
set quality moves off the bit-exact path onto it. In-repo callers are
updated.

The mechanisms now match the tiers

BitExactFusionGate backs paths that are bit-exact by construction and says so
— that is the exact tier's mechanism, and its first-sight check exists to
catch a dispatch that changed underneath it. QualityGatedFusion mounts per
request, and every entry in _QUALITY_FUSION_HANDLERS now declares the lowest
tier 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:

  • FLUX.2 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
    (F.rms_norm) keeps in fp32.
  • SANA-Video 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.

One admission template instead of per-case tolerances

test/quality_tier_admission.py. A hand-picked atol cannot separate the two
cases 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_tf32 and
allow_bf16_reduced_precision_reduction are approximate by the definitions
above, 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-reduction and logs what
it 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" — off
tensor cores, so making the strict setting the default would slow exact down
to 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:

arm output md5 ms/step
origin/main, default (lossless) f1e7e33bd68a9558… 72
this PR, default (exact) f1e7e33bd68a9558… 69
origin/main, default, repeat f1e7e33bd68a9558… —
this PR, default, repeat f1e7e33bd68a9558… 69

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 lossless mounts the fused linear+GELU and
moves 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-high reproduces the
lossless output bit for bit, so the alias resolves.

Why lossless is the default

The 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:

output md5 steady rate
default before (exact) fb4c78b8… 14.39 it/s
default after (lossless) f3bf24b8… 14.97 it/s
after, with --quality exact fb4c78b8… — bit-identical to the old default 14.38 it/s
BCG, default before fb4c78b8… 18.21 it/s
BCG, default after f3bf24b8… 21.96 it/s
BCG, --quality exact fb4c78b8… 13.85 it/s

The default gets faster on both paths, and exact still reproduces the old
default 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 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
BCG runner is now keyed by (module, level), so a level warmup never captured
finds 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 that quality and BCG
were 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:

classified transformation RMS error vs the reference path's gate
lossless fused linear+GELU (cublasLt epilogue) 0.66x pass
lossless reassociated fp32 reduction (split-K shape) 0.98x pass
lossless per-token gated residual (Helios / LingBot) 1.01x pass
high bf16-accumulated norm statistics (Ideogram) ~3.8e4 x reject
high BF16-input attention GEMM (SANA-Video) ~9.9e3 x reject
high quantized FC2 input (FLUX.2 NVFP4) ~6.9e5 x reject

The 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.py
keeps 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:

comparison PSNR
control: exact TP1 vs exact TP1 again bit-identical
the band: exact TP1 vs exact TP2 32.5 dB
the tier: exact vs lossless, TP1 34.5 dB
the tier: exact vs lossless, TP2 37.3 dB
both at once: exact TP1 vs lossless TP2 33.0 dB

Higher 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_match rejected the real fused linear+GELU on a NaN
input. 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.allclose defaults (rtol=1e-5), which no bf16 fast
path 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

mickqian and others added 5 commits October 3, 2026 15:46
`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 mickqian added run-ci CI: run the baseline test suite on this PR diffusion SGLang Diffusion labels Oct 3, 2026
@github-actions github-actions Bot added documentation Improvements or additions to documentation quant LLM Quantization blackwell SM100/SM120 jit-kernel labels Oct 3, 2026
mickqian and others added 7 commits October 3, 2026 17:30
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>
mickqian and others added 5 commits October 5, 2026 16:47
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

blackwell SM100/SM120 diffusion SGLang Diffusion documentation Improvements or additions to documentation jit-kernel quant LLM Quantization run-ci CI: run the baseline test suite on this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant