gpumode · 강의 아카이브
《GPU Mode》 L035 2024 · OCT High priority transcript · available

SGLang — Performance Optimization

LLM 서빙 엔진의 latency 가 절반 이상 CPU scheduling 위에서 새는 시점이 왔다. vLLM 의 v0 가 GPU idle 50% 까지 흘렸다는 측정에서 출발해, SGLang 이 깐 답 — RadixAttention 의 prefix sharing, CPU/GPU overlap, 그리고 FlashInfer/torch.compile/structured generation 까지의 통합. Yineng Zhang 이 직접 짜고 있는 엔진의 내부를 한 시간 안에 깐 강의를 학습 노트로 다시 정리한다.

SGLang RadixAttention prefix cache vLLM FlashInfer scheduler structured output tensor parallel
Y
Speaker
Yineng Zhang
SGLang core maintainer · zhyncs
강의 번호
L035
스피커
Yineng Zhang
학습 우선순위
High · 정독
다시 볼 때
서빙 엔진 비교
§ 01강의가 풀려는 문제· why a new serving engine

“커널이 빨라도 엔진이 막으면 GPU 는 절반만 돈다” 의 측정에서 시작

2024년 LLM 서빙은 더 이상 “FlashAttention 을 누가 더 빨리 짜느냐” 의 게임이 아니다. 같은 attention 커널을 쓰면서도 엔진별로 throughput 이 두 배 차이가 나는데, 그 차이가 거의 다 CPU scheduling overhead에 있다는 게 SGLang 의 출발점.

강의 첫 슬라이드의 측정 — vLLM 의 초기 버전과 SGLang 의 초기 버전 모두에서 “CPU 가 다음 batch 를 준비하는 동안 GPU 가 50% 이상 idle” 인 구간이 있었다. 같은 GPU, 같은 모델, 같은 attention 커널인데도 엔진의 control loop 가 막혀서 throughput 이 절반 이하로 떨어진다.

강의의 인지적 frame

SGLang 의 입장 — “LLM 서빙은 더 이상 kernel 게임이 아니라 system 게임이다.” 좋은 attention 커널 (FlashAttention 3, FlashInfer) 은 이미 있다. 진짜 잠금이 풀리는 자리는 다음 batch 를 어떻게 미리 준비하는가, 같은 prefix 를 쓰는 요청들의 KV 를 어떻게 공유하는가, structured output 을 grammar mask 와 어떻게 묶는가 같은 파이프라인 차원의 결정이다.

“엔진이 50% 이상의 시간을 CPU scheduling 에 쓰는 걸 측정하고 나서야, ‘좋은 커널만으론 안 된다’ 가 분명해졌다.”Yineng Zhang · 강의 도입부

그래서 강의 끝에 손에 잡혀야 하는 자산은 — (1) CPU scheduling 이 어떻게 GPU 를 굶기는가의 mental model, (2) RadixAttention 이 prefix sharing 을 어떻게 일반화하는가, (3) SGLang 의 frontend DSL 이 왜 단순 OpenAI 호환이 아니라 별도의 형태가 됐는가, (4) vLLM 과 정확히 어디가 다른가.

§ 02LLM 서빙의 control-flow 문제· CPU 가 GPU 를 굶긴다

continuous batching 의 매 step 에서 CPU 가 해야 하는 일들

continuous batching (vLLM 이 일반화한 패턴) 은 매 forward 마다 batch 의 구성을 다시 만든다. 새 요청 도착, 종료된 요청 제거, KV cache 메모리 할당, FlashAttention 메타데이터 빌드 — 이 모든 게 한 step 안에 들어간다. 모델 forward 가 5ms 라면, scheduling 이 또 5ms 걸리는 순간 throughput 은 절반.

FIG · CPU/GPU overlap 의 Before / AftervLLM v0 → SGLang
v0 (overlap 없음)
GPU idle 50%
sched (n)
forward (n)
sched (n+1)
forward (n+1)
sched (n+2)
SGLang (overlapped)
GPU near 100%
sched (n)
forward (n)
prep (n+1)
forward (n+1)
prep (n+2)
SGLang 은 다음 step 의 scheduling 을 현재 step 의 GPU forward 와 병렬로 미리 깐다. CPU 가 해야 하는 일은 같지만, GPU 의 idle 구간이 사라진다.

이 한 줄짜리 변경이 만들어내는 차이는 단순한 latency 가 아니다 — throughput 이 거의 두 배로 뜬다. 강의에서 Yineng 이 보여준 측정 — Llama-3.1-8B 의 throughput 이 v0 vLLM 대비 약 1.5×~2× 수준. 같은 모델, 같은 GPU 에서.

scheduling 안에서 CPU 가 하는 일

한 step 마다 — (a) 새 request 받기 (HTTP/gRPC), (b) KV cache 페이지 할당, (c) RadixAttention 트리 lookup, (d) attention metadata (block table) 빌드, (e) tokenize/detokenize, (f) structured output 의 mask 갱신. (d)와 (f)가 보통 가장 무겁다.

§ 03RadixAttention· prefix tree 위 KV 공유

같은 system prompt 를 쓰는 1,000개 요청의 KV 를 한 번만 계산한다

RadixAttention 은 SGLang 의 핵심 자산. 여러 요청이 prefix 를 공유하는 일 이 LLM 서빙의 거의 모든 실제 워크로드(few-shot, system prompt, tree-of-thoughts 등)에서 흔하다는 관찰에서 출발 — KV cache 를 단순 LRU 페이지가 아니라 radix tree 로 관리한다.

FIG · radix tree 가 prefix 공유를 어떻게 잡는가simplified · 3 requests
[system: "You are a helpful…"]3 hits
├─[user: "Translate to French:"]2 hits
│ ├─"Hello world"unique
│ └─"How are you"unique
└─[user: "Summarize:"]1 hit
트리의 회색 노드가 공유된 prefix — 그 부분의 KV 는 한 번만 계산되고, 새 요청이 도착하면 트리를 따라 내려가며 일치하는 가장 긴 prefix 까지 cache 를 재사용한다. 분기점에서만 새로 계산.

이 구조의 이득은 두 단계로 온다. 첫째 — prefill 비용이 줄어든다. system prompt 가 1k 토큰이라도, 두 번째 요청부터는 prefix 부분의 prefill 을 건너뛴다. 둘째 — 같은 prefix 를 쓰는 N 개 요청이 batch 안에 들어오면, attention 커널이 K/V 를 한 번만 read 한다 (FlashInfer 의 shared-prefix 모드와 결합).

구현이 까다로운 곳은 eviction. radix tree 노드를 LRU 로 자르면, 자식 노드까지 잘라야 cache 일관성이 깨지지 않는다. SGLang 은 reference count 기반의 변형 LRU 를 쓴다 — “현재 진행 중인 요청이 의존하는 노드는 자르지 않는다”.

“radix tree page size = 1 이 가장 많은 prefix match 를 만든다. 더 큰 page 로 묶으면 메모리 효율은 좋지만 hit 비율이 깎인다.”Yineng Zhang
§ 04frontend DSL· SGLang 프로그램 구조

OpenAI API 호환만으로는 RadixAttention 이 못 산다 — 그래서 별도 frontend

SGLang 은 backend 만 있는 게 아니다. 같은 이름이 붙은 frontend DSL 이 있고, 이게 RadixAttention 을 효율적으로 쓸 수 있는 형태로 프로그램을 구조화하게 강제한다.

# SGLang frontend — sl.gen / sl.fork / sl.select
@sgl.function
def few_shot_qa(s, question):
    s += "You are a math tutor.\n"     # shared prefix
    s += "Q: 1+1?\nA: 2\n"             # shared examples
    s += "Q: 2*3?\nA: 6\n"
    s += "Q: " + question + "\nA: "     # branch point
    s += sgl.gen("answer", max_tokens=64)

# 1000개의 question 으로 호출 — system prompt 와 examples 의 KV 가
# 자동으로 radix tree 위에서 공유된다

이 형태가 중요한 이유 — radix tree 가 토큰 시퀀스를 즉시 알 수 있어야 공유가 잡힌다. OpenAI API 처럼 messages = [...] 만 받으면 같은 system prompt 라도 형식 차이로 prefix match 가 깨진다.

  • sgl.fork(N) — 같은 prefix 위에서 N 개 분기. self-consistency 류 알고리즘에 직접 매핑.
  • sgl.gen(name, …) — 명시적인 generation 단계. 각 단계의 출력을 바로 다음 단계의 prefix 로 쓸 수 있다.
  • sgl.select(choices) — “이 시점에 N 개 선택지 중 LLM 이 무엇을 가장 확률 높게 보는가” 를 RadixAttention 으로 효율적으로 측정.

이 DSL 이 자체적으로 의미가 있는 게 아니라 — backend 의 prefix sharing 이득을 사용자가 의도하지 않아도 자동으로 받을 수 있는 형태로 프로그램을 강제한다는 점이 핵심.

§ 05스케줄러 구조· batch · 메모리 회계

다음 batch 를 미리 준비하는 두 단계 파이프라인

SGLang scheduler 의 핵심 결정 — (a) request 의 reorder, (b) 메모리 budget 안에서 batch 크기 결정, (c) attention metadata 빌드 — 이 셋을 GPU forward 와 overlap 한다. 한 step 의 GPU 가 도는 동안, 그 다음 step 의 (a)~(c) 가 CPU thread 위에서 미리 끝난다.

v0 lockstep scheduler

step 단위 직렬화

한 step 끝 → 다음 batch 결정 → forward → 반복. 단순하지만 GPU idle 이 매 step 마다 발생. CPU 작업이 무거워지면 그대로 latency 에 합산.

SGLang overlap scheduler

forward 와 prep 동시 진행

step n 의 forward 가 GPU 위에서 도는 동안, step n+1 의 batch 결정과 metadata 빌드가 별도 thread 에서 끝난다. CUDA stream 을 사이에 두고 producer/consumer 가 한 큐로 묶여 있다.

overlap 이 가능한 이유는 단순하다 — step n 의 forward 가 끝나기 전에도, n+1 의 batch 구성은 (현재 진행 중인 요청들 + queue 에 쌓인 새 요청) 만으로 결정된다. n 의 출력 토큰 이 들어와야 하는 부분(EOS 검사, structured output mask 갱신)만 마지막에 갱신.

메모리 회계가 가장 까다로운 부분

scheduler 가 다음 batch 를 잡을 때, KV cache 메모리가 현재 진행 중인 모든 요청을 위해 필요한 분 + 새 batch 가 가져갈 분 을 다 감당해야 한다. RadixAttention 의 prefix 공유로 줄어든 양도 정확히 회계 — 특정 prefix 가 N 개 요청에 의해 reference 되면, 그 prefix 의 KV 는 메모리 점유 1번으로만 잡힌다. 이 회계가 깨지면 OOM 또는 GPU 가 비는 underutilization.

§ 06vLLM 과 비교· 설계 결정의 차이

같은 문제를 같은 시기에 풀고 있는 두 엔진 — 어디가 다른가

vLLM 이 사실상 표준이 된 시점에서 SGLang 이 등장한 이유. 강의에서 Yineng 이 직접 비교한 표를 정리한다 — 모든 항목에서 SGLang 이 우위는 아니지만, prefix sharing 과 frontend DSL 이 두드러진다.

항목vLLMSGLang차이의 근원
KV cache layoutpaged (block=16)paged + radix treeprefix sharing 의 일급화
prefix cachingenable_prefix_caching=true기본 활성DSL 이 cache 친화적
schedulerasync lock-stepoverlap schedulerCPU/GPU 동시 진행
attention backendFlashAttention/FA3/TritonFlashInfer 우선 + Tritonbackend 사이 빠른 전환
structured outputXGrammar / outlinesXGrammar 통합grammar mask 의 overlap
frontendOpenAI 호환만DSL + OpenAI 호환프로그램 구조 강제
multi-modal광범위 (LLaVA, Qwen-VL)광범위 + 동등사실상 동등

정확한 입장 — vLLM 은 더 먼저 와 있고 더 많은 모델/하드웨어를 지원한다. SGLang 의 자리는 “같은 워크로드에서 throughput 1.5~2배 + 더 풍부한 frontend”. 회사가 OpenAI 호환만 필요하면 vLLM 이 보통 더 안전하고, multi-turn agent 워크로드처럼 prefix 공유가 큰 시나리오라면 SGLang 의 이득이 가시화된다.

“우리는 vLLM 을 ‘이긴다’ 가 아니라 ‘다른 trade-off’ 라고 본다. 정해진 워크로드에서는 SGLang 이 분명히 더 좋다.”Yineng Zhang · Q&A
§ 07멀티모달 · structured output· JSON · regex grammar

출력 형식이 결정된 시나리오에서 grammar mask 의 비용을 어떻게 낮추는가

structured output (JSON schema, regex) 은 매 토큰마다 vocabulary 위에 mask 를 만든다. 그 mask 비용이 무거운 경우, scheduling 의 overlap 으로 해결된다.

SGLang 이 기본으로 통합한 backend 는 XGrammar — Tianqi Chen 그룹이 만든 grammar-based decoding 라이브러리. 핵심은 두 가지.

  • compressed mask — 일반적으로 vocabulary mask 는 vocab_size 만큼 큰 boolean tensor. XGrammar 는 이걸 sparse 한 형태로 압축해 GPU 로의 transfer 비용을 낮춘다.
  • parallel mask construction — 다음 토큰의 mask 를 GPU forward 와 병렬로 CPU 위에서 만든다. SGLang scheduler 의 overlap 패턴과 자연스럽게 합쳐짐.

multi-modal 은 강의에서 짧게 언급. 핵심은 — vision encoder 의 cache 를 별도로 관리 한다는 점. 같은 이미지가 여러 요청에 들어오면 (예: agent 의 전체 화면 캡처가 stream 되는 상황) 그 vision encoder 출력을 cache.

FIG · structured output 의 mask 비용 분포JSON schema · 50 토큰
naive (per-step CPU)+25 ms
XGrammar (overlapped)+4 ms
free run (no schema)+0 ms
JSON schema 한 줄을 강제하는 50 토큰 생성. naive 한 mask 빌드는 25ms 추가. XGrammar + overlap 으로 거의 free 에 가까워진다.
§ 08FlashInfer · Triton 통합· attention backend

SGLang 은 attention 커널을 직접 안 짠다 — 그 자리에 FlashInfer 를 묶는다

SGLang 의 명시적 결정 — “attention 커널은 우리가 짜지 않는다”. 대신 FlashInfer (L041) 를 first-class backend 로 묶고, fall-back 으로 Triton attention 을 둔다.

왜 FlashInfer 인가

FlashInfer 는 다양한 attention 형태 (prefill / decode / GQA / MQA / sparse / chunked prefill) 를 한 API 로 노출한다. SGLang 의 RadixAttention 이 만들어내는 shared-prefix attention 처럼 특수한 패턴도 FlashInfer 의 한 모드로 표현 가능. kernel 개발과 system 개발의 책임 분리가 깔끔해진다.

강의에서 Yineng 이 말한 한 줄 — “FlashInfer 의 다음 release 는 SGLang 에 약 10~20% 추가 성능을 가져올 것이다. 우리가 같이 작업 중이라.” 이 종속성이 강의의 자연스러운 다리가 — L041 으로 이어지는 자리.

Triton attention 이 fallback 으로 남는 이유 — 새 모델 / 새 GPU 로 처음 띄울 때 FlashInfer 의 binary 가 아직 빌드되지 않은 상태에서 동작 보장. 또한 Triton 은 customize 가 trivial 해서 디버깅 용도로도 유용.

“좋은 attention 커널은 이미 있다. 그걸 다시 짜는 건 의미가 없고, 잘 묶는 게 진짜 일이다.”Yineng Zhang
§ 09배포 사례와 다음 단계· production hooks

실제 회사들이 SGLang 을 어디서 어떻게 쓰는가 — 강의 시점의 풍경

강의의 마지막 절반에서 Yineng 이 언급한 production 사례들. 자세한 숫자는 NDA 로 막혀 있지만, 패턴은 분명하다 — agent / multi-turn / structured output 워크로드가 많은 곳일수록 SGLang 의 이득이 큼.

코드 어시스턴트

long shared prefix

같은 system prompt 와 codebase 가 여러 요청에 공유. RadixAttention hit rate 가 높고 throughput 이득이 가장 명확.

agent / tool use

multi-step + structured output

각 step 의 output 이 JSON. XGrammar 통합 + overlap 이 누적 latency 를 깎는다. SGLang 의 frontend DSL 이 자연스럽게 매핑.

self-consistency / ToT

prefix fork

같은 prompt 위에서 N 갈래로 분기. sgl.fork(N) 한 줄. attention 커널 안에서 K/V read 를 한 번만.

RAG (긴 컨텍스트)

chunked prefill

수만 토큰 컨텍스트의 prefill 을 chunk 단위로 잘라 decode 와 overlap. FlashInfer 의 chunked prefill 모드가 직접 도움.

강의 시점 기준으로 SGLang 의 다음 단계 — (a) Hopper TMA 활용 강화, (b) FlashInfer 의 다음 release 와의 통합, (c) stream-K 류 SM 균등 분배 알고리즘. (c) 의 실험은 강의 시점에 이미 “예상보다 잘 된다” 는 측정.

§ 10기억할 메모와 코드· repo · slides

다시 열었을 때 5분 안에 손에 잡혀야 할 것

SGLang 을 6개월 뒤 다시 마주했을 때 가장 빨리 복원해야 할 사실들과 — 직접 읽어볼 코드 위치들.

CPU/GPU overlap
scheduling 작업을 forward 와 병렬로 진행 — 같은 hardware 에서 throughput 1.5~2× 의 가장 큰 단일 원인.
RadixAttention
KV cache 를 LRU 페이지가 아니라 radix tree. page=1 이 hit rate 표준. reference count 기반 eviction.
scheduler 의 메모리 회계
prefix 공유로 줄어든 KV 메모리를 정확히 잡아야 underutilization / OOM 안 생긴다.
frontend DSL
sgl.gen / sgl.fork / sgl.select. 프로그램 구조가 backend 의 prefix 공유에 친화적이게 강제.
FlashInfer 우선
attention 커널은 직접 안 짠다. FlashInfer 가 main backend, Triton 이 fallback.
XGrammar 통합
structured output 의 mask 비용을 overlap 으로 거의 free 까지. JSON/regex 시나리오의 표준.
vLLM 과의 위치
overlap + prefix sharing 의 일급화로 차별. vLLM 은 모델/하드웨어 커버리지에서 더 넓다.
실험 중 기능
stream-K 류 SM 균등화, Hopper TMA 강화, chunked prefill. 다음 release 의 큰 줄기.

손에 새기기 — 실습 시퀀스

  1. SGLang 띄우고 toy 모델 서빙 — Llama-3.2-1B 정도. --enable-radix-cache 명시 (default 지만). 첫 sanity.
  2. same-prefix 워크로드 throughput — 같은 system prompt + 다른 question 1k 개. radix cache hit rate 와 throughput 측정. RadixAttention 의 이득이 보이는 가장 단순한 실험.
  3. frontend DSL 로 self-consistencysgl.fork(8) 로 같은 question 에 8개 답 생성. attention 커널이 K/V 를 한 번만 읽는지 메모리 트래픽으로 검증.
  4. structured output 비용 비교 — JSON schema 강제 ON/OFF 에서 latency 차이. XGrammar 통합 의 효과.
  5. scheduler overlap 끄기 — 환경변수로 v0 lockstep 으로 fall-back 시켜서 throughput 차이 직접 측정. overlap 의 값을 숫자로 손에 잡는다.
  6. vLLM 과 같은 모델 비교 — 같은 GPU, 같은 모델, 같은 batch. throughput · TTFT (time-to-first-token) · TBT (time-between-tokens) 셋 다 본다.
§ 11다른 강의로 이어지는 길· connections

SGLang 의 도구가 시리즈 안에서 다시 등장하는 자리

L035 의 system 차원 최적화 가 다른 강의들의 어디에 다시 묶이는지.

§ 12열린 질문· open questions

다음에 다시 들었을 때 직접 검증해야 할 것들

강의에서 부분적으로만 등장한 주제, 후속 작업으로 비워둔 자리들.

검증 메모

이 노트의 비교 수치(throughput 1.5~2×, mask cost 25ms→4ms)는 강의 슬라이드를 재구성한 예시. 자기 워크로드와 모델에 대해 직접 측정해야 의미 있는 baseline. 특히 vLLM 의 버전이 빠르게 갱신되고 있어 “v0” 라는 표현은 이미 historical 한 점.

← Lecture 034 Low Bit Triton Kernels Lecture 036 → CUTLASS & FA3 — Jay Shah 가 깐 H100 의 producer-consumer 와 warp specialization