LLM 서빙 엔진의 latency 가 절반 이상 CPU scheduling 위에서 새는 시점이 왔다. vLLM 의 v0 가 GPU idle 50% 까지 흘렸다는 측정에서 출발해, SGLang 이 깐 답 — RadixAttention 의 prefix sharing, CPU/GPU overlap, 그리고 FlashInfer/torch.compile/structured generation 까지의 통합. Yineng Zhang 이 직접 짜고 있는 엔진의 내부를 한 시간 안에 깐 강의를 학습 노트로 다시 정리한다.
2024년 LLM 서빙은 더 이상 “FlashAttention 을 누가 더 빨리 짜느냐” 의 게임이 아니다. 같은 attention 커널을 쓰면서도 엔진별로 throughput 이 두 배 차이가 나는데, 그 차이가 거의 다 CPU scheduling overhead에 있다는 게 SGLang 의 출발점.
강의 첫 슬라이드의 측정 — vLLM 의 초기 버전과 SGLang 의 초기 버전 모두에서 “CPU 가 다음 batch 를 준비하는 동안 GPU 가 50% 이상 idle” 인 구간이 있었다. 같은 GPU, 같은 모델, 같은 attention 커널인데도 엔진의 control loop 가 막혀서 throughput 이 절반 이하로 떨어진다.
SGLang 의 입장 — “LLM 서빙은 더 이상 kernel 게임이 아니라 system 게임이다.” 좋은 attention 커널 (FlashAttention 3, FlashInfer) 은 이미 있다. 진짜 잠금이 풀리는 자리는 다음 batch 를 어떻게 미리 준비하는가, 같은 prefix 를 쓰는 요청들의 KV 를 어떻게 공유하는가, structured output 을 grammar mask 와 어떻게 묶는가 같은 파이프라인 차원의 결정이다.
그래서 강의 끝에 손에 잡혀야 하는 자산은 — (1) CPU scheduling 이 어떻게 GPU 를 굶기는가의 mental model, (2) RadixAttention 이 prefix sharing 을 어떻게 일반화하는가, (3) SGLang 의 frontend DSL 이 왜 단순 OpenAI 호환이 아니라 별도의 형태가 됐는가, (4) vLLM 과 정확히 어디가 다른가.
continuous batching (vLLM 이 일반화한 패턴) 은 매 forward 마다 batch 의 구성을 다시 만든다. 새 요청 도착, 종료된 요청 제거, KV cache 메모리 할당, FlashAttention 메타데이터 빌드 — 이 모든 게 한 step 안에 들어간다. 모델 forward 가 5ms 라면, scheduling 이 또 5ms 걸리는 순간 throughput 은 절반.
이 한 줄짜리 변경이 만들어내는 차이는 단순한 latency 가 아니다 — throughput 이 거의 두 배로 뜬다. 강의에서 Yineng 이 보여준 측정 — Llama-3.1-8B 의 throughput 이 v0 vLLM 대비 약 1.5×~2× 수준. 같은 모델, 같은 GPU 에서.
한 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)가 보통 가장 무겁다.
RadixAttention 은 SGLang 의 핵심 자산. 여러 요청이 prefix 를 공유하는 일 이 LLM 서빙의 거의 모든 실제 워크로드(few-shot, system prompt, tree-of-thoughts 등)에서 흔하다는 관찰에서 출발 — KV cache 를 단순 LRU 페이지가 아니라 radix tree 로 관리한다.
이 구조의 이득은 두 단계로 온다. 첫째 — 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 를 쓴다 — “현재 진행 중인 요청이 의존하는 노드는 자르지 않는다”.
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 가 깨진다.
이 DSL 이 자체적으로 의미가 있는 게 아니라 — backend 의 prefix sharing 이득을 사용자가 의도하지 않아도 자동으로 받을 수 있는 형태로 프로그램을 강제한다는 점이 핵심.
SGLang scheduler 의 핵심 결정 — (a) request 의 reorder, (b) 메모리 budget 안에서 batch 크기 결정, (c) attention metadata 빌드 — 이 셋을 GPU forward 와 overlap 한다. 한 step 의 GPU 가 도는 동안, 그 다음 step 의 (a)~(c) 가 CPU thread 위에서 미리 끝난다.
한 step 끝 → 다음 batch 결정 → forward → 반복. 단순하지만 GPU idle 이 매 step 마다 발생. CPU 작업이 무거워지면 그대로 latency 에 합산.
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.
vLLM 이 사실상 표준이 된 시점에서 SGLang 이 등장한 이유. 강의에서 Yineng 이 직접 비교한 표를 정리한다 — 모든 항목에서 SGLang 이 우위는 아니지만, prefix sharing 과 frontend DSL 이 두드러진다.
정확한 입장 — vLLM 은 더 먼저 와 있고 더 많은 모델/하드웨어를 지원한다. SGLang 의 자리는 “같은 워크로드에서 throughput 1.5~2배 + 더 풍부한 frontend”. 회사가 OpenAI 호환만 필요하면 vLLM 이 보통 더 안전하고, multi-turn agent 워크로드처럼 prefix 공유가 큰 시나리오라면 SGLang 의 이득이 가시화된다.
structured output (JSON schema, regex) 은 매 토큰마다 vocabulary 위에 mask 를 만든다. 그 mask 비용이 무거운 경우, scheduling 의 overlap 으로 해결된다.
SGLang 이 기본으로 통합한 backend 는 XGrammar — Tianqi Chen 그룹이 만든 grammar-based decoding 라이브러리. 핵심은 두 가지.
multi-modal 은 강의에서 짧게 언급. 핵심은 — vision encoder 의 cache 를 별도로 관리 한다는 점. 같은 이미지가 여러 요청에 들어오면 (예: agent 의 전체 화면 캡처가 stream 되는 상황) 그 vision encoder 출력을 cache.
SGLang 의 명시적 결정 — “attention 커널은 우리가 짜지 않는다”. 대신 FlashInfer (L041) 를 first-class backend 로 묶고, fall-back 으로 Triton attention 을 둔다.
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 해서 디버깅 용도로도 유용.
강의의 마지막 절반에서 Yineng 이 언급한 production 사례들. 자세한 숫자는 NDA 로 막혀 있지만, 패턴은 분명하다 — agent / multi-turn / structured output 워크로드가 많은 곳일수록 SGLang 의 이득이 큼.
같은 system prompt 와 codebase 가 여러 요청에 공유. RadixAttention hit rate 가 높고 throughput 이득이 가장 명확.
각 step 의 output 이 JSON. XGrammar 통합 + overlap 이 누적 latency 를 깎는다. SGLang 의 frontend DSL 이 자연스럽게 매핑.
같은 prompt 위에서 N 갈래로 분기. sgl.fork(N) 한 줄. attention 커널 안에서 K/V read 를 한 번만.
수만 토큰 컨텍스트의 prefill 을 chunk 단위로 잘라 decode 와 overlap. FlashInfer 의 chunked prefill 모드가 직접 도움.
강의 시점 기준으로 SGLang 의 다음 단계 — (a) Hopper TMA 활용 강화, (b) FlashInfer 의 다음 release 와의 통합, (c) stream-K 류 SM 균등 분배 알고리즘. (c) 의 실험은 강의 시점에 이미 “예상보다 잘 된다” 는 측정.
SGLang 을 6개월 뒤 다시 마주했을 때 가장 빨리 복원해야 할 사실들과 — 직접 읽어볼 코드 위치들.
--enable-radix-cache 명시 (default 지만). 첫 sanity.sgl.fork(8) 로 같은 question 에 8개 답 생성. attention 커널이 K/V 를 한 번만 읽는지 메모리 트래픽으로 검증.L035 의 system 차원 최적화 가 다른 강의들의 어디에 다시 묶이는지.
강의에서 부분적으로만 등장한 주제, 후속 작업으로 비워둔 자리들.
이 노트의 비교 수치(throughput 1.5~2×, mask cost 25ms→4ms)는 강의 슬라이드를 재구성한 예시. 자기 워크로드와 모델에 대해 직접 측정해야 의미 있는 baseline. 특히 vLLM 의 버전이 빠르게 갱신되고 있어 “v0” 라는 표현은 이미 historical 한 점.