perf(diffusion): fused qknorm rope Flux 2.0 - #239
Merged
Conversation
xuanzic
force-pushed
the
feat/diffusion-fused-qknorm-rope
branch
from
June 29, 2026 18:00
269d591 to
9a1941b
Compare
Replaces FLUX.2's per-block chain of 4 per-head RMSNorms + 4 RoPEs (double-stream) and 2+2 (single-stream) with one fused TVM-FFI kernel call per tensor.
xuanzic
force-pushed
the
feat/diffusion-fused-qknorm-rope
branch
from
June 30, 2026 23:00
9a1941b to
c76689c
Compare
xuanzic
force-pushed
the
feat/diffusion-fused-qknorm-rope
branch
from
July 1, 2026 00:19
c76689c to
e46b6bc
Compare
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.
Fused DiT QK-Norm + RoPE kernel for FLUX.2
Replaces FLUX.2's per-block chain of 4 per-head RMSNorms + 4 RoPEs (double-stream) and
2+2 (single-stream) with one fused TVM-FFI kernel call per tensor. Per-warp algorithm
adapted from TensorRT-LLM PR-13052
(
fusedDiTQKNormRopeKernel.cu), simplified to a per-tensor launcher thatslots into our existing separate-Q/K/V graph without restructuring. Templated on
dtype — both fp16 (FLUX.2's default) and bf16 are exported.
Numbers — FLUX.2-dev 1024×1024, 28 steps, GB300
Both fused paths beat torch.compile. Before this PR, TRT BF16 was 1.08× slower;
after, 1.01× faster. Engine plan shrinks 21% because the unfused chain materialized
intermediate weight tensors the fused path collapses.
Kernel correctness (vs PyTorch reference, realistic RoPE cos/sin):
bf16max_abs 1.56e-2 (limit 3e-2) ✓ ·fp16max_abs 1.95e-3 (limit 5e-3) ✓Kernel microbench (vs PyTorch eager bf16): 15.8× – 25.7× across FLUX.2 shapes.
What each commit does
f076f00build — pip-installsapache-tvm-ffi==0.1.12in the Dockerfile; CMakeuses
importlib.util.find_spec()to discovertvm_ffi+tensorrt_libspaths inthe venv. Previously these libs were silently undiscoverable in the stock dev image.
2049c14plugin bf16 — addsdtype=2 → kBF16in five sites oftvm_ffi_kernel_plugin.{cpp,h}(output spec, format combination, DLTensor fill).1193351Mistral bf16 (1/3) —work_np_dtypefor bf16 now usesml_dtypes.bfloat16(wasnp.float16, causing strongly-typed TRT type mismatches).71dc58ffused kernel — newdit_rms_norm_rope.cu(bf16-only at landing time)flux2_dit_builder.pyinjection at 6 paired call sites behindTRTMC_FUSED_DIT_KERNELS=1env flag. Default path byte-identical to before. Buildgated by
option(TRTMC_BUILD_DIFFUSION_KERNELS OFF). sm_90 + sm_100 + sm_103.together complete the picture; bundled because the bf16 fixes only become testable
end-to-end once you can also build a fused engine in fp16 (FLUX.2's default):
DTypeTraits<T>(specializations for__nv_bfloat16and__half); two TVM-FFI exportstrtmc.dit_rms_norm_rope_{bf16,fp16}; builder picks the suffix by_CAST_DTYPE.graph_ops.add_constant: detectml_dtypes.bfloat16(a V16 numpy extension dtypethat TRT bindings can't auto-convert) and route to the explicit
trt.Weights(trt.bfloat16, ptr, count)constructor. That constructor does notcopy, so we also keep the numpy buffer alive via a module-level
_bf16_weight_keepalivedict keyed byid(network). Without the keepalive, GCof the local array caused exit-139 segfaults during TRT graph construction.
mistral_encoder_builder.py:np.finforejectsml_dtypes.bfloat16(not inexact); switch toml_dtypes.finfofor the attention-mask min value.How to verify
Expected fused step time: fp16 0.184 s/step, bf16 0.183 s/step on GB300.
Not in this PR — follow-ups
fuse_scale_shift(AdaLN, 81 calls/step) — biggest remaining win (~25 ms/step).group_norm_silu(VAE) — runs once per gen, small.ctypes.CDLL; the C++ trtmc binary needs a static-init wrapper to use the fused path.tests/test_dit_rms_norm_rope.py(mirrortest_flashinfer_plugin_e2e.py).Risk / backwards compat
TRTMC_BUILD_DIFFUSION_KERNELSdefaultsOFF; without
TRTMC_FUSED_DIT_KERNELS=1, the legacy chain runs._bf16_weight_keepalivedict lives for process lifetime, bounded by enginebuilds. Acceptable; long-lived processes that build many bf16 engines could
explicitly clear entries on completion.
Architecture note
TvmFfiKernelPluginlooks up kernels by global name at engine deserialize time. Anytoolchain that emits a
tvm_ffi.Functionslots in identically — raw CUDA today,CuTeDSL/Triton/FlashInfer JIT later via the same
add_tvm_ffi_kernel(...)Pythonhelper. This PR is the first end-to-end shake-out of that plug for diffusion graphs.