피스와이즈 CUDA 그래프
피스와이즈 CUDA 그래프 (Piecewise CUDA Graph)
Piecewise CUDA Graph (PCG)는 모델의 계산 그래프를 "분할 지점"(예: MoE 디스패치 연산)에서 여러 조각(대략 레이어당 하나)으로 나눠, 반복마다 토큰 수가 변하는 extend/prefill에서도 커널 런치 오버헤드를 제거하면서 동적 형태(dynamic shapes)를 지원합니다. 기본적으로 활성화됩니다.
출처: 문서
본문
동기 (Motivation)
표준 CUDA 그래프는 전체 모델 forward pass를 단일 그래프로 캡처합니다. 이는 decode(고정 배치 크기)에는 잘 맞지만, 토큰 수가 반복마다 변하는 extend/prefill에는 맞지 않아요.
Piecewise CUDA Graph (PCG)는 모델의 계산 그래프를 "분할 지점"(split points, 예: MoE dispatch ops)에서 여러 조각(대략 레이어당 하나)으로 나눠 해결합니다. 각 조각은 사전 정의된 토큰 길이 집합에 대해 별도 CUDA 그래프로 캡처됩니다. 런타임에 입력은 가장 가까운 캡처 크기로 패딩되고 각 조각이 재생됩니다. 이는 동적 형태를 지원하면서 prefill/extend의 커널 런치 오버헤드를 제거합니다.
PCG는 기본적으로 활성화됩니다. 끄려면 --cuda-graph-backend-prefill=disabled를 전달하세요.
사용법 (Usage)
PCG는 지원되는 구성에서 기본 활성화됩니다. 추가 플래그가 필요 없어요:
python3 -m sglang.launch_server \
--model-path meta-llama/Llama-3.1-8B-Instruct
PCG 비활성화 (Disable PCG)
python3 -m sglang.launch_server \
--model-path meta-llama/Llama-3.1-8B-Instruct \
--cuda-graph-backend-prefill=disabled
커스텀 캡처 크기 (Custom capture sizes)
python3 -m sglang.launch_server \
--model-path meta-llama/Llama-3.1-8B-Instruct \
--cuda-graph-max-bs-prefill 2048
서버 인자 (Server Args)
| Argument | Default | Description |
|---|---|---|
--cuda-graph-backend-prefill |
None (auto) |
prefill 단계의 백엔드. 선택지: full, breakable, tc_piecewise, disabled. disabled를 전달하면 extend/prefill에서 PCG를 끄고, tc_piecewise는 모든 자동 비활성 검사를 건너뛰고 강제로 켭니다(테스트 전용). |
--cuda-graph-max-bs-prefill |
None (auto) |
캡처할 최대 토큰 수. 기본은 chunked_prefill_size(비-MLA) 또는 2048(MLA). |
--cuda-graph-bs-prefill |
None (auto) |
캡처할 토큰 길이의 명시적 목록. 설정하지 않으면 자동 생성. |
--cuda-graph-tc-compiler |
"eager" |
캡처된 서브그래프의 컴파일러 백엔드. 선택지: eager, inductor. |
버그 리포트 (Bug Report)
PCG는 기본 활성화지만 여전히 실험 단계입니다. PCG는 torch.compile로 모델 forward pass를 추적하므로, 대부분의 버그는 torch compile 추적 실패(예: 추적 불가 op, 동적 제어 흐름, 그래프 중단)에서 발생합니다. PCG 관련 문제가 발생하면 시작 명령에 --cuda-graph-backend-prefill=disabled를 추가해 비활성화하고 GitHub Issues에 버그를 보고해 주세요. 이 기능을 개선하는 데 큰 도움이 됩니다.
사용자용 (For Users)
서버 시작 중 다음과 같은 오류 메시지가 보이면 PCG 버그입니다:
Piecewise CUDA Graph is enabled by default as an experimental feature.
To work around this error, add --cuda-graph-backend-prefill=disabled to your launch command.
Please report this issue at https://github.com/sgl-project/sglang/issues/new/choose
이를 우회하려면 시작 명령에 --cuda-graph-backend-prefill=disabled를 추가하세요. 버그 리포트를 작성할 때는 다음을 포함해 주세요:
- 전체 오류 트레이스백
- 모델명과 양자화 방법
- 모든 인자가 있는 시작 명령
- GPU 유형과 드라이버 버전
개발자용 (For Developers)
PCG는 torch.compile로 모델 forward pass를 추적하므로, 새로 개발된 CUDA 커널(JIT 커널과 sgl-kernels 모두)은 대개 torch.compile과 기본적으로 호환되지 않습니다. 추적은 커널 내부의 JIT 컴파일, 파일 I/O, 동적 모듈 로딩 같은 추적 불가 연산에서 실패합니다.
커널을 PCG와 호환되게 만들려면 sglang.srt.utils.custom_op의 register_custom_op로 커스텀 op로 등록해야 합니다. 이는 커널을 컴파일된 그래프에서 불투명한 노드로 감싸 torch.compile이 그 안을 추적하지 않게 합니다.
예제 사용법 (JIT 커널):
from sglang.srt.utils.custom_op import register_custom_op
# Inplace operator (no return value)
@register_custom_op(mutates_args=["output_q", "output_s"])
def per_token_group_quant_8bit(
input: torch.Tensor,
output_q: torch.Tensor,
output_s: torch.Tensor,
) -> None:
# kernel implementation ...
예제 사용법 (출력이 있는 연산자):
# out_shape indicates which argument has the same shape as the output
@register_custom_op(mutates_args=["x"], out_shape=0)
def add(x: torch.Tensor, y: torch.Tensor) -> torch.Tensor:
return x.add_(y)
외부 라이브러리 함수(예: FlashInfer 커널)를 감싸려면 register_custom_op_from_extern을 대신 사용하세요. 전체 API 문서는 python/sglang/srt/utils/custom_op.py를 참고하세요.
동작 방식 (How it works)
Torch compile 백엔드
PCG는 커스텀 백엔드(SGLangBackend)로 torch.compile을 사용해 모델 forward pass를 분할·컴파일합니다. 흐름은:
model.forward wrapper
→ torch.compile(..., backend=SGLangBackend)
→ FX graph
→ split_graph() at registered split ops
→ split_gm (top-level graph that chains the pieces)
→ replace capturable submodules with CUDAPiecewiseBackend
→ runtime dispatch: eager split ops + per-piece capture/replay
-
설치 (Install):
install_torch_compiled()가model.forward를 래퍼 함수로 교체합니다.is_in_piecewise_cuda_graph()가 True를 반환하면 래퍼는 컴파일된 callable로 디스패치하고, 그 외에는 원래 forward로 폴백합니다. 이 경로를 통한 첫 호출이 Dynamo 트레이싱과 그래프 컴파일을 트리거합니다. CUDA 그래프 재생은 캡처 단계가 끝난 후에만 발생합니다. -
분할 (Split):
torch.compile이 모델을 추적할 때SGLangBackend가 FX 그래프를 받아split_graph()를 호출합니다.CompilationConfig.split_ops에 나열된 연산은 분할 지점으로 취급되어 각 지점에서 그래프가 잘립니다. 이 split-op 서브모듈은 런타임에서 eager로 실행되도록 남겨지고, 주변 서브모듈은CUDAPiecewiseBackend로 컴파일·감싸집니다. 결과는 캡처 가능한 서브그래프와 eager split-op 서브모듈이 교차하는submod_0,submod_1, … 자식이 있는 최상위 "스티칭 그래프"(split_gm)입니다. -
교체 (Replace):
PiecewiseCompileInterpreter가split_gm의 각 캡처 가능 서브모듈을 반복해 일반(동적) 형태로 컴파일하고, 그 자리를CUDAPiecewiseBackend인스턴스로 교체합니다. Split-op 서브모듈(예: attention, all-reduce)은 그대로 두고 런타임에서 eager로 실행합니다. -
디스패치 (Dispatch): 런타임에서
split_gm을 호출하면 스티칭 그래프가 실행되어 각 서브모듈을 순서대로 호출합니다. Split-op 서브모듈은 eager로 실행됩니다. 각CUDAPiecewiseBackend서브모듈은 세 단계를 거칩니다:- Compile warmup — 일반 형태 컴파일 경로를 실행.
- Capture — 각 캡처 크기에 대해 warmup 통과 후 CUDA 그래프를 기록.
- Steady-state replay — 각 forward pass에 대해 캡처된 CUDA 그래프를 재생.
피스와이즈 cuda graph 러너
PiecewiseCudaGraphRunner는 세 단계를 통해 전체 수명주기를 조정합니다:
- Compile — dummy forward pass로 JIT 커널을 워밍업한 뒤 모델을
torch.compile로 감싸 Dynamo 트레이싱을 트리거해 FX 그래프를 분할하고 각 서브그래프 조각에CUDAPiecewiseBackend인스턴스를 생성. - Capture — 캡처 크기를 역순(큰 것부터)으로 반복. 각 크기에 대해 forward pass를 두 번 실행(워밍업 1회, CUDA 그래프 캡처 1회).
- Replay — 런타임에서 이진 검색으로 실제 토큰 수 >= 캡처 크기 중 가장 작은 것을 찾아 입력을 정적 버퍼에 zero-padding으로 복사하고 캡처된 CUDA 그래프를 재생하며, 출력을 실제 토큰 수로 슬라이스.
메모리 최적화 (Memory optimization)
PCG의 메모리 비용은 두 부분에서 옵니다: torch memory allocator와 non-torch memory.
torch 메모리 할당자 오버헤드는 몇 가지 최적화 덕분에 사소합니다. 전역 공유 메모리 풀이 모든 CUDA 그래프 러너와 캡처 크기에 걸쳐 재사용되고, 캡처가 역순(큰 것부터 작은 것)으로 수행되어 작은 그래프가 큰 그래프가 할당한 메모리를 재사용하며, 마지막 서브그래프의 출력 텐서는 최대 메모리 재사용을 위해 약한 참조(weak references)로 저장됩니다.
주요 메모리 오버헤드는 non-torch 메모리에서 옵니다. CUDA 그래프 객체 자체가 기록된 커널 런치 파라미터와 내부 상태를 저장하려면 GPU 메모리가 필요합니다. 이 오버헤드는 캡처된 크기 수에 따라 확장되므로, piecewise_cuda_graph_max_tokens는 기본적으로 보수적으로 상한을 둡니다.
형태 구성 (Shape configuration)
Piecewise CUDA 그래프는 토큰 수 집합에 대한 그래프를 미리 캡처합니다. 런타임에 실제 토큰 수는 가장 가까운 캡처 크기로 올림(이진 검색)되고 해당 그래프가 재생됩니다. 토큰 수가 가장 큰 캡처 크기를 초과하면 런타임은 일반(비그래프) forward 경로로 폴백합니다.
기본 캡처 스케줄은 증가하는 세분성으로 자동 생성됩니다:
| Token range | Step size |
|---|---|
| 4 – 32 | 4 |
| 48 – 256 | 16 |
| 288 – 512 | 32 |
| 576 – 1024 | 64 |
| 1280 – 4096 | 256 |
| 4096+ | 512 |
자동 생성 스케줄의 경우 크기는 --cuda-graph-max-bs-prefill로 상한이 정해집니다. 기본 상한은 비-MLA 모델의 chunked_prefill_size, MLA 백엔드 모델의 2048입니다. --max-total-tokens가 설정되면 상한은 이를 넘지 않도록 더 제한됩니다. 또한 Llama-2 모델은 임시 해결책으로 4096 토큰에서 자동 상한이 정해집니다.
호환성 (Compatibility)
PCG는 다음 시나리오에서 자동 비활성화됩니다. 우리는 호환성 확장을 적극적으로 작업 중이며, 이들 중 많은 지원이 곧 제공될 예정입니다.
- 비활성 모델 아키텍처 (예:
DeepseekV32ForCausalLM) - 추측 디코딩 (Speculative decoding)
- DP attention
- 파이프라인 병렬화 (
pp_size > 1) - 비-CUDA 하드웨어 (AMD ROCm, Ascend NPU)
- MoE A2A 백엔드
- LoRA
- 멀티모달 / VLM 모델
- DLLM (diffusion LLM)
- 결정적 추론 (Deterministic inference)
- PD disaggregation
- Expert distribution recorder / EPLB
모든 자동 비활성 검사를 건너뛰려면 --cuda-graph-backend-prefill=tc_piecewise를 사용하세요(테스트/디버깅 전용).
코드 참조 (Code Reference)
| File | Description |
|---|---|
python/sglang/srt/model_executor/runner_backend/tc_piecewise_cuda_graph_backend.py |
백엔드 구현: compile, capture, replay |
python/sglang/srt/compilation/compile.py |
install_torch_compiled 트램폴린 |
python/sglang/srt/compilation/backend.py |
SGLangBackend, 그래프 분할, piecewise 컴파일 |
python/sglang/srt/compilation/cuda_piecewise_backend.py |
서브그래프별 CUDA 그래프 캡처/재생 |
python/sglang/srt/compilation/piecewise_context_manager.py |
전역 컨텍스트 플래그와 ForwardContext |
python/sglang/srt/compilation/compilation_config.py |
캡처 크기, split ops, 컴파일러 설정 |
python/sglang/srt/utils/custom_op.py |
torch.compile 호환용 register_custom_op |
python/sglang/srt/server_args.py |
서버 인자와 자동 비활성 로직 |