| 1 | # Fusion torch.compile passes |
| 2 | |
| 3 | vLLM applies a set of kernel/operator fusions at compile time (via custom [`torch.compile`](torch_compile.md) Inductor passes) |
| 4 | to separate optimizations from model definitions and avoid breaking layer abstractions in model code. |
| 5 | These fusions are controlled by fields in [`PassConfig`][vllm.config.compilation.PassConfig] and are automatically enabled |
| 6 | at appropriate [optimization levels](optimization_levels.md). |
| 7 | |
| 8 | ## Quick Reference |
| 9 | |
| 10 | The table below maps each fusion to its controlling flag/config knob, the |
| 11 | operations it fuses, what level enables it by default, and an indicative speedup. |
| 12 | The Fullgraph column indicates whether the fusion requires the entire model graph to be |
| 13 | visible (either via Inductor partition or `splitting_ops=[]`), |
| 14 | and the last column indicates whether the fusion activates for all `num_tokens` |
| 15 | or just on the low or high end. |
| 16 | |
| 17 | !!! info |
| 18 | Speedup depends heavily on the exact model, batch size, and hardware. |
| 19 | If tuning performance by hand, always benchmark your exact use-case with and without the fusion to verify the impact. |
| 20 | |
| 21 | | Fusion | `PassConfig` flag | Fused operations | Default at | E2E Speedup | Fullgraph | `num_tokens` | |
| 22 | | ------------------------------------------------------------------------------ | ---------------------------- | ---------------------------------------------- | ------------------------------ | ------------------ | --------- | ------------ | |
| 23 | | [AllReduce + RMSNorm](#allreduce--rmsnorm-fuse_allreduce_rms) | `fuse_allreduce_rms` | All-reduce → RMSNorm (+residual_add) (→ quant) | O2 (Hopper/Blackwell + TP > 1) | 5-20% | No | Low | |
| 24 | | [Attention + Quant](#attention--quantization-fuse_attn_quant) | `fuse_attn_quant` | Attention output → FP8/NVFP4 quant | Off by default | 3-7% | Yes | Always | |
| 25 | | [MLA Attention + Quant](#attention--quantization-fuse_attn_quant) | `fuse_attn_quant` | MLA Attention output → FP8/NVFP4 quant | Off by default | TBD | Yes | Always | |
| 26 | | [RoPE + KV-Cache Update](#rope--kv-cache-update-fuse_rope_kvcache) | `fuse_rope_kvcache` | Rotary embedding → KV cache write | O2 (ROCm/AITER only) | 2-4% | No | Low | |
| 27 | | [QK Norm + RoPE](#qk-norm--rope-enable_qk_norm_rope_fusion) | `enable_qk_norm_rope_fusion` | Q/K RMSNorm → rotary embedding | Off by default | 2-3% | No | Low | |
| 28 | | [Sequence Parallelism](#sequence-parallelism-enable_sp) | `enable_sp` | AllReduce → ReduceScatter + AllGather | Off by default | Prereq for AsyncTP | Yes | High | |
| 29 | | [AsyncTP GEMM + collective](#asynctp-gemm--collective-overlap-fuse_gemm_comms) | `fuse_gemm_comms` | GEMM → reduce-scatter / all-gather → GEMM | Off by default | 7-10% | Yes | High | |
| 30 | | [RMSNorm + Quant](#rmsnorm--quantization-fuse_norm_quant) | `fuse_norm_quant` | RMSNorm (+residual add) → FP8/FP4 quant | O1 (conditional) | 1-4% | No | Always | |
| 31 | | [SiLU+Mul + Quant](#silumul--quantization-fuse_act_quant) | `fuse_act_quant` | SiLU+Mul activation → FP8/FP4 quant | O1 (conditional) | 1-4% | No | Always | |
| 32 | | [RMSNorm + Padding](#rmsnorm--padding-fuse_act_padding) | `fuse_act_padding` | Residual add + RMSNorm → padding | O1 (ROCm/AITER only) | TBD | No | Always | |
| 33 | | [MLA Dual RMSNorm](#mla-dual-rmsnorm-fuse_mla_dual_rms_norm) | `fuse_mla_dual_rms_norm` | Paired Q + KV RMSNorm (+ FP8 quant) → 1 kernel | O1 (ROCm/AITER only) | 1-2% | No | Always | |
| 34 | |
| 35 | ## Support Matrix |
| 36 | |
| 37 | The table below lists the quantization schemes supported by each fusion on each platform. |
| 38 | **—** means the fusion is not available on that platform. The latest and in-progress work is available in the tracking issue: |
| 39 | [#36066](https://github.com/vllm-project/vllm/issues/36066) |
| 40 | |
| 41 | | Fusion | SM100 (Blackwell) | SM90 (Hopper) | SM89 (Ada) | SM80 (Ampere) | ROCm | |
| 42 | | ---------------------------- | ---------------------------------------- | ---------------------------------------- | ---------------------------------------- | ------------- | ---------------------------------------- | |
| 43 | | `fuse_allreduce_rms` | FP16/BF16, FP8 static, NVFP4 | FP16/BF16, FP8 static | — | — | — | |
| 44 | | `fuse_attn_quant`\* | FP8 static\*, NVFP4\* | FP8 static\* | FP8 static\* | — | FP8 static\* | |
| 45 | | `fuse_attn_quant` (MLA)\* | FP8 static\*, FP8 per-group\*, NVFP4\* | FP8 static\*, FP8 per-group\* | FP8 static\*, FP8 per-group\* | — | FP8 static\* (untested) | |
| 46 | | `fuse_rope_kvcache` | — | — | — | — | FP16/BF16 | |
| 47 | | `enable_qk_norm_rope_fusion` | FP16/BF16 | FP16/BF16 | FP16/BF16† | FP16/BF16† | — | |
| 48 | | `enable_sp` | FP16/BF16, FP8 static† | FP16/BF16, FP8 static | FP16/BF16† | FP16/BF16† | — | |
| 49 | | `fuse_gemm_comms` | FP16/BF16, FP8 static† | FP16/BF16, FP8 static | FP16/BF16† | FP16/BF16† | — | |
| 50 | | `fuse_norm_quant` | FP8 static, FP8 per-token, FP8 per-group | FP8 static, FP8 per-token, FP8 per-group | FP8 static, FP8 per-token, FP8 per-group | — | FP8 static, FP8 per-token, FP8 per-group | |
| 51 | | `fuse_act_quant` | FP8 static, NVFP4 | FP8 static, FP8 per-group (128/64) | FP8 static, FP8 per-group (128/64) | — | FP8 per-group | |
| 52 | | `fuse_act_padding` | — | — | — | — | FP16/BF16 | |
| 53 | | `fuse_mla_dual_rms_norm` | — | — | — | — | BF16 | |
| 54 | |
| 55 | \* `fuse_attn_quant` support depends on the attention backend in use; not all backends support |
| 56 | fused quantization output. See the [`fuse_attn_quant` section](#attention--quantization-fuse_attn_quant) |
| 57 | for per-backend details. |
| 58 | |
| 59 | † `enable_sp` and `fuse_gemm_comms` are only autoconfigured for SM90 today; |
| 60 | other architectures support requires setting `PassConfig.sp_min_token_num` explicitly. |
| 61 | SM100 support also requires setting `VLLM_DISABLED_KERNELS=FlashInferFP8ScaledMMLinearKernel`. |
| 62 | |
| 63 | ## Enabling / Disabling Fusions |
| 64 | |
| 65 | Fusions are exposed through `PassConfig`, which is nested inside `CompilationConfig`: |
| 66 | |
| 67 | ```python |
| 68 | from vllm import LLM |
| 69 | from vllm.config import CompilationConfig, PassConfig |
| 70 | |
| 71 | llm = LLM( |
| 72 | model="...", |
| 73 | optimization_level=2, # Default optimization level |
| 74 | compilation_config=CompilationConfig( |
| 75 | pass_config=PassConfig( |
| 76 | fuse_norm_quant=True, |
| 77 | fuse_act_quant=True, |
| 78 | fuse_allreduce_rms=False, # disable a specific fusion |
| 79 | ) |
| 80 | ), |
| 81 | ) |
| 82 | ``` |
| 83 | |
| 84 | Fusions can also be enabled using command-line flags with any `vllm ...` command: |
| 85 | |
| 86 | ```bash |
| 87 | # Enable O2 defaults, but turn off allreduce fusion |
| 88 | vllm serve meta-llama/Llama-3.1-8B-Instruct -O2 -cc.pass_config.fuse_allreduce_rms=False |
| 89 | |
| 90 | # The above is equivalent to the more verbose: |
| 91 | vllm serve meta-llama/Llama-3.1-8B-Instruct -O2 --compilation-config '{"pass_config": {"fuse_allreduce_rms": false}}' |
| 92 | |
| 93 | # Same syntax in other commands, e.g. vllm bench: |
| 94 | vllm bench latency --model=meta-llama/Llama-3.1-8B-Instruct -O2 -cc.pass_config.fuse_allreduce_rms=False |
| 95 | ``` |
| 96 | |
| 97 | Fields set explicitly by the user always take precedence over optimization-level defaults. |
| 98 | |
| 99 | ## Fusion Details |
| 100 | |
| 101 | ### AllReduce + RMSNorm (`fuse_allreduce_rms`) |
| 102 | |
| 103 | !!! warning |
| 104 | TP+DP and TP+PP combinations are currently broken |
| 105 | ([#34458](https://github.com/vllm-project/vllm/issues/34458) and |
| 106 | [#35426](https://github.com/vllm-project/vllm/issues/35426)). |
| 107 | Only supported on NVIDIA Hopper (SM90) and Blackwell (SM100) with FlashInfer installed. |
| 108 | |
| 109 | **What it fuses.** Fuses the tensor-parallel all-reduce collective with the subsequent residual add, |
| 110 | RMSNorm, and optionally a quantization step into a single FlashInfer / TRT-LLM communication kernel. |
| 111 | This fusion is only profitable for small `num_tokens`, |
| 112 | so the fusion is only performed in the lower compiled range. |
| 113 | |
| 114 | Patterns covered: |
| 115 | |
| 116 | - `AllReduce → RMSNorm(+residual_add)`: CUDA sm90+ with FlashInfer |
| 117 | - `AllReduce → RMSNorm(+residual_add) → FP8 static quant`: CUDA sm90+ with FlashInfer |
| 118 | - `AllReduce → RMSNorm(+residual_add) → NVFP4 dynamic quant`: CUDA sm100+ with FlashInfer |
| 119 | |
| 120 | The maximum tensor size below which the fused kernel is used is hardware-dependent (64 MB for TP=2 |
| 121 | on SM90/SM100) and configurable via `PassConfig.fi_allreduce_fusion_max_size_mb`. |
| 122 | |
| 123 | **Code locations.** |
| 124 | |
| 125 | - Pass: [`vllm/compilation/passes/fusion/allreduce_rms_fusion.py`](https://github.com/vllm-project/vllm/blob/main/vllm/compilation/passes/fusion/allreduce_rms_fusion.py) |
| 126 | - FlashInfer all-reduce: [`vllm/distributed/device_communicators/flashinfer_all_reduce.py`](https://github.com/vllm-project/vllm/blob/main/vllm/distributed/device_communicators/flashinfer_all_reduce.py) |
| 127 | - Benchmark: [`benchmarks/kernels/benchmark_fused_collective.py`](https://github.com/vllm-project/vllm/blob/main/benchmarks/kernels/benchmark_fused_collective.py) |
| 128 | |
| 129 | ### Attention + Quantization (`fuse_attn_quant`) |
| 130 | |
| 131 | !!! info |
| 132 | `fuse_attn_quant` is currently not enabled at any optimization level by default and must be set |
| 133 | explicitly. It requires the full model graph to be visible (Inductor partition or `splitting_ops=[]`). |
| 134 | |
| 135 | **What it fuses.** Fuses the attention output quantization directly after the attention computation, |
| 136 | eliminating a full-precision memory round-trip of the attention output. This fusion supports both |
| 137 | standard `Attention` and `MLAAttention` (used by DeepSeek-V2/V3/R1 models). Patterns covered: |
| 138 | |
| 139 | `Attention → FP8 static quant`: |
| 140 | |
| 141 | - `TRITON_ATTN`: CUDA, ROCm |
| 142 | - `FLASHINFER`: CUDA sm100+ with FlashInfer installed |
| 143 | - `ROCM_ATTN`: ROCm |
| 144 | - `ROCM_AITER_UNIFIED_ATTN`: ROCm with AITER |
| 145 | |
| 146 | `Attention → NVFP4 dynamic quant`: |
| 147 | |
| 148 | - `FLASHINFER`: CUDA sm100+ with FlashInfer installed |
| 149 | |
| 150 | `MLAAttention → FP8 static, FP8 per-group, NVFP4 dynamic quant` |
| 151 | |
| 152 | The MLA fusion operates at the graph level on the `unified_mla_attention_with_output` op and works |
| 153 | with all MLA decode and prefill backend combinations. Unlike standard `Attention` backends (where |
| 154 | the kernel writes FP8 output directly), no MLA prefill or decode backend currently supports direct |
| 155 | FP8/FP4 output. The fusion writes to an intermediate buffer and quantizes in a separate step, so |
| 156 | there is no memory round-trip elimination yet. |
| 157 | |
| 158 | !!! info |
| 159 | The MLA attention fusion is not expected to yield a measurable speedup yet. |
| 160 | This will improve once MLA prefill/decode kernels support direct FP8/FP4 output. |
| 161 | |
| 162 | Other attention backends do not support fused output quantization yet. |
| 163 | |
| 164 | **Code locations.** |
| 165 | |
| 166 | - Pass (Attention): [`vllm/compilation/passes/fusion/attn_quant_fusion.py`](https://github.com/vllm-project/vllm/blob/main/vllm/compilation/passes/fusion/attn_quant_fusion.py) |
| 167 | - Pass (MLAAttention): [`vllm/compilation/passes/fusion/mla_attn_quant_fusion.py`](https://github.com/vllm-project/vllm/blob/main/vllm/compilation/passes/fusion/mla_attn_quant_fusion.py) |
| 168 | - Attention backends: [`vllm/v1/attention/backends/`](https://github.com/vllm-project/vllm/blob/main/vllm/v1/attention/backends/) |
| 169 | |
| 170 | ### RoPE + KV-Cache Update (`fuse_rope_kvcache`) |
| 171 | |
| 172 | !!! info |
| 173 | ROCm/AITER-only. Not available on NVIDIA CUDA or CPU. The fusion is only enabled for |
| 174 | `num_tokens ≤ 256` by default due to AITER fused kernel performance issues. |
| 175 | This threshold is configurable via `PassConfig.rope_kvcache_fusion_max_token_num`. |
| 176 | |
| 177 | **What it fuses.** Fuses the rotary positional embedding kernel with the KV-cache scatter/write into |
| 178 | a single kernel, avoiding separate reads and writes of the key and value tensors. |
| 179 | |
| 180 | Requires: AMD ROCm with AITER enabled, the `rotary_embedding` custom op active (automatic), |
| 181 | and the `kv_cache` update op visible in the graph: either by using Inductor graph partition |
| 182 | or removed from `splitting_ops`. |
| 183 | If these conditions are set, the fusion is enabled automatically for optimization level O1 and above. |
| 184 | |
| 185 | **Code locations.** |
| 186 | |
| 187 | - Pass: [`vllm/compilation/passes/fusion/rope_kvcache_fusion.py`](https://github.com/vllm-project/vllm/blob/main/vllm/compilation/passes/fusion/rope_kvcache_fusion.py) |
| 188 | |
| 189 | ### Sequence Parallelism (`enable_sp`) |
| 190 | |
| 191 | **What it fuses.** Replaces all-reduce collectives with reduce-scatter + local RMSNorm + all-gather, |
| 192 | splitting the sequence dimension across TP ranks. This restructures the graph so the subsequent AsyncTP |
| 193 | pass can fuse the reduce-scatter / all-gather with the surrounding GEMMs. |
| 194 | |
| 195 | Sequence Parallelism itself does not directly improve performance; it is a prerequisite for the |
| 196 | AsyncTP pass (`fuse_gemm_comms`). SP is only applied above a minimum token threshold that is |
| 197 | autoconfigured based on device capability and model `hidden_size`. Currently only active on |
| 198 | H100/SM90 for models with `hidden_size >= 8192`. The threshold is configurable via |
| 199 | `PassConfig.sp_min_token_num`. |
| 200 | |
| 201 | The general transformation: |
| 202 | |
| 203 | ```text |
| 204 | Input → AllReduce → RMSNorm → Output |
| 205 | becomes: |
| 206 | Input → ReduceScatter → local RMSNorm → AllGather → Output |
| 207 | ``` |
| 208 | |
| 209 | Patterns covered: |
| 210 | |
| 211 | - First block: `AllReduce → RMSNorm` → `ReduceScatter → RMSNorm → AllGather` |
| 212 | - Middle blocks: `AllReduce → fused_add_RMSNorm` → `ReduceScatter → fused_add_RMSNorm → AllGather` |
| 213 | - Both with optional `→ FP8 static quant` suffix |
| 214 | |
| 215 | Requires: `use_inductor_graph_partition=True` **or** piecewise compilation with static sizes |
| 216 | divisible by `tensor_parallel_size`. |
| 217 | |
| 218 | Supported hardware: Only tested on NVIDIA CUDA, possibly works on ROCm. FP8 all-gather requires sm90+. |
| 219 | |
| 220 | **Code locations.** |
| 221 | |
| 222 | - Pass: [`vllm/compilation/passes/fusion/sequence_parallelism.py`](https://github.com/vllm-project/vllm/blob/main/vllm/compilation/passes/fusion/sequence_parallelism.py) |
| 223 | |
| 224 | ### AsyncTP GEMM + Collective Overlap (`fuse_gemm_comms`) |
| 225 | |
| 226 | !!! info |
| 227 | Requires `enable_sp=True` (enabled automatically). This pass is a no-op if Sequence Parallelism has not been applied. |
| 228 | |
| 229 | **What it fuses.** After Sequence Parallelism transforms the graph, fuses GEMM kernels with the |
| 230 | surrounding reduce-scatter (output projection) and all-gather (input projection) using |
| 231 | `torch.ops.symm_mem` symmetric-memory primitives, overlapping communication and computation. |
| 232 | This overlap is only profitable for large `num_tokens`, so the fusion (and preceding SP) |
| 233 | is only performed in the higher compiled range above `PassConfig.sp_min_token_num`. |
| 234 | |
| 235 | Patterns covered: |
| 236 | |
| 237 | - `GEMM → reduce-scatter` → `fused_matmul_reduce_scatter` |
| 238 | - `all-gather → GEMM` → `all_gather_matmul` |
| 239 | - FP8 scaled variants of both patterns |
| 240 | |
| 241 | Supported hardware: NVIDIA CUDA with symmetric-memory (`torch.distributed._symmetric_memory`) support. |
| 242 | |
| 243 | On B200, pattern-matching fp8 FlashInfer scaled MM is not supported, so it must be disabled |
| 244 | ([#27893](https://github.com/vllm-project/vllm/issues/27893)) |
| 245 | |
| 246 | ```shell |
| 247 | VLLM_DISABLED_KERNELS=FlashInferFP8ScaledMMLinearKernel ... |
| 248 | ``` |
| 249 | |
| 250 | **Code locations.** |
| 251 | |
| 252 | - Pass: [`vllm/compilation/passes/fusion/collective_fusion.py`](https://github.com/vllm-project/vllm/blob/main/vllm/compilation/passes/fusion/collective_fusion.py) |
| 253 | - Sequence parallelism pass: [`vllm/compilation/passes/fusion/sequence_parallelism.py`](https://github.com/vllm-project/vllm/blob/main/vllm/compilation/passes/fusion/sequence_parallelism.py) |
| 254 | |
| 255 | ### QK Norm + RoPE (`enable_qk_norm_rope_fusion`) |
| 256 | |
| 257 | !!! info |
| 258 | Only applicable to models that apply per-head RMSNorm to Q and K before rotary positional |
| 259 | embedding (e.g. Qwen). Not enabled by default at any optimization level due to perf issues on H100: |
| 260 | [#34391](https://github.com/vllm-project/vllm/issues/34391) |
| 261 | |
| 262 | **What it fuses.** Fuses the sequence: split QKV → reshape → Q/K RMSNorm → reshape → rotary |
| 263 | embedding into a single `fused_qk_norm_rope` CUDA kernel. |
| 264 | |
| 265 | ```text |
| 266 | # Unfused: |
| 267 | q, k, v = split(qkv) |
| 268 | q_norm = rms_norm(q.view(heads)) |
| 269 | k_norm = rms_norm(k.view(kv_heads)) |
| 270 | q_rope, k_rope = rotary_embedding(q_norm, k_norm, ...) |
| 271 | |
| 272 | # Fused: |
| 273 | fused_qk_norm_rope(qkv, ...) |
| 274 | ``` |
| 275 | |
| 276 | Supported hardware: CUDA (sm80+) only, tested only on sm90 and sm100. |
| 277 | |
| 278 | **Code locations.** |
| 279 | |
| 280 | - Pass: [`vllm/compilation/passes/fusion/qk_norm_rope_fusion.py`](https://github.com/vllm-project/vllm/blob/main/vllm/compilation/passes/fusion/qk_norm_rope_fusion.py) |
| 281 | - CUDA kernel: [`csrc/ops.h`](https://github.com/vllm-project/vllm/blob/main/csrc/ops.h) (`fused_qk_norm_rope`) |
| 282 | |
| 283 | ### RMSNorm + Quantization (`fuse_norm_quant`) |
| 284 | |
| 285 | !!! warning |
| 286 | On NVIDIA, Inductor actually generates a faster fused kernel than our custom CUDA kernel. |
| 287 | Hence, this fusion is only enabled when either `rms_norm` or `quant_fp8` is using a custom kernel. |
| 288 | |
| 289 | **What it fuses.** Combines the custom `rms_norm` / `fused_add_rms_norm` |
| 290 | operations with subsequent quantization into a single fused kernel, |
| 291 | eliminating an intermediate read/write of the full-precision activation tensor. |
| 292 | Two variants are fused: |
| 293 | |
| 294 | - *Plain RMSNorm + quant*: `rms_norm(x) → quant_fp8(y)` |
| 295 | - *Fused-add RMSNorm + quant*: `fused_add_rms_norm(x, residual) → quant_fp8(y)` — also updates the residual in-place. |
| 296 | |
| 297 | Note that AITER fusions are currently in a separate pass in `vllm.compilation.passes.fusion.rocm_aiter_fusion`. |
| 298 | |
| 299 | Supported quantization scheme/hardware combinations: |
| 300 | |
| 301 | - FP8 static per-tensor: CUDA & HIP kernel |
| 302 | - FP8 dynamic per-token: CUDA & HIP kernel, AITER |
| 303 | - FP8 dynamic per-token-group (128/64): CUDA & HIP kernel, AITER |
| 304 | |
| 305 | **Code locations.** |
| 306 | |
| 307 | - Pass: [`vllm/compilation/passes/fusion/rms_quant_fusion.py`](https://github.com/vllm-project/vllm/blob/main/vllm/compilation/passes/fusion/rms_quant_fusion.py) |
| 308 | - ROCm AITER pass: [`vllm/compilation/passes/fusion/rocm_aiter_fusion.py`](https://github.com/vllm-project/vllm/blob/main/vllm/compilation/passes/fusion/rocm_aiter_fusion.py) |
| 309 | - CUDA/HIP kernels: [`csrc/libtorch_stable/layernorm_quant_kernels.cu`](https://github.com/vllm-project/vllm/blob/main/csrc/libtorch_stable/layernorm_quant_kernels.cu) |
| 310 | |
| 311 | ### SiLU+Mul + Quantization (`fuse_act_quant`) |
| 312 | |
| 313 | !!! warning |
| 314 | Same as `fuse_norm_quant`: on NVIDIA, Inductor generates a faster fused kernel than our custom ops. |
| 315 | This fusion is only enabled when either `silu_and_mul` or `quant_fp8` are using a custom kernel, |
| 316 | or for NVFP4-quantized models (where FP4 quant is always a custom op). |
| 317 | |
| 318 | **What it fuses.** Fuses the `silu_and_mul` gate-up projection activation with subsequent quantization into a single kernel, |
| 319 | avoiding materialization of the full-precision post-activation tensor. |
| 320 | |
| 321 | Note that AITER fusions are in a separate pass in `vllm.compilation.passes.fusion.rocm_aiter_fusion`. |
| 322 | |
| 323 | Supported quantization scheme/hardware combinations: |
| 324 | |
| 325 | - FP8 static per-tensor: CUDA & HIP kernel |
| 326 | - FP8 dynamic per-group (128/64): CUDA kernel (sm89+, not active when DeepGemm is used on sm100+) |
| 327 | - NVFP4 dynamic: CUDA sm100+ only with FlashInfer |
| 328 | - FP8 per-token-group (128): ROCm AITER only |
| 329 | |
| 330 | **Code locations.** |
| 331 | |
| 332 | - Pass: [`vllm/compilation/passes/fusion/act_quant_fusion.py`](https://github.com/vllm-project/vllm/blob/main/vllm/compilation/passes/fusion/act_quant_fusion.py) |
| 333 | - ROCm AITER pass: [`vllm/compilation/passes/fusion/rocm_aiter_fusion.py`](https://github.com/vllm-project/vllm/blob/main/vllm/compilation/passes/fusion/rocm_aiter_fusion.py) |
| 334 | - CUDA/HIP kernels: [`csrc/quantization/`](https://github.com/vllm-project/vllm/blob/main/csrc/quantization/) |
| 335 | - Fused SiLU+Mul+BlockQuant kernel: [`csrc/libtorch_stable/quantization/fused_kernels/fused_silu_mul_block_quant.cu`](https://github.com/vllm-project/vllm/blob/main/csrc/libtorch_stable/quantization/fused_kernels/fused_silu_mul_block_quant.cu) |
| 336 | |
| 337 | ### RMSNorm + Padding (`fuse_act_padding`) |
| 338 | |
| 339 | !!! info |
| 340 | ROCm/AITER-only. Targeted at GPT-OSS models. |
| 341 | |
| 342 | **What it fuses.** Fuses a residual add + RMSNorm with a subsequent padding operation that pads |
| 343 | the hidden dimension to a multiple required by downstream AITER Triton GEMM kernels. |
| 344 | |
| 345 | Requires: AMD ROCm with AITER RMSNorm enabled. Enabled by default in optimization level O1 and above |
| 346 | when the hidden size is 2880 and AITER Triton GEMMs *not* enabled. |
| 347 | |
| 348 | **Code locations.** |
| 349 | |
| 350 | - Pass: [`vllm/compilation/passes/fusion/rocm_aiter_fusion.py`](https://github.com/vllm-project/vllm/blob/main/vllm/compilation/passes/fusion/rocm_aiter_fusion.py) (`RocmAiterTritonAddRMSNormPadFusionPass`) |
| 351 | |
| 352 | ### MLA Dual RMSNorm (`fuse_mla_dual_rms_norm`) |
| 353 | |
| 354 | !!! info |
| 355 | ROCm/AITER-only. Targeted at DeepSeek-V3 / Kimi-K2 MLA attention. |
| 356 | |
| 357 | !!! note |
| 358 | When the native implementation of `rms_norm` is used (the default on CUDA and |
| 359 | ROCm for now), Inductor's built-in fusion already handles merging these norms |
| 360 | automatically. This explicit pass targets the case where AITER's custom |
| 361 | `rms_norm` op is active, which Inductor cannot fuse on its own. |
| 362 | |
| 363 | **What it fuses.** Fuses the paired `q_a_layernorm` and `kv_a_layernorm` RMS norm |
| 364 | operations in MLA attention into a single `fused_qk_rmsnorm` HIP kernel call via AITER, |
| 365 | reducing kernel launch overhead from 2 launches to 1 per MLA layer. |
| 366 | |
| 367 | ```text |
| 368 | # Unfused: |
| 369 | q_c, kv_lora = split(projected, [q_dim, kv_dim]) |
| 370 | kv_c, k_pe = split(kv_lora, [kv_c_dim, k_pe_dim]) |
| 371 | q_c = rms_norm(q_c, q_weight, eps) |
| 372 | kv_c = rms_norm(kv_c, kv_weight, eps) |
| 373 | |
| 374 | # Fused: |
| 375 | q_c, kv_lora = split(projected, [q_dim, kv_dim]) |
| 376 | kv_c, k_pe = split(kv_lora, [kv_c_dim, k_pe_dim]) |
| 377 | q_normed, kv_normed = fused_mla_dual_rms_norm( |
| 378 | q_c, q_weight, kv_c, kv_weight, eps1, eps2) |
| 379 | ``` |
| 380 | |
| 381 | Requires: AMD ROCm with AITER enabled. Enabled by default at optimization level O1 and above |
| 382 | when AITER is available. |
| 383 | |
| 384 | **FP8 attention variant (per-token quant).** With a per-token FP8 `q_b_proj`, |
| 385 | only the *q* latent is FP8-quantized while *kv* stays bf16. |
| 386 | `RocmAiterRMSNormQuantFusionPass` first folds the q side into |
| 387 | `rocm_aiter_rmsnorm_fused_dynamic_quant`, leaving kv a plain |
| 388 | `rms_norm` — breaking the symmetric pattern above. The same pass then matches |
| 389 | this asymmetric pair and lowers it to `fused_mla_dual_rms_norm_per_token_quant`. |
| 390 | |
| 391 | ```text |
| 392 | # Unfused (q norm+quant fused; kv still plain rms_norm): |
| 393 | q_c, kv_lora = split(projected, [q_dim, kv_dim]) |
| 394 | kv_c, k_pe = split(kv_lora, [kv_c_dim, k_pe_dim]) |
| 395 | q_fp8, q_scale = rocm_aiter_rmsnorm_fused_dynamic_quant(q_c, q_weight, eps, fp8) |
| 396 | kv_normed = rms_norm(kv_c, kv_weight, eps) # bf16 |
| 397 | |
| 398 | # Fused: |
| 399 | q_c, kv_lora = split(projected, [q_dim, kv_dim]) |
| 400 | kv_c, k_pe = split(kv_lora, [kv_c_dim, k_pe_dim]) |
| 401 | q_fp8, q_scale, kv_normed = fused_mla_dual_rms_norm_per_token_quant( |
| 402 | q_c, q_weight, kv_c, kv_weight, eps1, eps2) |
| 403 | ``` |
| 404 | |
| 405 | **Code locations.** |
| 406 | |
| 407 | - Pass: [`vllm/compilation/passes/fusion/rocm_aiter_fusion.py`](https://github.com/vllm-project/vllm/blob/main/vllm/compilation/passes/fusion/rocm_aiter_fusion.py) (`MLADualRMSNormFusionPass`, `MLADualRMSPerTokenQuantPattern`) |
| 408 | - Custom op: [`vllm/_aiter_ops.py`](https://github.com/vllm-project/vllm/blob/main/vllm/_aiter_ops.py) (`fused_mla_dual_rms_norm`, `fused_mla_dual_rms_norm_per_token_quant`) |
| 409 | - AITER kernels: [`fused_qk_rmsnorm`](https://github.com/ROCm/aiter/pull/2442), `fused_qk_rmsnorm_per_token_quant` |
| 410 | |
| 411 | ## See Also |
| 412 | |
| 413 | - [Optimization Levels](optimization_levels.md) — high-level presets that set |
| 414 | fusion defaults. |
| 415 | - [torch.compile in vLLM](torch_compile.md) — how the Inductor pass pipeline |
| 416 | works. |
| 417 | - [Attention Backends](attention_backends.md) — attention-specific kernel |
| 418 | selection. |