추론·서빙 프레임워크
SGLang은 LLM·멀티모달 모델의 추론을 API 서비스로 제공하는 프레임워크입니다. 모델 가중치를 읽고, 입력을 숫자 토큰으로 바꾸고, 여러 요청을 묶어 실행한 뒤, 생성한 토큰을 다시 문장으로 돌려줍니다. 이름의 출발점은 구조화된 생성 언어이지만, 현재 저장소에는 서빙 런타임과 확산 모델 실행 엔진도 들어 있습니다.
이 저장소의 핵심은 모델을 더 똑똑하게 학습시키는 것이 아니라, 이미 있는 모델을 더 효율적으로 실행하는 방법입니다. 강화학습 시스템과 연결할 때도 주로 답변 후보를 생성하는 역할을 맡습니다. 학습용 최적화기와 역전파 전체를 이 서버가 대신한다는 뜻은 아닙니다.
질문마다 요리사를 새로 부르면 준비 시간이 길어집니다. 여러 주문의 공통 준비 작업을 재사용하고, 한 주문이 끝난 자리에 다음 주문을 넣으면 같은 장비로 더 많은 손님을 받을 수 있습니다.
KV 캐시는 미리 손질해 둔 재료, 스케줄러는 다음 조리 순서를 정하는 담당자, 배치는 함께 조리할 주문 묶음입니다. 재료를 무한정 보관할 수 없어서 버릴 시점도 정해야 합니다.
기본 HTTP 서버는 /generate와 OpenAI 호환 /v1/chat/completions 등을 제공합니다. 전자는 SGLang의 요청 형식을 직접 사용하고, 후자는 익숙한 채팅 클라이언트를 연결하기 좋습니다. API 형식이 호환된다고 모든 공급자의 부가 기능까지 동일한 것은 아닙니다.
# 요청 형식 예시: POST /generate 의 JSON 본문
{
"text": "배치 처리를 초보자에게 설명해 줘.",
"sampling_params": {
"temperature": 0,
"max_new_tokens": 64
},
"stream": false
}
이 필드는 python/sglang/srt/managers/io_struct.py의 GenerateReqInput에서 확인할 수 있습니다. sampling_params는 생성 길이와 무작위성 같은 조건이고, stream은 결과를 한 번에 받을지 생성 중간에도 받을지를 정합니다.
학습할 때는 세 층을 나눠 보세요. 입구는 요청 검증과 응답, 조율은 배치와 캐시, 실행은 모델과 GPU 커널입니다. 처음부터 모든 모델의 수학을 이해하지 않아도 입구에서 실행까지 연결되는 흐름은 추적할 수 있습니다.
상담 챗봇은 매 요청에 같은 시스템 지시문을 붙이고, 문서 질의 서비스는 같은 문서를 여러 번 읽습니다. 앞부분이 동일한 토큰열이라면 이전 계산의 일부를 재사용할 여지가 있습니다. SGLang의 대표 아이디어인 RadixAttention은 이런 접두어 공유를 KV 캐시 관리와 연결합니다. 원래의 설계 배경은 SGLang 논문에서 확인할 수 있습니다.
여기서 재사용하는 것은 완성된 답변 문자열이 아닙니다. 입력 접두어를 처리하며 만든 모델 내부 상태입니다. 따라서 뒷부분의 질문이 달라도 앞부분이 정확히 같으면 도움이 될 수 있습니다. 의미가 비슷한 문장을 벡터 검색으로 찾아 답을 재활용하는 기능과는 원리가 다릅니다.
사용자마다 입력 길이와 출력 길이가 다릅니다. 가장 긴 응답이 끝날 때까지 처음의 묶음을 고정하면 빈자리가 생깁니다. Scheduler.get_next_batch_to_run()은 실행 중인 요청을 관리하면서 새 요청이 합류할 배치를 결정합니다. LLM 서버의 처리량은 모델 계산 속도뿐 아니라, 다음 계산을 얼마나 빈틈없이 준비하느냐에도 달려 있습니다.
| 도구 | 특징과 학습 초점 | SGLang을 살펴볼 이유 |
|---|---|---|
| SGLang | 접두어 캐시·배치 스케줄링·모델 실행·분산 서빙을 연결 | 알고리즘이 API 서버의 상태 관리와 만나는 경로를 한 저장소에서 추적 |
| vLLM | PagedAttention과 블록 단위 KV 메모리 관리, 접두어 캐싱 | 같은 문제를 트리·캐시 선택 계층과 어떻게 결합하는지 비교 학습 |
| llama.cpp | C/C++ 중심 추론, 양자화와 여러 로컬 실행 환경 | 로컬 실행 중심 관점에서 다중 요청 서버의 프로세스·배치 설계로 시야 확장 |
vLLM도 접두어 캐싱을 지원합니다. “SGLang에만 캐시가 있다”는 비교는 틀립니다. 비교 근거는 vLLM의 접두어 캐시 설계 문서와 llama.cpp 공식 저장소입니다. 이 표는 구조와 공부 방향을 비교하며, 동일 조건의 속도 순위를 뜻하지 않습니다.
python/sglang/multimodal_gen/과 별도 의존성 묶음이 존재합니다.2026-10-23 GitHub Trending Python Weekly 9위, 스타 37,655개는 관심의 크기를 보여 줍니다. 다만 그 주 순위가 오른 단일 원인을 이 숫자만으로 증명할 수는 없습니다. 위 항목은 현재 코드가 다루는 문제를 바탕으로 해석한 관심 요인입니다. 2024년 논문의 성능 수치도 2026년의 모든 모델·GPU에 그대로 적용할 수 없습니다.
주 언어는 Python이지만 Python만으로 완결된 서버는 아닙니다. 요청 조율은 Python, 장치 계산은 PyTorch와 전용 커널, 일부 서버·라우팅·확장 모듈은 Rust가 맡습니다. 아래 버전은 커밋 611a703의 선언값입니다. 설치 환경에서 실제로 선택된 모든 패키지의 버전 목록과는 구분해야 합니다.
| 층 | 기술·선언 버전 | 어디서 확인하고 무엇을 배우나 |
|---|---|---|
| Python 기반 | Python >=3.11, torch==2.14.1, transformers==5.19.0 |
python/pyproject.toml — 모델 설정·가중치·텐서 연산의 경계 |
| HTTP·검증 | fastapi, pydantic, msgspec, uvicorn>=0.51 |
동일 의존성 파일 — FastAPI·Pydantic·msgspec은 버전 고정 없음. 요청 구조와 서버 실행을 분리 |
| 프로세스 통신 | pyzmq>=25.1.2, uvloop, orjson |
python/sglang/srt/entrypoints/engine.py — IPC·비동기 대기·직렬화 |
| 계산 가속 | flashinfer_python[cu13]==0.7.2, sglang-kernel==0.4.10, tilelang==0.1.12 |
python/pyproject.toml — 모델 코드와 장치별 연산 구현의 분리 |
| 구조화 생성 | xgrammar==0.2.7, outlines==0.1.11, llguidance>=1.7.6,<2.0.0 |
python/sglang/srt/constrained/ — 출력 문법에 맞는 다음 토큰 후보 제한 |
| 클라이언트·계측 | openai==2.8.0, prometheus-client>=0.20.0 |
클라이언트 호환성과 서버 관측은 GPU 연산과 별도의 책임 |
확산 모델용 선택 의존성 diffusion에는 diffusers==0.37.0, cache-dit==1.5.1, imageio==2.36.0 등이 선언되어 있습니다. 선택 의존성을 나누면 언어 모델 서버와 영상 생성 서버에 필요한 패키지 범위를 구분할 수 있습니다.
논문의 “프론트엔드 언어”는 브라우저 화면을 뜻하지 않습니다. python/sglang/lang/api.py의 function()·gen()은 생성 프로그램을 표현하는 Python API입니다. python/sglang/lang/ir.py는 그 표현을 담고, python/sglang/lang/interpreter.py는 실행을 담당합니다.
실제 브라우저 UI도 있습니다. python/sglang/multimodal_gen/apps/realtime_webui/의 index.html·styles.css·app.js는 확산 모델의 실시간 영상 데모입니다. 별도 package.json이나 npm 빌드가 없는 정적 구성이고, new WebSocket(...)·Canvas·디코더 작업자를 사용합니다. React나 Vue 기반의 공통 관리 화면으로 설명하면 부정확합니다.
웹 UI README는 원시 RGB 프레임 렌더링을 설명하지만, app.js의 DEFAULT_PREVIEW_OUTPUT_FORMAT은 "webp"이며 HTML에도 WebP·JPEG·원시 RGB 선택지가 있습니다. 설명의 기본 사례와 현재 기본 설정이 다를 수 있음을 보여 주는 작은 예입니다.
| 부품 | 선언값 | 역할 |
|---|---|---|
rust/ | edition 2024, pyo3 0.29.0, tonic 0.14 | Python 확장·API 타입·gRPC·대체 서버. 해당 Cargo workspace에서 버전을 공유 |
sgl-router/ | edition 2021, axum 0.8, tokio 1.42, tonic 0.14.6 | SGLang 워커 앞의 라우팅과 KV 상태 활용. 의존성은 Cargo 버전 요구식 |
sgl-model-gateway/ | 버전 0.3.2, axum 0.8.6, tokio 1.42.0 | 별도 Rust workspace인 게이트웨이. 단일 엔진 내부의 배치 스케줄러와 구분 |
| Go 바인딩 | go 1.24.0, toolchain go1.24.10, gRPC v1.77.0 | sgl-model-gateway/bindings/golang/go.mod — 서버 본체 언어가 아니라 SDK 영역 |
Python의 ==와 Cargo의 version = "0.8.6"는 같은 의미가 아닙니다. 후자는 보통 호환 가능한 버전 범위를 허용합니다. 버전 문자열을 읽을 때는 숫자뿐 아니라 고정·하한·호환 범위를 나타내는 연산자까지 함께 봐야 합니다.
docker/Dockerfile의 기본값은 CUDA 13.0.3, Ubuntu 24.04, Python 3.12입니다. 기반 이미지와 실행 환경을 함께 고정하는 이유를 공부할 수 있습니다.python/sglang/kernels/aot/pyproject.toml은 scikit-build-core>=0.10, 같은 폴더의 CMakeLists.txt는 CMake 3.26 이상과 C++ 20을 선언합니다.python/setup.py는 Cargo 메타데이터의 python-module 선언을 찾아 PyO3 확장을 구성합니다. 확장마다 수작업으로 Python 빌드 목록을 늘리지 않는 방식입니다..github/workflows/pr-test.yml은 변경 범위와 실행 단계를 나누며, 예약 실행은 UTC 11:00·23:00입니다. CPU와 여러 GPU 규모의 검증 경로가 구분됩니다.docker/Dockerfile은 SGL_KERNEL_VERSION=0.4.9를 기본 빌드 인자로 두고, 설치 단계에서 그 값을 사용합니다. 반면 python/pyproject.toml과 AOT 패키지 선언은 0.4.10입니다. 빌드 인자 재정의나 후속 설치에 따라 결과가 달라질 수 있으므로, 최종 이미지가 반드시 어느 버전이라고 이 두 줄만으로 단정해서는 안 됩니다.
sglang serve의 LLM 경로는 python/sglang/cli/serve.py의 _run_llm()에서 python/sglang/launch_server.py의 run_server()로 이어집니다. 기본 HTTP 실행은 python/sglang/srt/entrypoints/http_server.py의 launch_server()가 담당합니다. 아래는 단일 엔진의 기본 Python 경로를 단순화한 구조입니다.
기본 구성에서는 HTTP 서버와 TokenizerManager가 주 프로세스에 있고, 스케줄러와 디토크나이저가 별도 프로세스입니다. Engine._launch_subprocesses()가 이 구성을 준비합니다. 다중 토크나이저·분산 병렬·Rust 서버 옵션에서는 프로세스 구성이 달라지므로 이 그림을 모든 실행 모드에 그대로 적용하면 안 됩니다.
특히 python/sglang/srt/environ.py의 SGLANG_RUST_SERVER 기본값은 False입니다. 켜면 launch_server()가 Python HTTP 경로 대신 Rust의 API·토크나이저·디토크나이저 경로를 사용합니다. Rust 디렉토리가 있다는 사실과 기본 서버가 Rust라는 주장은 구별해야 합니다.
python/sglang/srt/entrypoints/http_server.py의 generate_request()는 요청 계약을 검사한 뒤 serve_generate_request()로 넘깁니다. 스트리밍 요청에서는 TokenizerManager.generate_request()의 결과를 비동기 반복으로 받아 data: ...\n\n 형태의 SSE 조각으로 보냅니다. HTTP 함수가 GPU 연산을 직접 구현하지 않습니다.
# python/sglang/srt/entrypoints/http_server.py
# serve_generate_request()의 스트리밍 핵심 부분을 발췌
async for out in _global_state.tokenizer_manager.generate_request(
obj, request
):
yield b"data: " + dumps_json(out) + b"\n\n"
python/sglang/srt/managers/tokenizer_manager.py의 같은 이름 함수는 배치 인자를 정규화하고, 요청 ID별 상태를 만들고, _tokenize_one_request()·_send_one_request()·_wait_one_response()를 연결합니다. 처리 중 실패하면 _release_req_states_on_failure()로 아직 전달하지 않은 상태를 지우거나 전달된 요청의 중단을 요청합니다.
이 정리는 성능 최적화만큼 중요합니다. HTTP 연결이 끊겼는데 요청 상태나 GPU 메모리 참조가 계속 남으면 서버는 오래 버티지 못합니다. “정상 응답을 만드는 코드”와 “중간에 실패했을 때 자원을 돌려주는 코드”를 함께 읽는 습관을 배울 수 있습니다.
await는 결과를 기다리는 동안 이벤트 루프가 다른 일을 처리할 기회를 줍니다. 별도 프로세스는 실행 공간 자체를 나눕니다. SGLang은 두 방법을 함께 사용합니다. async를 붙인다고 GPU 계산이 자동으로 빨라지거나 CPU 작업이 자동으로 병렬화되는 것은 아닙니다.먼저 원리를 읽기 좋은 python/sglang/srt/mem_cache/radix_cache.py를 봅시다. RadixKey는 토큰열과 추가 구분 정보를 담습니다. RadixCache.match_prefix()는 가장 길게 일치하는 접두어를 찾고, insert()는 토큰열에 대응하는 KV 슬롯 정보를 트리에 넣습니다.
압축된 한 노드의 중간에서 일치가 끝나면 _match_prefix_helper()가 _split_node()로 경계를 만듭니다. 문자열 한 글자마다 노드를 만드는 단순 트라이보다 긴 토큰 구간을 한 노드로 묶을 수 있습니다. 실제 키는 페이지 크기에 맞춰 정렬되므로 재사용 길이가 입력 길이와 항상 같지는 않습니다.
사용 중인 KV를 지우면 다음 생성 계산이 잘못된 메모리를 읽습니다. 그래서 inc_lock_ref()·dec_lock_ref()는 경로의 참조 수를 갱신하고, evict()는 퇴거 가능한 잎에서 정책 우선순위에 따라 슬롯을 반환합니다. 여기의 lock_ref는 “누군가 쓰는 캐시를 보호하는 참조 수”이며 일반적인 스레드 뮤텍스와는 역할이 다릅니다.
python/sglang/srt/mem_cache/registry.py의 default_radix_cache_factory()는 일반 경로에서 create_unified_radix_cache()를 호출합니다. 순수 슬라이딩 윈도·외부 캐시 등에는 별도 분기가 있습니다. 따라서 위의 RadixCache만 읽고 현재 기본 엔진의 구현을 모두 설명했다고 보면 안 됩니다.
python/sglang/srt/mem_cache/unified_radix_cache.py의 UnifiedRadixCache는 전체 어텐션·슬라이딩 윈도·Mamba 상태 등의 컴포넌트와 트리 코어를 조합합니다. match_prefix()는 세션 경로 또는 트리 조회로, insert()는 트리 코어의 단계별 작업과 메모리 관련 작업으로 이어집니다. 작은 구현에서 원리를 이해한 뒤 이 조합 계층으로 올라가는 순서가 좋습니다.
접두어가 같아도 무조건 공유하지는 않습니다. RadixKey.extra_key와 GenerateReqInput.cache_salt 같은 구분 정보가 있습니다. LoRA나 캐시 네임스페이스가 다른 요청을 섞지 않기 위한 경계입니다. 공통 지시문을 앞에 둔다는 최적화도 토큰열과 이 구분 조건이 맞아야 효과가 납니다.
python/sglang/srt/managers/schedule_policy.py의 SchedulePolicy.calc_priority()는 대기열 정렬을 맡습니다. 도착 순서인 fcfs, 접두어 일치 길이를 활용하는 lpm, 대기 중 증가한 prefill 처리 토큰 수와 미계산 입력 토큰 수를 고려하는 hrrn, 미계산 prefill이 짧은 순서인 shortest-prefill-first 등으로 분기합니다.
기본 정책은 fcfs입니다. 근거는 python/sglang/srt/arg_groups/fields/schedule.py의 schedule_policy = "fcfs" 선언입니다. 캐시를 가진 서버라는 이유로 접두어가 긴 요청을 항상 먼저 실행한다고 설명해서는 안 됩니다. 우선순위 옵션이나 모델별 해석 과정도 실제 동작에 영향을 줍니다.
캐시 구현 선택에도 같은 발상이 있습니다. register_radix_cache_backend()는 이름과 생성 함수를 등록하고, create_tree_cache()는 선택한 이름에 맞는 팩터리를 부릅니다. 이름이 없으면 기본 선택 체인을 사용하며, 잘못된 이름은 오류로 처리합니다.
# registry.py의 구조를 학습용으로 축약한 의사코드
if backend_name:
factory = get_radix_cache_factory(backend_name)
if factory is None:
raise ValueError("등록되지 않은 캐시 구현")
cache = factory(context)
else:
cache = default_radix_cache_factory(context)
스케줄러가 원하는 일과 캐시가 그 일을 수행하는 방법을 분리하면, 호출부 전체를 바꾸지 않고 구현을 확장할 수 있습니다. 다만 같은 인터페이스라도 지원 기능이 다를 수 있어 create_tree_cache()에는 세션 캐시나 호스트 버퍼 모드의 호환성 검사도 들어 있습니다.
Scheduler.event_loop_normal()은 요청 수신, 다음 배치 선택, 실행, 결과 처리 순서로 읽으면 됩니다. event_loop_overlap()은 결과 대기열을 두고 현재 배치를 실행하면서 이전 배치 결과를 처리하는 구조입니다. CPU의 준비 시간과 GPU의 계산 시간이 직렬로 모두 더해지는 구간을 줄이려는 설계입니다.
겹친다고 동기화를 없애지는 않습니다. _apply_war_barrier()는 다음 스케줄링의 공유 버퍼 쓰기가 이전 GPU 읽기를 앞지르지 않도록 이벤트 또는 스트림 대기를 적용합니다. 문법 제약 등으로 직전 결과가 필요하면 is_disable_overlap_for_batch()가 배치별 예외를 판단합니다.
모델 실행은 python/sglang/srt/model_executor/model_runner.py의 ModelRunner.forward()로 이어집니다. 여기에는 실행 경로, 프로파일링, 분산 모델 관련 처리가 모입니다. HTTP부터 모든 일을 한 함수에 넣지 않고 실행 계층으로 내려보내는 이유를 확인할 수 있습니다.
프린터가 한 장을 인쇄하는 동안 컴퓨터가 다음 장을 준비하는 것과 비슷합니다. 하지만 첫 장의 결과를 읽어 다음 장의 내용을 정해야 한다면 기다려야 합니다. 겹침 실행의 어려움은 “동시에 한다”보다 어떤 데이터가 언제 준비되는지 지키는 것에 있습니다.
| 서로 헷갈리는 위치 | 구별하는 질문 |
|---|---|
lang/ · srt/ | 생성 프로그램을 표현하는 API인가, 실제 요청을 실행하는 런타임인가? |
models/ · layers/ | 모델 전체의 연결 구조인가, 여러 모델이 쓸 수 있는 연산 부품인가? |
mem_cache/ · kernels/ | 무엇을 보관하고 언제 돌려줄지 결정하는가, 실제 장치 연산을 수행하는가? |
| 엔진 · 라우터 | 한 엔진 안에서 요청을 묶는가, 여러 엔진 중 어디로 보낼지 고르는가? |
test/ · benchmark/ | 동작이 맞는지 검증하는가, 어떤 조건에서 얼마나 빠른지 측정하는가? |
python/sglang/srt/entrypoints/http_server.py에서 generate_request()를 찾습니다. 전체 서버 파일을 처음부터 읽을 필요는 없습니다.python/sglang/srt/managers/tokenizer_manager.py에서 요청이 토큰화되고 전송되는 지점을 찾습니다.python/sglang/srt/managers/scheduler.py에서 event_loop_normal()과 다음 배치 선택만 먼저 읽습니다.python/sglang/srt/mem_cache/registry.py에서 캐시 선택을 확인한 뒤 python/sglang/srt/mem_cache/radix_cache.py와 python/sglang/srt/mem_cache/unified_radix_cache.py를 비교합니다.커밋 기준 python/sglang/srt/managers/scheduler.py는 6,291줄, python/sglang/srt/managers/tokenizer_manager.py는 4,122줄입니다. 파일이 길다는 사실은 읽기를 포기할 이유보다 한 요청의 호출 경로를 좁혀야 할 이유에 가깝습니다. 함수별로 입력·출력·바꾸는 상태를 세 칸으로 기록해 보세요.
TokenizerManager.generate_request()는 비동기 생성기입니다. 한 번에 최종 문자열만 반환하는 함수와 달리 결과 조각을 여러 번 내보낼 수 있습니다. HTTP 계층은 이 흐름을 SSE로 감쌉니다.
읽을 것: python/sglang/srt/managers/tokenizer_manager.py의 rid_to_state, _wait_one_response(), 실패 시 정리 경로.
작은 실습: 별도 Python 파일에서 지연 시간을 두고 세 단어를 내보내는 비동기 생성기를 만들어 보세요. 소비를 중간에 멈췄을 때 finally가 자원을 정리하는지도 관찰합니다.
RadixCache의 값은 답변이 아니라 KV 슬롯 인덱스입니다. 검색용 자료구조와 실제 텐서 저장소를 분리하면, 트리 구조를 바꾸는 일과 GPU 버퍼를 다루는 일을 구분할 수 있습니다.
읽을 것: python/sglang/srt/mem_cache/radix_cache.py의 _split_node()·inc_lock_ref()·evict(), test/registered/unit/mem_cache/test_radix_cache_unit.py의 페이지 정렬·참조·퇴거 사례.
작은 실습: 리스트를 슬롯 풀로 보고 “빌린 번호 집합”과 “사용 가능한 번호 집합”을 관리해 보세요. 사용 중인 번호를 퇴거하려 하면 거부하도록 만듭니다.
입력 1,000토큰 중 900토큰이 캐시에 있다면 새로 계산할 부분은 대략 100토큰입니다. 입력 길이만 보고 정렬하면 이 차이를 놓칩니다. 하지만 짧은 일만 먼저 처리하면 긴 일이 계속 밀릴 수 있습니다.
읽을 것: python/sglang/srt/managers/schedule_policy.py의 _sort_by_hrrn()·_sort_by_shortest_prefill(), test/registered/unit/managers/test_schedule_policy.py의 대기량 증가 사례.
작은 실습: 도착 시점·미계산 토큰 수가 다른 요청 다섯 개를 표로 만들고 FCFS·짧은 작업 우선·대기 보정 정책의 순서를 손으로 계산합니다.
ModelRunner.forward()가 받는 것은 HTTP JSON이 아니라 실행에 필요한 배치 표현입니다. 이후 모델·어텐션 백엔드·커널로 내려갑니다. 텐서의 shape, dtype, device가 다음 연산이 요구하는 조건과 맞는지 확인해야 합니다.
읽을 것: python/sglang/srt/model_executor/forward_batch_info.py, python/sglang/srt/model_executor/model_runner.py, python/sglang/kernels/. 처음에는 CUDA 구현 전체보다 함수 입력 텐서의 모양부터 기록합니다.
작은 실습: 길이가 다른 세 입력을 고정 길이로 패딩할 때 낭비되는 토큰 수와, 실제 토큰만 모았을 때의 수를 계산해 보세요. 커널을 작성하기 전에도 배치 설계의 효과를 이해할 수 있습니다.
rust/Cargo.toml의 workspace는 공통 의존성을 공유하고, python/setup.py는 확장 모듈 메타데이터를 탐색합니다. proto/와 API 타입 crate는 통신하는 양쪽이 메시지 구조를 공유하는 지점입니다.
작은 실습: Rust 문법을 모두 배우기 전에 Python 모듈 이름·Cargo 패키지 이름·공개 API 타입을 연결한 표를 만들어 보세요. 라우터의 비동기 작업과 GPU 커널 실행을 같은 종류의 병렬성으로 오해하지 않는 것이 첫 목표입니다.
이 저장소에서 배울 가장 실용적인 습관은, 함수가 무엇을 계산하는지와 함께 어떤 상태를 소유하고 언제 반환하는지를 묻는 것입니다. 이 질문은 추론 서버뿐 아니라 DB 연결 풀, 작업 대기열, 이미지 처리 서버에도 그대로 적용됩니다.
| 목표 | 환경을 잡는 기준 | 주의할 점 |
|---|---|---|
| 소스·기초 과제 | 일반 PC, 텍스트 편집기, 작은 예제용 Python | GPU 없이 요청 흐름·접두어·스케줄링 원리 학습 가능 |
| 작은 LLM 서버 | Linux + 지원 GPU, 짧은 문맥·낮은 동시성부터 시작 | 0.6B 모델의 BF16 가중치는 계산상 약 1.2GB지만 전체 사용량은 더 큼 |
| 7B~8B급 탐색 | 24GiB급 GPU를 검토하더라도 가중치·KV·버퍼 합을 먼저 계산 | 긴 문맥·동시 요청·추가 모델은 여유 메모리를 빠르게 소모 |
| 다중 GPU 실험 | 동일 계열 GPU, 지원 통신 경로, 충분한 호스트 메모리 | TP·PP 크기와 장치 간 대역폭이 지연 시간에 영향 |
| 다중 노드·P/D | 고속 네트워크와 선택한 KV 전송 백엔드의 요구사항 확인 | GPU가 빨라도 KV 전송이나 통신에서 병목 가능 |
위 표는 모든 모델의 공식 최소 사양표가 아닙니다. 필요 용량은 모델 구조·정밀도·문맥·동시성·백엔드에 따라 달라집니다. CPU RAM과 디스크에도 모델 다운로드·로딩·캐시용 공간이 필요합니다. GPU 메모리에 딱 맞는 크기만 보고 호스트 자원을 결정하지 마세요.
# 가중치의 단순 근사: 양자화 메타데이터·작업 버퍼는 제외
가중치 메모리 ≈ 파라미터 수 × 파라미터당 바이트
8B × BF16 2바이트 = 16,000,000,000바이트 ≈ 14.9GiB
# 일반적인 디코더 어텐션의 KV 캐시 근사
KV 바이트 ≈ 2 × 층 수 × KV 헤드 수 × 헤드 차원
× 보관 토큰 수 × 원소당 바이트
# 층 32, KV 헤드 8, 헤드 차원 128, BF16 예시
2 × 32 × 8 × 128 × 8192 × 2 = 1GiB / 요청
같은 조건의 독립 요청 8개 ≈ 8GiB
총 GPU 사용량 ≈ 가중치 + KV 풀 + 활성값 + 그래프·작업 버퍼
이 KV 식은 모든 모델에 통하는 식이 아닙니다. GQA의 KV 헤드 수, 슬라이딩 윈도, MLA의 압축 상태, Mamba의 상태 구조가 다릅니다. 공유 접두어가 있으면 중복을 줄일 수 있지만, 최악의 예산은 공유되지 않는 입력도 고려해야 합니다.
모델 가중치가 GPU에 들어간다는 사실만으로, 원하는 문맥 길이와 동시 요청 수까지 감당할 수 있다는 결론은 나오지 않습니다. 캐시를 크게 잡으면 더 많은 토큰을 보관할 수 있지만, 실행 중 임시 텐서가 쓸 공간도 남겨야 합니다.
python/sglang/srt/arg_groups/fields/schedule.py에서 mem_fraction_static과 chunked_prefill_size는 둘 다 None으로 시작합니다. python/sglang/srt/arg_groups/memory_hook.py의 handle_gpu_memory_settings()가 장치 메모리와 다른 설정을 보고 값을 정합니다.
| GPU 메모리 구간 | chunked prefill 크기 |
|---|---|
| 20GiB 미만 | 사용자 지정이 없으면 2,048토큰 |
| 20~35GiB | 사용자 지정이 없으면 4,096토큰 |
| 60~90GiB | 사용자 지정이 없으면 8,192토큰 |
구간은 하한을 포함하고 상한은 포함하지 않으며, 해당 함수의 일부 분기를 GiB로 표현했습니다. 모델·플랫폼별 후속 조정과 명시적 옵션이 있으므로 전체 실행의 고정값으로 외우지 마세요. mem_fraction_static은 가중치와 KV 풀에 쓰는 정적 메모리의 비율이며, KV 캐시만의 비율이 아닙니다.
README는 NVIDIA·AMD·Intel GPU/CPU·Ascend·Apple Silicon 등을 설명합니다. 실제 저장소에도 docker/rocm.Dockerfile·docker/xpu.Dockerfile·docker/npu.Dockerfile·docker/xeon.Dockerfile과 장치별 백엔드가 있습니다. 그러나 모델·연산·옵션의 지원 범위까지 모두 같다고 볼 수는 없습니다.
TPU는 README에서 SGLang-TorchTPU와 SGL-JAX라는 별도 프로젝트 경로도 안내합니다. NVIDIA용 python/pyproject.toml의 CUDA 의존성을 모든 플랫폼에 그대로 적용하지 말고, 공식 플랫폼 가이드와 모델별 실행 예제를 함께 확인해야 합니다. 기본 Dockerfile의 CUDA 13.0.3도 모든 하드웨어의 공통 최소 버전은 아닙니다.
목표: /generate의 JSON이 모델 실행까지 도달하는 경로를 이해합니다.
GenerateReqInput에서 text·sampling_params·stream의 타입을 적습니다.generate_request() → 토큰화 → 스케줄러 → run_batch() → 디토크나이저를 화살표로 연결합니다.완료 기준: HTTP 서버가 모델의 모든 토큰을 직접 계산하지 않는 이유와 요청 ID가 필요한 이유를 자신의 말로 설명할 수 있습니다.
목표: 토큰열의 공통 앞부분과 중간 부분의 일치가 다른 문제임을 이해합니다. 아래는 SGLang 구현을 대체하는 코드가 아니라 원리만 보여 주는 독립 예제입니다.
def prefix_length(a, b):
n = 0
while n < min(len(a), len(b)) and a[n] == b[n]:
n += 1
return n
previous = [10, 20, 30, 40]
request = [10, 20, 30, 99, 88]
matched = prefix_length(previous, request)
uncached = len(request) - matched
assert matched == 3
assert uncached == 2
assert prefix_length([1, 2], [0, 1, 2]) == 0
확장: 요청 다섯 개를 만들고, 앞서 계산한 모든 요청 중 최대 접두어 길이를 찾으세요. 단순 선형 탐색을 접두어 트리로 바꿀 때 필요한 노드 정보를 적습니다. 토큰을 문자열 글자 수로 바꿔 계산하면 어떤 오차가 생기는지도 생각해 봅니다.
완료 기준: 공통 접두어가 늘면 새 prefill 작업은 줄어들 수 있지만, 새 출력 토큰 계산까지 전부 사라지지는 않는다고 설명할 수 있습니다.
전제: Linux·NVIDIA 환경에 호환되는 SGLang과 드라이버가 준비되어 있고, 작은 모델과 실행 버퍼를 담을 GPU 여유가 있어야 합니다. 모델은 Hugging Face에서 내려받으므로 네트워크와 디스크 공간도 필요합니다. 아래는 단일 GPU·TP 1·단일 노드 예제이며, 다른 플랫폼은 전용 가이드를 따릅니다.
# 전용 실습 환경의 첫 터미널
sglang serve \
--model-path Qwen/Qwen3-0.6B \
--host 127.0.0.1 --port 30000 \
--context-length 4096 \
--max-running-requests 1 \
--enable-metrics
# 서버가 준비된 뒤 두 번째 터미널
curl -sS http://127.0.0.1:30000/generate \
-H 'Content-Type: application/json' \
-d '{
"text": "배치 처리를 한 문장으로 설명해 줘.",
"sampling_params": {"temperature": 0, "max_new_tokens": 64},
"stream": false
}'
그다음 stream을 true로 바꾸고 curl -N으로 다시 요청합니다. 출력 조각이 응답 전체인지 차이만 담은 값인지도 관찰하세요. 네이티브 API의 스트림을 곧바로 OpenAI 응답 조각과 같은 모양이라고 가정하지 않습니다.
완료 기준: JSON 응답의 text와 meta_info를 구분하고, SSE가 여러 요청을 보내는 방식이 아니라 한 응답을 이어 받는 방식임을 설명할 수 있습니다.
목표: “두 번째가 빨랐다”를 설명 가능한 실험으로 바꿉니다. 긴 공통 지시문 뒤에 짧은 질문을 붙이고, 총 입력이 서버의 문맥 한도 안에 들도록 합니다.
| 실험군 | 바꾸는 조건 | 관찰 |
|---|---|---|
| A | 캐시가 비어 있는 상태의 첫 요청 | 초기화·워밍업과 prefill을 구분 |
| B | 같은 접두어, 다른 질문 | meta_info.cached_tokens와 첫 출력 지연 |
| C | 접두어 앞에 매번 다른 날짜·ID 추가 | 앞부분 변경이 일치 길이에 미치는 영향 |
| D | --disable-radix-cache를 준 별도 서버 실행 | 캐시 비활성 조건에서 같은 부하 비교 |
모델·정밀도·입력과 출력 토큰 수·동시성·워밍업 횟수를 고정하고 여러 번 반복합니다. 첫 출력 지연을 재려면 스트리밍의 첫 실제 출력 조각을 기록하고, 전체 응답 시간과 구분하세요. 페이지 정렬과 마지막 토큰 처리 때문에 캐시 토큰 수가 공통 입력 길이와 정확히 같지 않을 수 있습니다.
완료 기준: 중앙값·느린 꼬리 지연·캐시 토큰 수를 함께 적고, 입력 재사용 효과와 출력 생성 속도 변화를 구분한 비교표를 만듭니다.
목표: 긴 요청 하나와 계속 도착하는 짧은 요청을 넣고 FCFS·짧은 작업 우선·대기 보정 정책을 비교합니다. 처음에는 GPU 없이 한 토큰을 한 작업 단위로 계산하는 시뮬레이터면 충분합니다.
test/registered/unit/managers/test_schedule_policy.py의 오래 기다린 긴 요청이 앞서는 사례와 비교합니다.완료 기준: 단순 모형의 가정과 실제 엔진의 차이까지 적습니다. GPU의 배치 효율·통신·메모리 제약이 빠진 모형으로 실제 처리량을 예측했다고 주장하지 않습니다.
성능 실습의 결과물은 가장 빠른 숫자 하나가 아니라, 어떤 조건을 바꾸었을 때 무엇이 달라졌는지 설명하는 기록입니다. GPU가 없다면 과제 1·2·5만으로도 파이프라인·접두어 자료구조·공정성이라는 핵심 주제를 공부할 수 있습니다.
하루 45~60분 정도를 기준으로 한 읽기 중심 경로입니다. 모델 수학과 GPU 커널을 한 번에 끝내려 하지 말고, 각 주마다 설명 가능한 작은 산출물을 남깁니다. 이미 익숙한 주제는 건너뛰어도 되지만 마지막에는 요청 전체를 다시 연결해 보세요.
| 주차 | 배울 내용과 소스 | 손에 남길 결과물 |
|---|---|---|
| 1주차 | HTTP·JSON·타입·생성기. python/sglang/srt/managers/io_struct.py와 python/sglang/srt/entrypoints/http_server.py |
일반 응답과 SSE의 차이, 요청 필드 설명표, 과제 1 |
| 2주차 | asyncio·프로세스·IPC. TokenizerManager와 Engine._launch_subprocesses() |
프로세스별 책임도, 정상·취소·검증 실패 시퀀스 세 개 |
| 3주차 | 토큰화·prefill·decode·KV. python/sglang/srt/mem_cache/radix_cache.py에서 시작해 캐시 팩터리로 이동 |
접두어 기초 구현, 참조 수와 퇴거의 불변조건 목록 |
| 4주차 | 연속 배칭·정책·공정성. event_loop_normal()과 SchedulePolicy |
과제 5의 대기열 비교표, 처리량과 지연 시간의 상충 설명 |
| 5주차 | PyTorch 텐서·dtype·메모리·CUDA Graph. ForwardBatch와 ModelRunner |
작은 모델의 메모리 예산표. GPU가 있으면 과제 3·4의 관찰 기록 |
| 6주차 | Rust 경계·gRPC·다중 엔진·관측. rust/·sgl-router/·모니터링 예제 |
단일 엔진을 여러 대로 확장할 때 바뀌는 상태·통신·장애 경계 정리 |
학습의 종료 기준은 파일을 많이 읽는 것이 아니라, 요청 하나의 상태와 메모리가 어디로 이동하는지 설명할 수 있는 것입니다. 그 뒤에는 관심에 따라 캐시 알고리즘, 장치 커널, 분산 라우팅 중 한 갈래를 선택하면 됩니다.
전체 런타임이 너무 크다면 README가 소개하는 Mini-SGLang을 보조 교재로 삼을 수 있습니다. 작은 구현에서 배치·캐시의 뼈대를 익힌 뒤, 이 저장소에 추가된 취소·장치 차이·세션·전송·오류 처리가 각각 어떤 현실 문제를 다루는지 비교하세요.
| 용어 | 쉬운 설명 |
|---|---|
| 토큰 / 토큰 ID | 모델이 읽고 쓰는 조각과 그 번호입니다. 한 글자·한 단어와 항상 같지는 않습니다. |
| 토크나이저 | 문자열을 토큰 ID로 바꾸고 그 반대 변환도 제공하는 도구입니다. 채팅 템플릿도 실제 입력 토큰에 영향을 줍니다. |
| 추론 서빙 | 모델 계산을 여러 사용자가 요청할 수 있는 API 서비스로 운영하는 일입니다. |
| prefill | 입력 토큰들을 읽어 다음 생성에 쓸 상태를 준비하는 단계입니다. 긴 입력의 비용과 관련이 큽니다. |
| decode | 앞에서 만든 상태를 이용해 출력 토큰을 이어 생성하는 단계입니다. 기본적인 자기회귀 생성은 순서 의존성이 있습니다. |
| KV 캐시 | 어텐션에서 이미 계산한 Key·Value를 보관하는 저장소입니다. 답변 문자열을 저장하는 응답 캐시와 다릅니다. |
| Radix tree | 공통 접두어를 공유하고 연속 구간을 압축해 저장하는 트리입니다. 여기서는 토큰열에 대응하는 캐시 위치를 찾습니다. |
| RadixAttention | 접두어 공유를 활용해 요청 사이의 KV 재사용을 관리하는 SGLang의 대표 설계입니다. 단순히 어텐션 수식 하나의 이름으로만 이해하면 부족합니다. |
| 페이지·슬롯 | KV 메모리 풀을 나누어 관리하는 단위입니다. 논리적인 토큰 위치와 실제 저장 위치를 연결해야 합니다. |
| 참조 수 / lock_ref | 캐시를 누가 사용 중인지 세는 값입니다. 사용 중인 데이터를 퇴거 대상에서 보호하는 데 쓰입니다. |
| 퇴거 / LRU | 공간이 부족할 때 캐시를 내보내는 일과, 최근 사용에서 가장 멀어진 후보를 우선하는 정책입니다. 사용 중인 캐시는 별도로 보호합니다. |
| 연속 배칭 | 생성 과정에서 완료 요청을 빼고 새 요청을 받아 GPU 실행 묶음을 갱신하는 방식입니다. |
| chunked prefill | 긴 입력의 prefill을 여러 조각으로 나누는 방식입니다. 긴 작업과 다른 요청 사이의 시간 배분에 영향을 줍니다. |
| FCFS / LPM | 각각 도착 순서 우선과 긴 접두어 일치 활용 정책입니다. 기본 스케줄링 선언은 FCFS입니다. |
| HRRN | 기다린 양과 남은 작업량을 함께 고려하는 정책입니다. 이 구현에서는 누적 prefill 처리 토큰 수의 증가량과 미계산 입력 토큰 수를 활용합니다. |
| TTFT | 요청부터 첫 출력 토큰까지 걸리는 시간입니다. 대기열·prefill·통신의 영향을 함께 받을 수 있습니다. |
| ITL / 처리량 | 각각 출력 토큰 사이 간격과 단위 시간에 처리한 양입니다. 첫 응답이 빠른 것과 전체 처리량이 큰 것은 다른 목표입니다. |
| SSE | 한 HTTP 응답을 열어 둔 채 서버가 이벤트 조각을 이어 보내는 방식입니다. 양방향 WebSocket과 구분합니다. |
| ZMQ IPC | 이 엔진의 프로세스들이 요청·결과 메시지를 주고받는 통로입니다. GPU 간 집단 통신과는 다른 층입니다. |
| TP / PP | 각각 모델 텐서 계산을 여러 장치로 나누는 병렬화와, 모델의 층 단계를 장치별로 나누는 병렬화입니다. |
| DP / EP | 각각 요청·데이터를 복제된 실행 자원에 나누는 방식과, MoE의 전문가들을 장치에 나누는 방식입니다. 모델에 따라 조합이 달라집니다. |
| P/D 분리 | 입력을 읽는 prefill과 출력을 이어 쓰는 decode를 다른 실행 자원에 맡기는 구성입니다. 중간 KV 전송이 필요합니다. |
| 양자화 | 가중치·연산 등에 더 적은 비트를 쓰는 방식입니다. 메모리 절감과 정확도·커널 지원을 함께 검토해야 합니다. |
| CUDA Graph | 반복되는 GPU 실행 작업을 미리 기록해 재실행하는 기능입니다. 준비 비용과 추가 메모리, 지원 형태의 제약이 있습니다. |
| 투기적 디코딩 | 보조 방식이 미리 제안한 토큰을 주 모델이 검증하는 생성 가속 기법입니다. 수락률과 추가 자원에 따라 이득이 달라집니다. |
| 구조화 출력 | JSON Schema·문법 등에 맞게 생성 후보를 제한하는 기능입니다. 형식이 맞는 것과 답의 사실성이 맞는 것은 별개입니다. |
| LoRA / MoE | 각각 작은 추가 파라미터로 모델을 조정하는 방법과, 여러 전문가 중 일부를 선택해 계산하는 모델 구조입니다. |
| PyO3 / gRPC | 각각 Rust와 Python을 연결하는 바인딩 도구와, 정해진 메시지 타입으로 원격 함수를 호출하는 통신 체계입니다. |
| 관측 가능성 | 로그·메트릭·트레이스로 대기·실행·실패 원인을 구별하는 능력입니다. 빠르다는 주장에 설명 가능한 근거를 붙입니다. |
처음에는 모든 약어를 외울 필요가 없습니다. 입력 처리, 실행 순서, 저장 공간, 통신, 측정 중 어느 문제를 다루는 말인지 먼저 분류하면 소스를 읽을 때 길을 잃지 않습니다.
| 항목 | 내용 |
|---|---|
| 저장소 · 선정 | sgl-project/sglang · GitHub Trending Python Weekly #9 |
| 스타 / 포크 | 37,655 / 9,819 — 2026-10-23 GitHub 기준 |
| 주 언어 · 생성일 | Python · 2024-01-08 |
| 최신 릴리스 | v0.5.22 · 2026-10-19 |
| 최근 push | 2026-10-23 |
| 최근 커밋 | 611a703 · 2026-10-23 · [Chore] api: add fields to generate request (#45164) — 생성 요청 필드 추가 |
| 라이선스 | Apache-2.0 — 모델 가중치와 외부 의존성의 조건은 각각 별도 확인 |
| 추적 파일 | 11,225개 항목 — 커밋 기준 Git 추적 경로 수 |
| Python | 8,018파일 · 2,622,416줄 |
| Rust | 642파일 · 302,159줄 |
| CUDA | .cu + .cuh 318파일 · 126,933줄 |
| C / C++ | .c·.cc·.cpp·.h·.hpp 150파일 · 52,415줄 |
| 그 외 코드 | Shell 13,547줄 · Go 7,598줄 · JavaScript 5,555줄 · CSS 1,905줄 · HTML 295줄 |
집계 기준: 커밋 611a703의 Git 추적 파일을 확장자별로 묶은 물리적 줄 수입니다. 빈 줄·주석·테스트·생성 코드·해당 확장자의 서드파티 코드를 포함하고, 심볼릭 링크는 제외했습니다. JavaScript는 .js·.mjs·.cjs, Shell은 .sh를 집계했습니다. 다운로드되는 모델 가중치나 외부 패키지의 소스는 포함하지 않습니다. 따라서 사람이 작성한 구현 코드만의 크기나 언어별 작업량 비율로 해석하면 안 됩니다.
읽기 범위를 잡는 참고로, python/sglang/srt/의 Python·Rust·C/C++·CUDA·Go·JavaScript 해당 확장자 합계는 약 92만 줄이고 test/는 약 72만 줄입니다. 전체 크기를 목표로 삼기보다 4절의 요청 경로와 작은 테스트부터 읽는 편이 현실적입니다.
① 릴리스와 개발 HEAD는 다릅니다. 최신 릴리스 표기는 v0.5.22이고 소스 설명은 2026-10-23의 611a703 기준입니다. 설치한 릴리스에 모든 함수·기본값·의존성이 똑같이 있다고 가정하면 안 됩니다. 공식 문서의 최신 페이지도 이후에 바뀔 수 있습니다.
② 모든 장치·모델·옵션 조합이 같은 수준으로 지원되지는 않습니다. 플랫폼 목록은 모델별 검증표가 아닙니다. 기본 Dockerfile과 Python 의존성의 커널 버전 차이도 있으므로 실제 배포 구성의 버전을 확인해야 합니다.
③ 빠른 속도는 조건이 붙는 결과입니다. 접두어 반복률·배치 크기·정밀도·입출력 길이·장치·워밍업에 따라 결과가 달라집니다. 캐시는 주로 반복 입력 처리의 비용을 줄이며, 새 출력 전체를 미리 계산해 주지는 않습니다.
④ 엔진 설치가 운영 구성을 완성하지는 않습니다. 외부 공개 서비스에는 인증·접근 제어·동시성 제한·타임아웃·로그 관리가 필요합니다. 특히 trust_remote_code처럼 모델 저장소의 코드를 허용하는 선택은 실행할 코드를 검토한 뒤 적용해야 합니다. 실습의 루프백 주소를 그대로 유지한 채 요청 흐름부터 익히세요.