Skip to content

Commit 52cec35

Browse files
committed
feat(sglang): upgrade to v0.5.9 on plain torch base for SLIME support
SLIME's post-training pipeline (Megatron + SGLang) requires SGLang v0.5.9. This upgrades from v0.4.x and rebases the image onto a plain torch base, dropping the torch-extras layer (DeepSpeed, Apex, xFormers) that neither SGLang nor SLIME actually uses. FlashInfer moves from JIT to v0.6.3 AOT compilation via TVM. sgl-kernel is now built with scikit-build-core and enables SM100A (Blackwell) and FP4 support. vLLM and Triton are removed from this image since they are served by the dedicated vllm-tensorizer image.
1 parent 47d448a commit 52cec35

9 files changed

Lines changed: 1242 additions & 129 deletions

.github/workflows/sglang.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ jobs:
2424
with:
2525
image-name: sglang
2626
folder: sglang
27-
tag-suffix: ${{ inputs.tag || '386fabe-nccl-cuda12.8.0-ubuntu22.04-nccl2.25.1-1-torch2.6.0-vision0.21.0-audio2.6.0-abi1' }}
27+
tag-suffix: ${{ inputs.tag || 'nccl-cuda12.9.1-ubuntu22.04-nccl2.29.2-1-torch2.10.0-vision0.25.0-audio2.10.0-abi1' }}
2828
build-args: |
29-
BASE_IMAGE=${{ inputs.base-image || 'ghcr.io/coreweave/ml-containers/torch-extras:es-actions-386fabe-nccl-cuda12.8.0-ubuntu22.04-nccl2.25.1-1-torch2.6.0-vision0.21.0-audio2.6.0-abi1'}}
30-
${{ inputs.base-image && 'BASE_IMAGE=' }}${{ inputs.base-image}}
29+
BASE_IMAGE=${{ inputs.base-image || 'ghcr.io/coreweave/ml-containers/torch:17ad6db-nccl-cuda12.9.1-ubuntu22.04-nccl2.29.2-1-torch2.10.0-vision0.25.0-audio2.10.0-abi1'}}
30+
${{ inputs.builder-image && format('BUILDER_IMAGE={0}', inputs.builder-image) || '' }}
31+
Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
1+
# CUDA Architectures and FlashInfer AOT Compilation
2+
3+
## The Problem
4+
5+
Our sglang build has a line that strips `12.0+PTX` from FlashInfer's
6+
architecture list, with a comment claiming "nvcc in CUDA 12.x doesn't support
7+
compute_120." Eta0's review pointed out this is wrong — CUDA 12.8+ fully
8+
supports compute_120 — and suggested we also enable FlashInfer's communication
9+
module by installing nvshmem.
10+
11+
To understand why this matters, we need to understand how CUDA code targets
12+
different GPUs, what FlashInfer's AOT build does, and why multi-GPU
13+
communication kernels are valuable.
14+
15+
## GPU Compute Capabilities
16+
17+
Every NVIDIA GPU has a **compute capability** (CC) — a version number that
18+
identifies which instructions it supports. Think of it like a CPU's instruction
19+
set (x86, ARM, etc.) but for GPUs.
20+
21+
| Compute Capability | Architecture | Example GPUs |
22+
|---|---|---|
23+
| 8.0 | Ampere | A100 |
24+
| 8.6 | Ampere | A40, RTX 3090 |
25+
| 8.9 | Ada Lovelace | L40, RTX 4090 |
26+
| 9.0 | Hopper | H100, H200 |
27+
| 10.0 | Blackwell | B200, GB200 |
28+
| 12.0 | Blackwell | DGX Spark, RTX PRO 6000 |
29+
30+
Notice that 10.0 and 12.0 are both "Blackwell" but are **different
31+
sub-architectures** with different instruction sets. B200 (the big data center
32+
GPU) is CC 10.0. DGX Spark and RTX PRO 6000 are CC 12.0. They share the
33+
Blackwell marketing name but have different silicon and different capabilities.
34+
35+
### The `a` Suffix
36+
37+
Some capabilities have an `a` variant: `9.0a`, `10.0a`, `12.0a`. The `a` means
38+
"architecture-specific features" — extra instructions that only exist on that
39+
exact GPU generation and have no equivalent in the portable PTX instruction set.
40+
41+
For example, `sm_100a` enables MXFP8 (e2m1) matrix operations that only
42+
Blackwell B200 hardware can execute. Code compiled for `sm_100` (without `a`)
43+
would miss these instructions. For high-performance inference, the `a` suffix
44+
matters.
45+
46+
## SASS vs PTX: Two Ways to Compile CUDA
47+
48+
When you compile CUDA code, nvcc can produce two kinds of output:
49+
50+
**SASS (Shader Assembly)** — native machine code for a specific GPU. Fast,
51+
optimal, but only runs on that exact compute capability.
52+
53+
**PTX (Parallel Thread Execution)** — a portable intermediate representation.
54+
Runs on any GPU with equal or higher compute capability, but must be
55+
JIT-compiled to SASS at runtime, which is slower.
56+
57+
```
58+
Source Code (.cu)
59+
|
60+
v
61+
nvcc compiler
62+
|
63+
+---> SASS for sm_90a (native, fast, H100 only)
64+
+---> SASS for sm_100a (native, fast, B200 only)
65+
+---> PTX for compute_90 (portable, JIT at runtime)
66+
```
67+
68+
### The +PTX Notation
69+
70+
In PyTorch's `TORCH_CUDA_ARCH_LIST`, `12.0+PTX` means "compile native SASS for
71+
sm_120 AND include PTX for compute_120 as a fallback." This is PyTorch-specific
72+
syntax. The `+PTX` part tells PyTorch's build system to generate an extra
73+
`-gencode` flag:
74+
75+
```
76+
12.0+PTX --> -gencode arch=compute_120,code=sm_120
77+
-gencode arch=compute_120,code=compute_120 (PTX fallback)
78+
```
79+
80+
Without `+PTX`, you only get the native SASS:
81+
82+
```
83+
12.0 --> -gencode arch=compute_120,code=sm_120
84+
```
85+
86+
This notation is a **PyTorch convention**, not a CUDA standard. Other build
87+
systems (like FlashInfer's) don't understand it.
88+
89+
## How FlashInfer's AOT Build Works
90+
91+
FlashInfer uses **Ahead-of-Time (AOT) compilation** to pre-compile attention
92+
kernels for each target GPU architecture. This avoids the startup latency of
93+
JIT compilation (which can take minutes on first use).
94+
95+
The AOT build is invoked via:
96+
97+
```bash
98+
FLASHINFER_CUDA_ARCH_LIST="9.0a 10.0a" python3 -m flashinfer.aot
99+
```
100+
101+
### FlashInfer's Arch Parser
102+
103+
FlashInfer parses `FLASHINFER_CUDA_ARCH_LIST` with this logic:
104+
105+
```python
106+
for arch in os.environ["FLASHINFER_CUDA_ARCH_LIST"].split(" "):
107+
major, minor = arch.split(".")
108+
major = int(major)
109+
self.TARGET_CUDA_ARCHS.add((int(major), str(minor)))
110+
```
111+
112+
It splits on spaces, then splits each value on `.` to get a `(major, minor)`
113+
tuple. The minor part is kept as a string to support suffixes like `"0a"`.
114+
115+
Then it generates nvcc flags:
116+
117+
```python
118+
f"-gencode=arch=compute_{major}{minor},code=sm_{major}{minor}"
119+
```
120+
121+
For `"9.0a"`, this produces:
122+
```
123+
-gencode=arch=compute_90a,code=sm_90a
124+
```
125+
126+
### Why 12.0+PTX Breaks
127+
128+
If you pass `"12.0+PTX"` to FlashInfer:
129+
130+
1. `arch.split(".")` gives `["12", "0+PTX"]`
131+
2. `minor = "0+PTX"`
132+
3. The gencode flag becomes: `-gencode=arch=compute_120+PTX,code=sm_120+PTX`
133+
4. nvcc doesn't understand `compute_120+PTX` and crashes
134+
135+
This is why our build.bash filters it out. **The filtering itself is correct
136+
and necessary.** The comment explaining it was wrong — it blamed nvcc for not
137+
supporting compute_120, when the real issue is FlashInfer's parser not
138+
understanding the `+PTX` suffix.
139+
140+
### The Right Fix: Convert, Don't Strip
141+
142+
Instead of stripping `12.0+PTX` entirely (losing all sm_120 support), we should
143+
**convert** it to `12.0a` like the vllm-tensorizer image does:
144+
145+
```bash
146+
# Current (strips 12.0+PTX, no sm_120 kernels):
147+
FLASHINFER_ARCH_LIST="$(echo "${TORCH_CUDA_ARCH_LIST}" | sed 's/12\.0+PTX//')"
148+
149+
# Better (converts to 12.0a, native sm_120a kernels):
150+
FLASHINFER_ARCH_LIST="$(echo "${TORCH_CUDA_ARCH_LIST}" \
151+
| sed -E 's@\b(9|10|12)\.0\b@\1\.0a@g; s@\+PTX\b@@g')"
152+
```
153+
154+
The vllm-tensorizer approach handles all architectures uniformly:
155+
1. Convert bare `.0` to `.0a` (adds architecture-specific features)
156+
2. Strip `+PTX` globally (FlashInfer only generates native SASS, no PTX)
157+
158+
## What sm_120 Kernels We're Missing
159+
160+
Without sm_120a in FlashInfer's arch list, these kernels are not compiled:
161+
162+
| Kernel | Purpose | Why it Matters |
163+
|---|---|---|
164+
| `mla_sm120.cu` | Multi-Latent Attention using CGA and TMA | Dedicated attention kernel for CC 12.0 GPUs |
165+
| `fp4_quantization_sm120` | FP4 quantization | Native FP4 support on Blackwell |
166+
| `cutlass_fused_moe_sm120` | Fused MoE dispatch | Efficient expert routing |
167+
| `gemm_sm120_cutlass_fp4` | FP4 GEMM | Low-precision matrix multiply |
168+
| `xqa_mla` | XQA MLA backend | sm_120-exclusive MLA implementation |
169+
170+
The MLA kernel (`mla_sm120.cu`) uses **CGA (Cooperative Group Arrays)** and
171+
**TMA (Tensor Memory Accelerator)** — hardware features exclusive to sm_120a
172+
that have no PTX equivalent. Without native compilation, these kernels simply
173+
don't exist. There's no PTX fallback — FlashInfer would fall back to JIT
174+
compilation from source at runtime (minutes of startup) or use a generic
175+
implementation that doesn't leverage the hardware.
176+
177+
### sgl-kernel Already Has sm_120a
178+
179+
Importantly, **sgl-kernel handles this correctly already.** It ignores
180+
`TORCH_CUDA_ARCH_LIST` entirely and manages its own gencode flags through CMake
181+
options. When CUDA >= 12.8, sgl-kernel automatically adds:
182+
183+
```
184+
-gencode=arch=compute_120a,code=sm_120a
185+
```
186+
187+
So the gap is FlashInfer-specific.
188+
189+
## The Communication Module and nvshmem
190+
191+
### What FlashInfer's Comm Module Provides
192+
193+
FlashInfer includes a communication module (`flashinfer.comm`) that provides
194+
optimized multi-GPU collective operations:
195+
196+
| Feature | What It Does |
197+
|---|---|
198+
| TRT-LLM allreduce fusion | Fused allreduce + residual add + RMSNorm in one kernel |
199+
| vLLM custom allreduce | CUDA IPC-based allreduce for vLLM |
200+
| NVSHMEM operations | GPU-initiated allreduce, alltoall, barrier |
201+
| MoE alltoall | Expert-parallel dispatch/combine for MoE models |
202+
| Multi-Node NVLink | MNNVL allreduce for multi-node setups |
203+
204+
The key innovation is **operation fusion**. Normally, tensor-parallel inference
205+
does three separate kernel launches per transformer layer:
206+
207+
```
208+
1. allreduce (collect results from all GPUs)
209+
2. residual add (add skip connection)
210+
3. RMSNorm (normalize)
211+
```
212+
213+
FlashInfer's comm module fuses these into a single kernel:
214+
215+
```
216+
1. allreduce + residual + RMSNorm (one kernel, one memory pass)
217+
```
218+
219+
This reduces kernel launch overhead and memory bandwidth usage significantly on
220+
H100+ GPUs.
221+
222+
### sglang Uses This
223+
224+
sglang v0.5.9 imports `flashinfer.comm` in
225+
`python/sglang/srt/layers/flashinfer_comm_fusion.py`:
226+
227+
```python
228+
import flashinfer.comm as comm
229+
230+
# Creates CUDA IPC workspace for fused allreduce
231+
workspace = comm.trtllm_create_ipc_workspace_for_all_reduce_fusion()
232+
233+
# Performs fused allreduce + residual + RMSNorm
234+
comm.trtllm_allreduce_fusion(
235+
pattern=AllReduceFusionPattern.kARResidualRMSNorm,
236+
...
237+
)
238+
```
239+
240+
This is auto-enabled for DeepSeek, GLM, Qwen, and other MoE models when
241+
running with tensor parallelism (TP > 1) on SM90+ GPUs.
242+
243+
**Our `--add-comm false` silently disables this optimization.** sglang falls
244+
back to standard NCCL allreduce (three separate kernels instead of one fused
245+
kernel).
246+
247+
### Why We Had `--add-comm false`
248+
249+
The comm module requires `nvidia-nvshmem-cu12` as a build dependency. When we
250+
wrote the build script, the base image didn't have nvshmem and we didn't want
251+
to add it. So we disabled the entire comm module to avoid a build failure.
252+
253+
The vllm-tensorizer image already installs nvshmem in its builder stage:
254+
255+
```dockerfile
256+
python3 -m pip install --no-cache-dir \
257+
"nvidia-nvshmem-cu${CUDA_VERSION%%.*}"
258+
```
259+
260+
This adds ~145MB to the builder stage download but **does not affect the final
261+
image size** because it's a multi-stage build — the builder stage is discarded
262+
after wheel compilation.
263+
264+
### What SLIME Needs
265+
266+
SLIME itself doesn't directly call `flashinfer.comm`. It inherits whatever
267+
sglang provides. SLIME's RL coordination uses `torch.distributed` (NCCL) for
268+
weight sync and process group management. However, when SLIME drives sglang for
269+
rollout inference with TP > 1, the fused allreduce would automatically
270+
activate — so enabling it benefits SLIME's inference performance.
271+
272+
SLIME does use nvshmem separately for **DeepEP** (expert-parallel MoE
273+
dispatch), but that's a different nvshmem dependency path, not related to
274+
FlashInfer's comm module.
275+
276+
## Summary of Required Changes
277+
278+
1. **Fix the comment** — CUDA 12.8+ supports compute_120. The issue is
279+
FlashInfer's parser, not nvcc.
280+
281+
2. **Convert `12.0+PTX` to `12.0a`** instead of stripping it — gives FlashInfer
282+
native sm_120a Blackwell kernels, matching vllm-tensorizer's approach.
283+
284+
3. **Install `nvidia-nvshmem-cu12`** in the builder stage — build-time only
285+
dependency, ~145MB, discarded in final image.
286+
287+
4. **Remove `--add-comm false`** — enables the fused allreduce+RMSNorm
288+
optimization for TP deployments on H100+ GPUs.
289+
290+
## Key Concepts to Remember
291+
292+
- **Compute capability** identifies a GPU's instruction set (8.0 = A100, 10.0 =
293+
B200, 12.0 = DGX Spark)
294+
- **SASS** = native GPU machine code, **PTX** = portable intermediate code
295+
- **`+PTX`** is PyTorch syntax for "include PTX fallback" — not understood by
296+
all build systems
297+
- **AOT** = pre-compile kernels ahead of time (fast startup, no JIT latency)
298+
- **The `a` suffix** enables architecture-exclusive instructions (no PTX
299+
equivalent)
300+
- **Fused operations** combine multiple kernels into one, reducing memory
301+
bandwidth and launch overhead
302+
- **Multi-stage Docker builds** mean builder dependencies don't bloat the final
303+
image

0 commit comments

Comments
 (0)