| name | vllm-compile |
| description | Expert guidance for vLLM's custom compiler - focusing on @support_torch_compile decorator, vllmBackend/Inductor Passes, PiecewiseBackend, and CudaGraphWrapper. Use when debugging torch.compile issues within vllm, implementing fusion passes, configuring graph splitting, investigating guard dropping, or working with CUDA graph capture in vLLM. |
vLLM torch.compile Expert
Expert guidance for understanding and working with vLLM's custom compilation system, focusing on the four core stages:
- @support_torch_compile Decorator - Entry point and dynamic shapes specification
- vllmBackend & Inductor Passes - Custom fusion passes and LLM optimizations
- PiecewiseBackend - Graph splitting and piecewise compilation
- CudaGraphWrapper - CUDA graph capture and replay
Quick start
If you're new to vLLM compilation, start with QUICK-REFERENCE.md for common commands and debugging.
For a full pipeline walkthrough, see COMPILATION-PIPELINE.md.
For architecture details, see ARCHITECTURE.md.
When to use this skill
Use when:
- Implementing or modifying
@support_torch_compile decorated methods
- Adding new fusion passes to vllmBackend
- Debugging graph splitting in PiecewiseBackend
- Configuring CUDA graph capture
- Understanding compilation performance bottlenecks
- Investigating guard dropping behavior
- Working with torch.compile in vLLM models
Pipeline Overview
┌──────────────────────────────────────────────────┐
│ Stage 1: @support_torch_compile Decorator │
│ - Marks methods for compilation │
│ - Specifies dynamic dimensions │
│ - Creates compilation wrapper │
└─────────────────┬────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────┐
│ Stage 2: vllmBackend & Inductor Passes │
│ - Custom LLM fusion passes │
│ - Pattern matching and replacement │
│ - Graph optimization │
└─────────────────┬────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────┐
│ Stage 3: PiecewiseBackend │
│ - Split at attention operations │
│ - Separate compilable from eager subgraphs │
│ - Manage piecewise execution │
└─────────────────┬────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────┐
│ Stage 4: CudaGraphWrapper │
│ - Capture CUDA graphs per subgraph │
│ - Manage static buffers │
│ - Replay at inference time │
└──────────────────────────────────────────────────┘
Stage 1: @support_torch_compile Decorator
File: vllm/compilation/decorators.py
The decorator is the entry point - it marks methods for compilation and specifies dynamic dimensions.
Usage
from vllm.compilation.decorators import support_torch_compile
class LlamaAttention(nn.Module):
@support_torch_compile(
dynamic_arg_dims={
"x": 0,
"positions": 0,
"kv_cache": (0, 2),
}
)
def forward(self, x, positions, kv_cache, attn_metadata):
q = self.q_proj(x)
k, v = self.k_proj(x), self.v_proj(x)
return output
What It Does
- Wraps the method in a compilation-aware wrapper
- Specifies dynamic shapes to Dynamo (which dims can change)
- Enables guard dropping (unsafe but effective for LLM inference)
- Triggers compilation on first call
Key Files
vllm/compilation/decorators.py - Decorator implementation
vllm/compilation/wrapper.py - TorchCompileWithNoGuardsWrapper
Dynamic Dimensions Explained
dynamic_arg_dims={
"x": 0,
"positions": 0,
"kv_cache": (0, 2),
}
Stage 2: vllmBackend & Inductor Passes
Files:
vllm/compilation/backends.py
vllm/compilation/passes/
vllmBackend applies custom LLM-specific fusion passes before Inductor compilation.
Pass Architecture
class VllmInductorPass:
"""Base class for all vLLM fusion passes"""
def pattern(self) -> PatternMatcher:
"""Define pattern to match in FX graph"""
raise NotImplementedError
def replacement(self, match: Match) -> Node:
"""Generate optimized replacement"""
raise NotImplementedError
def apply(self, gm: GraphModule) -> bool:
"""Apply pass to graph, return True if changed"""
matches = self.pattern().find_matches(gm.graph)
for match in matches:
replacement = self.replacement(match)
gm.graph.replace_subgraph(match, replacement)
return len(matches) > 0
Example Pass: RoPE + KV Cache Fusion
class RoPEKVCacheFusionPass(VllmInductorPass):
def pattern(self):
"""Match: RoPE → KV cache update sequence"""
return PatternMatcher([
Match("rope_embedding", Var("q"), Var("k"), Var("pos")),
Match("kv_cache_update", Var("k_rope"), Var("v"), Var("cache"))
])
def replacement(self, match):
"""Replace with fused Triton kernel"""
return FusedRoPEKVCache(
match["q"], match["k"], match["v"],
match["pos"], match["cache"]
)
%q_rope, %k_rope = rope_embedding(%q, %k, %positions)
%cache_new = kv_cache_update(%k_rope, %v, %kv_cache)
%q_rope, %cache_new = fused_rope_kv_cache(%q, %k, %v, %positions, %cache)
All vLLM Fusion Passes
- AllReduceRMSNormFusion - Overlap TP communication with normalization
- RoPEKVCacheFusion - Fuse positional encoding with cache update
- SiLUMulQuantFusion - Fuse activation + multiply + quantization
- CollectiveFusion - Pipeline communication primitives
- MultiHeadProjectionFusion - Fuse Q/K/V projections
- ... (10+ total)
Stage 3: PiecewiseBackend
File: vllm/compilation/piecewise_backend.py
PiecewiseBackend splits the graph at attention operations to enable hybrid compilation.
Why Split?
Problem: Attention is complex and dynamic
- KV cache management
- Flash attention variants
- Variable sequence lengths
- PagedAttention
Solution: Leave attention as custom op (eager), compile everything else
How It Works
class PiecewiseBackend:
def __init__(self, splitting_ops=["vllm::unified_attention_with_output"]):
self.splitting_ops = splitting_ops
def __call__(self, gm: GraphModule) -> CompiledModule:
split_nodes = [
node for node in gm.graph.nodes
if node.target in self.splitting_ops
]
subgraphs = self._split_graph(gm, split_nodes)
compiled_subgraphs = []
for sg in subgraphs:
if self._is_attention(sg):
compiled_subgraphs.append(sg)
else:
compiled_subgraphs.append(
self._compile_subgraph(sg)
)
return PiecewiseCompiledModule(compiled_subgraphs)
Example Split
Original Graph:
┌──────────────────────────────────────────────────────────┐
│ [Q/K/V Proj] → [RoPE] → [Attn] → [O Proj] → [MLP] │
└──────────────────────────────────────────────────────────┘
After Splitting:
┌────────────────────┐ ┌────────┐ ┌──────────────┐
│ Subgraph 0 │ │ Subgr1 │ │ Subgraph 2 │
│ [Q/K/V] → [RoPE] │ → │ [Attn] │ → │ [O] → [MLP] │
│ (Compiled) │ │ (Eager)│ │ (Compiled) │
└────────────────────┘ └────────┘ └──────────────┘
Configuration
from vllm.config.compilation import CompilationConfig
config = CompilationConfig(
splitting_ops=[
"vllm::unified_attention_with_output",
],
backend="inductor",
)
Stage 4: CudaGraphWrapper
File: vllm/compilation/cuda_graph.py
CudaGraphWrapper captures CUDA graphs per compiled subgraph for maximum performance.
Piecewise CUDA Graphs
Unlike standard CUDA graphs (entire model), vLLM captures one graph per compiled subgraph.
class CudaGraphWrapper:
def __init__(self, compiled_subgraphs, capture_sizes):
self.subgraphs = compiled_subgraphs
self.cuda_graphs = {}
for batch_size in capture_sizes:
self.cuda_graphs[batch_size] = self._capture(batch_size)
def _capture(self, batch_size):
"""Capture CUDA graph for given batch size"""
inputs = self._create_dummy_inputs(batch_size)
for _ in range(3):
for sg in self.subgraphs:
if sg.is_compiled:
sg(*inputs)
torch.cuda.synchronize()
graph = torch.cuda.CUDAGraph()
with torch.cuda.graph(graph):
for sg in self.subgraphs:
if sg.is_compiled:
sg(*inputs)
return graph
def replay(self, inputs, batch_size):
"""Replay captured CUDA graph"""
graph = self.cuda_graphs.get(batch_size)
if graph:
self._copy_to_static(inputs)
graph.replay()
return self._copy_from_static()
else:
return self._run_eager(inputs)
Capture Sizes
capture_sizes = [1, 2, 4, 8, 16, 24, 32, 48, 64, 96, 128, 256, 512]
batch_size = 10
closest = min(capture_sizes, key=lambda x: abs(x - batch_size))
Benefits
| Aspect | Full CUDA Graph | Piecewise CUDA Graph |
|---|
| Memory | High (entire model) | Low (per subgraph) |
| Flexibility | None (all or nothing) | High (eager attention) |
| Overhead | ~1 μs | ~1-2 μs per subgraph |
| Capture time | Long | Short |
Entry Points (Actual Implementation)
Entry Point Flow
@support_torch_compile(...) [decorators.py]
↓
TorchCompileWithNoGuardsWrapper [wrapper.py]
↓
torch.compile(..., backend="vllm")
↓
CompilerManager [backends.py]
↓
InductorStandaloneAdaptor/InductorAdaptor [compiler_interface.py]
↓
torch._inductor.standalone_compile() [PyTorch]
↓ (via post_grad_custom_post_pass hook)
PostGradPassManager [passes/pass_manager.py]
↓
VllmInductorPass (fusion passes) [passes/fusion/*.py]
↓
PiecewiseBackend (graph splitting) [piecewise_backend.py]
↓
CUDAGraph capture [piecewise_backend.py]
Key Entry Points by File
Stage 1: Decorator → Wrapper
@support_torch_compile(dynamic_arg_dims={"x": 0})
def forward(self, x): ...
class TorchCompileWithNoGuardsWrapper:
def __call__(self, *args):
return torch.compile(self.fn, backend="vllm")(args)
Stage 2a: CompilerManager → InductorAdaptor
class CompilerManager:
def __init__(self, config):
self.compiler = make_compiler(config)
def compile(self, graph, inputs, compile_range):
return self.compiler.compile(graph, inputs, ...)
class InductorStandaloneAdaptor(CompilerInterface):
def compile(self, graph, example_inputs, ...):
from torch._inductor import standalone_compile
compiled = standalone_compile(graph, example_inputs, ...)
return compiled, handle
Stage 2b: PassManager (via Inductor Hook)
self.inductor_config = {
"post_grad_custom_post_pass": PostGradPassManager(),
...
}
class PostGradPassManager(CustomGraphPass):
def __call__(self, graph: fx.Graph):
for pass_ in self.passes:
pass_(graph)
Stage 3: PiecewiseBackend
class PiecewiseBackend:
def __call__(self, graph: fx.GraphModule):
subgraphs = self._split_at_ops(graph, splitting_ops)
for sg in subgraphs:
if not is_attention(sg):
compiled = self.compiler.compile(sg)
Stage 4: CUDA Graph Wrapper
def capture_cuda_graphs(compiled_subgraphs, capture_sizes):
graphs = {}
for size in capture_sizes:
graph = torch.cuda.CUDAGraph()
with torch.cuda.graph(graph):
for sg in compiled_subgraphs:
sg(dummy_input_size)
graphs[size] = graph
return graphs
End-to-End Flow
Putting all 4 stages together:
@support_torch_compile(dynamic_arg_dims={"x": 0})
def forward(self, x):
q = self.q_proj(x)
attn_out = ops.unified_attention_with_output(...)
out = self.o_proj(attn_out)
return out
model.forward(x)
↓
model.forward(x2)
Key Files by Stage
Stage 1: Decorator
vllm/compilation/decorators.py - @support_torch_compile implementation
vllm/compilation/wrapper.py - TorchCompileWithNoGuardsWrapper
Stage 2: vllmBackend & Passes
vllm/compilation/backends.py - Backend registration
vllm/compilation/passes/vllm_inductor_pass.py - Pass base class
vllm/compilation/passes/fusion/ - All fusion passes:
allreduce_rms_fusion.py
rope_kvcache_fusion.py
silu_mul_quant_fusion.py
collective_fusion.py
- ... (10+ total)
Stage 3: PiecewiseBackend
vllm/compilation/piecewise_backend.py - Graph splitting logic
vllm/compilation/backends.py - Subgraph compilation
Stage 4: CudaGraphWrapper
vllm/compilation/cuda_graph.py - CUDA graph capture/replay
vllm/compilation/piecewise_backend.py - Integration with piecewise
Configuration
from vllm.config.compilation import CompilationConfig
config = CompilationConfig(
backend="inductor",
splitting_ops=["vllm::unified_attention_with_output"],
cudagraph_mode=CUDAGraphMode.PIECEWISE,
cudagraph_capture_sizes=[1, 2, 4, 8, 16, 32, 64, 128, 256],
compile_sizes=[1, 8, 16],
)
Debugging by Stage
Stage 1: Decorator Issues
import inspect
method = model_class.forward
print(hasattr(method, '_vllm_torch_compile_wrapped'))
wrapper = method._vllm_torch_compile_wrapper
print(wrapper.dynamic_arg_dims)
Stage 2: Inductor/Pass Issues
TORCH_LOGS="+inductor,+graph" vllm serve model
VLLM_LOGGING_LEVEL=DEBUG \
VLLM_PATTERN_MATCH_DEBUG=1 \
vllm serve model
ls ~/.cache/vllm/torch_compile_cache/<hash>/rank_0_0/
cat inductor_code.py
Stage 3: Piecewise Backend Issues
VLLM_LOGGING_LEVEL=DEBUG vllm serve model
Stage 4: CUDA Graph Issues
vllm serve model -cc.cudagraph_mode=NONE
vllm serve model -cc.cudagraph_capture_sizes='[1,8,16]'
VLLM_LOGGING_LEVEL=DEBUG vllm serve model
Common CLI Commands
vllm serve model --enforce-eager
vllm serve model -cc.backend=eager
vllm serve model -cc.cudagraph_mode=NONE
vllm serve model -cc.compile_sizes='[1,8,16]'
VLLM_DISABLE_COMPILE_CACHE=1 vllm serve model
VLLM_USE_STANDALONE_COMPILE=1 vllm serve model
vllm serve model -cc.pass_config.enable_all=false
vllm serve model \
-cc.pass_config.fuse_rope_kvcache=true \
-cc.pass_config.fuse_allreduce_rms=true
Performance Metrics
Compilation Time
- Cold start (no cache): 10-30 seconds
- Warm start (cache hit): <1 second
- Per-subgraph compilation: 1-5 seconds
Runtime Performance
- Overall speedup: 10-20% vs eager mode
- Best gains: Multi-GPU (TP) workloads with fusion passes
- CUDA graph overhead: ~1-2 μs per subgraph replay
Memory Usage
- Cache on disk: ~100-500 MB per model
- Additional runtime: ~5-10% for CUDA graphs
- Savings from fusion: 20-30% (eliminates intermediates)
Quick Reference
Most Important Files
| Stage | File | Purpose |
|---|
| 1 | decorators.py | @support_torch_compile decorator |
| 1 | wrapper.py | Guard dropping wrapper |
| 2 | compiler_interface.py | InductorStandaloneAdaptor entry point |
| 2 | passes/pass_manager.py | PostGradPassManager (fusion orchestrator) |
| 2 | passes/fusion/*.py | Individual fusion passes |
| 3 | piecewise_backend.py | Graph splitting logic |
| 4 | piecewise_backend.py | CUDA graph capture/replay |
| - | backends.py | CompilerManager orchestrator |
Key Environment Variables
VLLM_USE_STANDALONE_COMPILE=1
VLLM_DISABLE_COMPILE_CACHE=1
VLLM_LOGGING_LEVEL=DEBUG
VLLM_PATTERN_MATCH_DEBUG=1
TORCH_LOGS="+inductor,+dynamo"
TORCH_TRACE=/tmp/trace
Best practices
- Start with debug logging (
VLLM_LOGGING_LEVEL=DEBUG) when investigating issues
- Use cache invalidation (
VLLM_DISABLE_COMPILE_CACHE=1) to force recompilation when testing
- Test without CUDA graphs first (
-cc.cudagraph_mode=NONE) to isolate compilation issues
- Check the cache directory (
~/.cache/vllm/torch_compile_cache/) for generated kernel code
- Avoid graph breaks (print statements, dynamic control flow) in decorated methods
- Use specific compile sizes (
-cc.compile_sizes) during development to reduce iteration time
- To view compiled artifacts use VLLM_DEBUG_DUMP_PATH=/tmp/baseline/ python /path_to_folder/your_python_file.py
Requirements
- vLLM installed from source
Related documentation