추론·서빙 엔진
1ed2de1.
TokenSpeed는 공개 LLM의 가중치를 불러와 여러 사용자의 생성 요청을 처리하는 추론·서빙 엔진입니다. 모델의 지식을 새로 학습시키는 것이 주목적은 아닙니다. 같은 모델로도 요청을 묶는 방법, 캐시를 보관하는 방법, GPU 연산을 호출하는 방법에 따라 응답 속도와 처리량이 달라집니다.
이 저장소의 핵심은 모델 계산을 빠르게 만드는 일과, 그 계산이 멈추지 않도록 준비하는 일을 함께 설계한다는 점입니다. Python만 읽어도 입구를 이해할 수 있지만, 중요한 결정은 C++과 GPU용 DSL까지 이어집니다.
GPU는 요리사입니다. 주문이 도착할 때마다 재료를 처음부터 꺼내고, 한 접시가 완성될 때까지 다음 주문표를 만들지 않으면 요리사가 쉽니다. 스케줄러는 다음 조리 순서를 정하고, 캐시는 이미 손질한 재료를 보관하며, 커널은 실제 조리 동작을 수행합니다.
그런데 조리 중인 재료를 다른 주문에 넘겨 버리면 음식이 뒤섞입니다. 그래서 빨리 배정하는 능력과 안전하게 회수하는 능력이 같은 정도로 중요합니다.
| 구간 | 무엇을 하나 | 주요 관심사 |
|---|---|---|
| Prefill | 입력 프롬프트의 토큰들을 처리하고 이후 계산에 필요한 상태를 만든다. | 긴 입력의 연산량, 첫 토큰 대기, 다른 요청과의 간섭 |
| Decode | 앞서 만든 상태를 이용해 다음 토큰을 반복 생성한다. | 토큰 사이 대기, 캐시 읽기, 작은 연산의 호출 비용 |
python/pyproject.toml은 tokenspeed와 ts를 같은 CLI 진입점에 등록합니다.
python/tokenspeed/cli/serve_smg.py의 설명에 따르면 ts serve는 SMG 게이트웨이와 gRPC 엔진을 함께 기동하고,
--headless는 외부 프론트엔드가 연결할 엔진만 기동합니다.
# 공식 시작 가이드의 실행 형태를 읽는 예시
tokenspeed serve openai/gpt-oss-20b \
--host 127.0.0.1 \
--port 8000 \
--tensor-parallel-size 1
# 모델 이름 → 가중치·토크나이저 선택
# host·port → 서비스 접점, tensor-parallel-size → 연산 분할 폭
이 명령은 사용 형태를 보여 주는 예시입니다. 실제 기동에는 지원 가속기와 설치 환경, 모델 파일이 필요합니다.
먼저 AsyncLLM.generate_request()가 요청을 받고, 스케줄러 계획과 GPU 실행을 거쳐 응답을 내보내는 흐름부터 이해해 봅시다.
글을 읽은 뒤에는 요청 상태, 실행 계획, GPU 작업, 완료 통지가 각각 어디에 있는지 설명할 수 있어야 합니다. 이 구분은 추론 엔진뿐 아니라 작업 큐, 파일 업로드, 영상 변환 서버에도 적용됩니다.
코딩 에이전트는 코드와 도구 결과를 읽고, 답을 생성하고, 다시 도구 결과를 받는 과정을 반복합니다. 대화가 길어질수록 입력이 커지고 공통 접두부가 늘어나므로 캐시 재사용과 응답 지연이 중요해집니다. TokenSpeed의 README는 이런 에이전트 작업 부하를 주된 대상으로 밝힙니다.
저장소에도 이를 구체화한 실험이 있습니다.
test/agentic_benchmark/Qwen3.8-Flash-Next/README.md는 첫 프롬프트를 최대 50,000토큰,
이후 사용자 입력 증가를 최대 800토큰으로 조정하고, 동시성 1·2·4·8·16·32에서 각각 측정합니다.
각 턴의 출력은 ignore_eos=true로 500토큰을 생성하도록 설정합니다.
이 실험은 대화를 재생하는 서빙 벤치마크입니다. 문서도 생성된 도구 호출을 실제 실행하지 않는다고 명시합니다. 따라서 이 수치를 에이전트의 문제 해결률이나 실제 개발 업무의 완료 시간으로 해석하면 안 됩니다.
| 관찰 | 근거 | 어떻게 읽을까 |
|---|---|---|
| 관심 증가 | 2026-10-23 GitHub 기준 스타 2,772·포크 371, Python 주간 트렌딩 #15 | 관심의 신호이며 운영 안정성의 점수는 아니다. |
| 최신 모델 최적화 | README의 10월 소식: EAGLE-4, Beam, MLA 2, Qwen3.8, AMD Kimi K3 | 프로젝트의 공개 발표이며 개별 지원 조합은 별도 확인한다. |
| 세밀한 성능 작업 | 커밋 1ed2de1 제목은 Kimi K3의 KDA Q·K·V 복사 제거와 FP8 양자화 타일 조정을 설명 | 모델 추가뿐 아니라 데이터 이동·실행 단위까지 다룬다. |
| 검증 인프라 | C++ 수명주기 테스트, GPU별 단위 테스트, 정확도·성능 CI 명세 | 어떤 조건을 검증하는지 소스로 읽을 수 있다. 통과 이력과는 별개다. |
이런 개발 방향은 트렌딩을 이해하는 배경입니다. 각 요인이 스타 증가에 얼마나 영향을 주었는지는 공개 코드만으로 알 수 없습니다. README의 “빛의 속도”라는 표현도 물리적 보장이 아니라 프로젝트의 성능 지향을 나타내는 문구입니다.
| 도구 | 공식 자료에서 확인할 특징 | TokenSpeed에서 배울 차별점 |
|---|---|---|
| vLLM | PagedAttention, 연속 배칭, 접두부 캐시, 다양한 모델·하드웨어 지원 | C++ 상태 머신과 Python 실행의 경계를 좁은 인터페이스로 읽기 좋다. |
| TensorRT-LLM | NVIDIA 중심의 최적화, 진행 중 배칭, 청크 단위 입력 처리, KV 캐시 관리 | 공용 커널 API 아래 NVIDIA·AMD·NPU 구현을 연결하는 방식을 볼 수 있다. |
| TokenSpeed | 에이전트 작업 지향, 요청·캐시 수명 관리, 커널 등록·선택 계층 | 스케줄링 정확성과 GPU 최적화를 한 요청 경로에서 함께 공부할 수 있다. |
비교 근거는 vLLM 공식 개요와 TensorRT-LLM 공식 Attention 문서입니다. 배칭·양자화·캐시가 TokenSpeed만의 기능인 것은 아닙니다. 위 표는 설계와 학습 관점의 비교입니다.
추론 엔진의 우열은 이름표가 아니라 같은 모델·정밀도·하드웨어·입출력 길이·동시성에서 비교해야 합니다. README의 “TensorRT-LLM 수준 성능, vLLM 수준 사용성”은 프로젝트가 내세우는 목표로 읽어야 합니다.
예를 들어 test/ci/perf/qwen3.8-flash-next-nvfp4-tp4ep4-evalscope-agentic.yaml은
사용자당 속도와 GPU당 출력 처리량의 단위를 구분합니다.
perf_reference는 기존 측정 결과를 바탕으로 정한 CI 비교 기준값입니다.
| 계층 | 실제 선언 | 역할과 근거 파일 |
|---|---|---|
| 주 패키지 | tokenspeed 0.1.1, Python >=3.11 | python/pyproject.toml의 프로젝트 버전·실행 환경 |
| 모델 생태계 | transformers==5.12.0, torch, torchvision | 주 패키지는 Torch 버전을 고정하지 않는다. 모델 설정·텐서 연산에 사용 |
| 비동기·전송 | uvloop, pyzmq, msgspec, orjson | 이벤트 루프, 프로세스 간 메시지, 직렬화. 여기 나열한 항목은 주 파일에서 버전 미고정 |
| 웹·제어 | fastapi, uvicorn, aiohttp | HTTP·제어 경로용 의존성. 주 서빙 명령의 게이트웨이는 SMG가 담당 |
| 게이트웨이 | tokenspeed-smg==1.13.0.post20261019 | python/tokenspeed/cli/serve_smg.py가 외부 패키지의 게이트웨이와 엔진을 조합 |
| gRPC 연결 | proto 0.4.27.post20261019, servicer 0.14.0.post20261019 | tokenspeed-smg-grpc-proto·tokenspeed-smg-grpc-servicer의 정확한 핀 |
| 보조 기능 | xgrammar==0.2.8, torch_memory_saver==0.0.9.post1 | 구조화 출력, 선택적으로 켜는 메모리 절약 경로 |
openai>=2.24.1도 의존성에 있습니다. 이것이 OpenAI 원격 API 호출을 필수로 뜻하지는 않습니다.
이 프로젝트의 중심은 모델 가중치를 직접 실행하는 서빙입니다.
패키지 이름만으로 데이터 흐름을 추정하지 말고 호출부를 따라가야 합니다.
| 부품 | 버전·도구 | 읽을 파일 |
|---|---|---|
| 스케줄러 | 0.1.42, Python >=3.10, C++20 | tokenspeed-scheduler/pyproject.toml·tokenspeed-scheduler/CMakeLists.txt |
| 바인딩·빌드 | nanobind>=2.13.0, scikit-build-core>=0.9.5, CMake >=3.22, Ninja >=1.11 | 스케줄러의 빌드 의존성. OpenSSL::Crypto도 링크 |
| 커널 공용 패키지 | 기준 버전 0.1.4, Python >=3.11 | tokenspeed-kernel/python/setup.py의 BASE_VERSION; 실제 버전은 브랜치·날짜에 따라 동적 생성 |
| NVIDIA 연산 | torch==2.14.0, torchvision==0.29.0, flashinfer-python==0.7.2 | tokenspeed-kernel/python/requirements/cuda.txt |
| NVIDIA DSL | nvidia-cutlass-dsl[cu13]==4.8.0, apache-tvm-ffi==0.1.13.post3 | 같은 CUDA 의존성 파일에 선언 |
| 공용 GPU DSL | tokenspeed-triton>=3.9.10.post20261008 | CUDA·ROCm 요구사항에 공통으로 선언 |
| AMD 커널 | tokenspeed-kernel-amd 0.1.4 | tokenspeed-kernel-amd/pyproject.toml; Torch와 TokenSpeed Triton에 의존 |
| NPU 커널 | tokenspeed-kernel-npu 0.1.0, triton-ascend==3.2.1 | tokenspeed-kernel-npu/pyproject.toml; 호환 TorchNPU 환경은 따로 필요 |
| MLA 커널 | tokenspeed-mla 2.0.4, CUTLASS DSL >=4.8.0 | tokenspeed-mla/pyproject.toml; 저수준 Attention 전용 부품 |
표의 NVIDIA DSL 의존성도 tokenspeed-kernel/python/requirements/cuda.txt에서 확인할 수 있습니다.
tokenspeed-kernel/python/requirements/cuda-thirdparty.txt에는 tokenspeed-deepep==2.1.0.post20260810,
tokenspeed-deepgemm==2.8.0.post20260924, tokenspeed-mla==2.0.4 등이 별도로 선언됩니다.
파일명이 thirdparty라고 해서 선택 의존성은 아닙니다.
tokenspeed-kernel/python/setup.py의 설명과 요구사항 결합 로직은 두 파일을 설치 메타데이터에 함께 반영합니다.
ROCm 쪽은 tokenspeed-kernel/python/requirements/rocm.txt에서 Torch 버전을 고정하지 않으며, tokenspeed-kernel/python/requirements/rocm-thirdparty.txt에는 Petit용 triton==3.8.0이 있습니다.
docs/package.json은 bun@1.3.0과 vitepress ^1.6.3을 선언합니다.
docs/.vitepress/config.mts는 기본 경로 /tokenspeed/, 로컬 문서 검색, 가이드·레시피 메뉴를 설정합니다.
이 구성은 문서를 빌드하는 프론트엔드이며, 별도의 React 채팅 제품을 뜻하지 않습니다.
커밋 1ed2de1의 TypeScript 계열 코드는 .mts 설정 1개·85줄입니다.
작은 설정 파일에서 Bun·VitePress가 보인다고 추론 런타임이 Node.js 서버라고 결론 내리면 안 됩니다.
docker/Dockerfile.nvidia: 기반 태그는 cu130-torch-2.14.0-flashinfer-0.6.18,
MAX_JOBS=16, CUDA_ARCH_LIST="9.0a 10.0a". 이후 소스 패키지 설치가 의존성을 다시 반영합니다.docker/Dockerfile.amd: 기반 태그는 rocm7.2.4-pytorch-2.13.0-gfx950지만,
뒤에서 torch==2.14.0·torchvision==0.29.0을 설치합니다..github/workflows/scheduler-cpp-test.yml: Ubuntu, Python 3.12, CMake·Ninja로 C++ 테스트를 빌드합니다.
CMake가 고정한 GoogleTest는 v1.14.0입니다..github/workflows/docs.yml: Bun 1.3.0으로 bun.lock을 고정 설치하고 VitePress 결과를 GitHub Pages에 배포합니다..pre-commit-config.yaml: Black 26.3.1, isort 8.0.1, clang-format v18.1.8 등 코드 형식을 고정합니다.버전은 패키지 선언, 가속기별 요구사항, 컨테이너 설치 단계를 함께 읽어야 실제 의미가 드러납니다. 특히 기반 이미지 이름에 적힌 FlashInfer 0.6.18과 현재 CUDA 요구사항의 0.7.2를 같은 값으로 적으면 부정확합니다.
아래는 ts serve의 대표 경로를 단순화한 구조입니다.
게이트웨이와 gRPC 서빙 구현 일부는 의존 패키지에 있고, 이 저장소는 기동 조합과 런타임을 제공합니다.
직접 ZMQ 연결이나 분리 서빙 같은 선택 경로까지 전부 그린 그림은 아닙니다.
AsyncLLM.generate_request()는 입력 검증, 배치 정규화, 토큰화와 응답 대기를 조합합니다.
_send_one_request()는 rid_to_state에 요청 상태를 저장하고 스케줄러 쪽으로 메시지를 보냅니다.
EngineCoreClient는 요청 송신·응답 수신·부하 스냅샷 수신 소켓을 소유합니다.
python/tokenspeed/runtime/engine/core_client.py에서는 호출 이름이 send_pyobj()여도 실제 소켓을
IpcSender·AsyncIpcReceiver로 감쌉니다. 기본 전송 형식은 MessagePack이며,
환경 변수 TOKENSPEED_USE_PICKLE_IPC=1을 설정하면 요청·응답 전송이 pickle로 바뀝니다.
함수 이름만으로 전송 형식을 판단하면 이 차이를 놓칩니다.
python/tokenspeed/runtime/engine/event_loop.py의 EventLoop.event_loop()는
요청을 받고, 캐시 완료 이벤트를 반영하고, C++ 스케줄러에서 다음 계획을 얻습니다.
실제 모델 연산은 DeviceHandle 뒤의 실행 계층으로 넘깁니다.
tokenspeed-scheduler/csrc/scheduler/scheduler.cpp의 Scheduler::NextExecutionPlan()은
실행할 요청만 고르는 함수가 아닙니다. 모델 배치와 함께 페이지 초기화·복사, 저장·복원, 원격 처리 작업도 계획에 담습니다.
Python에 노출되는 이름은 tokenspeed-scheduler/bindings/python_module.cpp의 next_execution_plan입니다.
# EventLoop.event_loop()를 이해하기 위한 의사 코드
while 서비스가_동작함:
새_요청을_받는다()
캐시_완료를_스케줄러에_반영한다()
plan = scheduler.next_execution_plan()
pending = device.execute(plan, 준비한_입력, cache_ops=장치_캐시_작업)
완료된_결과를_반영한다()
스케줄러에_상태_변화를_알린다()
관제탑이 비행 계획을 내리고 비행기가 운항하는 관계입니다. 관제탑이 직접 엔진을 조작하거나 한 비행기의 착륙만 기다리면 다른 비행기를 관리할 수 없습니다. 계획과 완료 보고를 주고받는 경계가 시스템을 확장하기 쉽게 만듭니다.
tokenspeed-scheduler/csrc/fsm/states.h는 요청 상태를 std::variant로 표현합니다.
Submitted, Prefilling, Decoding, Finishing, Finished 외에
원격 처리·저장소 대기·회수와 복원 상태도 포함합니다.
tokenspeed-scheduler/csrc/fsm/forward_states.h의 ForwardResources는 요청 슬롯, 블록 테이블,
캐시 진행 정보, results_in_flight를 한 묶음으로 가집니다.
tokenspeed-scheduler/csrc/fsm/forward_events.cpp는 std::move(state.resources)로 다음 상태에 이 묶음을 전달합니다.
// forward_events.cpp의 ReleaseEvent 본문 핵심 발췌
Finished ReleaseEvent::operator()(Finishing&& state) {
_assert(coordinator_ != nullptr, "ReleaseEvent requires a cache coordinator");
_assert(state.resources.results_in_flight == 0,
"a request is released only once its forwards have landed");
FreeRequest(*coordinator_, state.resources.block_tables);
return Finished{};
}
논리적으로 답이 끝나는 시점과 메모리를 다시 빌려줄 수 있는 시점은 다를 수 있습니다. 위 검사는 그 차이를 직접 보여 줍니다. 타입은 상태와 자원 묶음의 형태를 제한하고, 실행 중 결과 수 같은 시간 조건은 런타임 검사로 확인합니다.
tokenspeed-scheduler/csrc/cache/core/cache_block_ref.cpp에서 마지막 CacheBlockRef가 해제되면 제어 객체가 파괴되고,
CacheBlock의 소멸자가 풀에 블록을 반환합니다. 요청·캐시·전송이 함께 참조할 수 있어 단순한 “요청 종료 즉시 반환”보다 정교합니다.README는 타입 시스템을 통한 안전성을 강조합니다. 소스는 여기에 참조 수, 완료 통지, _assert도 결합합니다.
따라서 “모든 비동기 메모리 오류를 컴파일 때 막는다”는 식으로 확대 해석하지 않아야 합니다.
python/tokenspeed/runtime/execution/forward_thread.py의 ForwardThread는 queue.SimpleQueue와
소비 스레드를 사용합니다. submit_forward()는 작업과 Future를 큐에 넣고 즉시 반환합니다.
작업 중 예외는 future.set_exception()으로 결과를 기다리는 쪽에 전달합니다.
python/tokenspeed/runtime/execution/types.py의 PendingExecution.result()는 먼저 Future를 기다리고,
이어 ModelExecutionResult.sync()로 GPU→CPU 복사 완료 이벤트를 기다립니다.
“스레드가 커널 호출을 끝냈다”와 “CPU가 결과를 읽어도 된다”를 별도로 확인하는 셈입니다.
EventLoop의 in_flight_depth=0은 같은 반복에서 결과를 반영하고,
1은 다음 작업을 제출한 뒤 이전 결과를 반영합니다.
파이프라인 병렬에서는 pp_size가 깊이가 됩니다.
_dispatch_depends_on_pending_commit()는 먼저 결과 반영이 필요한 경우를 모읍니다.
비동기는 기다림을 없애는 기술이 아니라, 정확성이 허용하는 시점까지 기다림을 옮기는 기술입니다. 결과를 너무 일찍 읽는 오류와 너무 일찍 기다려 병렬성을 잃는 문제를 함께 다뤄야 합니다.
tokenspeed-kernel/python/tokenspeed_kernel/registry.py의 register_kernel()은 연산 종류, 실행 모드,
형식 서명, 하드웨어 능력, 특성, 우선순위를 등록합니다.
tokenspeed-kernel/python/tokenspeed_kernel/selection.py의 select_kernel()은 조건에 맞는 후보를 고르고 결과를 캐시합니다.
solution=은 특정 구현 계열로 후보를 좁히면서 호환성 검사를 유지합니다.
반면 override=는 하드웨어·형식·특성 매칭을 건너뛰는 강제 선택이며, 필수 기능 검사는 남습니다.
두 인자를 같은 “백엔드 선택 옵션”으로 뭉뚱그리면 중요한 차이를 놓칩니다.
tokenspeed-kernel/python/tokenspeed_kernel/selection.py의 _autotune_select()는 현재 _rank()의 첫 후보를 반환하며,
로그에도 휴리스틱으로 대체한다고 기록합니다. 설정에 warmup_iters=3·bench_iters=10이 있어도
이 함수가 모든 후보를 측정하는 구현은 아닙니다. 별도의 benchmark 도구와 구분해야 합니다.
python/tokenspeed/runtime/models/kimi_k3.py의 KimiLinearKDA._project_qkvfab()는
합쳐 계산한 투영 결과에서 mixed_qkv = output[:, : 3 * proj_local]로 뷰를 만듭니다.
이 지점에서 .contiguous()로 별도 복사하지 않습니다. 후속 KDA 연산이 뷰의 행 간격을 처리할 수 있어야 가능한 선택입니다.
아래의 토큰 그룹 양자화 함수는 연속 텐서만 받으므로 입력 조건을 구별해야 합니다.
tokenspeed-kernel/python/tokenspeed_kernel/ops/quantization/triton.py의
_fp8_token_group_quantize()는 그룹별 FP8 양자화를 여러 그룹씩 타일로 묶어 실행합니다.
# 실제 분기 조건 발췌: padded_elements = groups * block
if padded_elements < 1 << 14 or block >= 1024:
groups_per_program = 1
elif padded_elements < 7 << 18:
groups_per_program = 1024 // block
else:
groups_per_program = 8192 // block
block=128, 큰 입력이라면 프로그램 하나가 64그룹을 처리합니다.
[4096, 7168] 입력은 그룹 크기 128일 때 229,376그룹이고, 64개씩 묶으면 3,584개 프로그램이 됩니다.
이는 실행 단위 개수의 계산이지 64배 속도 향상의 측정값이 아닙니다.
이 커널의 groups는 런타임 인자이고 GROUPS_PER_PROGRAM은 컴파일 때 정해집니다.
서빙 전에는 block이 1024보다 작은 경우 같은 서명에 필요한 세 타일을 warmup()으로 미리 컴파일합니다.
목표는 작업 분할 개선과 함께 서빙 도중 새로운 크기 때문에 컴파일이 끼어드는 일을 줄이는 것입니다.
| 언어·영역 | 파일 수 | 물리적 줄 수 |
|---|---|---|
| Python 전체 | 2,381 | 1,024,856 |
| C/C++·CUDA 전체 | 223 | 94,360 |
| JavaScript | 2 | 221 |
| TypeScript 계열 .mts | 1 | 85 |
| Shell | 61 | 5,077 |
| python/ | 537 | 214,822 |
| tokenspeed-scheduler/ | 117 | 44,378 |
| tokenspeed-kernel/ | 1,132 | 486,749 |
| test/ | 563 | 222,290 |
표의 아래 네 행은 영역별 Python·C/C++·CUDA 합계입니다. 위의 언어별 합계와 겹치므로 다시 더하지 않습니다.
집계는 .git/을 제외한 물리적 줄 수이며, 빈 줄·주석·라이선스 문구·테스트·동봉한 외부 코드를 포함합니다.
C/C++·CUDA는 .c/.cc/.cpp/.h/.hpp/.cu/.cuh, Python은 .py/.pyi를 대상으로 셌습니다.
Python 중 thirdparty/ 안에 있는 코드만 221파일·105,960줄입니다.
Python에는 GPU용 DSL도 포함됩니다. 주 언어가 Python이라고 해서 100만 줄 모두가 HTTP 처리나 CPU 처리인 것은 아닙니다.
docs/design/event-loop.md로 제어와 실행의 경계를 파악한다.python/tokenspeed/runtime/engine/async_llm.py의 generate_request()에서 송신 경로를 따라간다.python/tokenspeed/runtime/engine/event_loop.py의 event_loop()에서 계획과 완료 반영을 찾는다.tokenspeed-scheduler/csrc/fsm/forward_events.cpp에서 상태와 자원의 이동을 읽는다.tokenspeed-kernel/python/tokenspeed_kernel/selection.py에서 관심 있는 연산으로 들어간다.거대한 모델 구현을 처음부터 읽기보다 요청·계획·실행·완료의 경계를 하나씩 연결하면 전체 구조를 이해하기 쉽습니다.
AsyncLLM.generate_request()의 async for는 응답이 생길 때마다 소비자에게 넘깁니다.
반면 ForwardThread는 GPU 작업 제출을 담당하는 별도 스레드입니다.
async라는 문법 자체가 GPU 계산을 병렬화해 주지는 않습니다.
배울 것: 코루틴은 대기 시간을 다른 일에 양보하고, 큐·Future는 생산자와 소비자의 책임을 연결합니다.
실습 아이디어: test/runtime/test_forward_thread.py에서 FIFO 순서, 예외 전달,
느린 작업 중에도 제출이 막히지 않는다는 세 테스트를 읽고 각각의 실패 상황을 글로 써 봅니다.
std::variant는 “현재 상태가 여러 후보 중 정확히 하나”임을 나타냅니다.
HoldsForwardResources라는 concept는 특정 상태가 필요한 자원 묶음을 갖는지 표현합니다.
“finished인데 running도 true” 같은 조합을 플래그 여러 개로 만들지 않는 설계입니다.
배울 것: 값의 범위뿐 아니라 유효한 상태 조합을 타입으로 표현하기. 실습 아이디어: 파일 업로드 상태를 대기·수신·검증·완료로 나누고, 임시 파일 핸들을 어느 상태가 소유해야 하는지 표로 그려 봅니다.
docs/design/cache-concepts.md는 접두부 식별 단위인 prefix_granularity,
이력 블록의 논리 토큰 범위인 block_granularity, 실제 저장 단위인 CacheBlock,
커널의 페이지 크기를 분리합니다. 서버의 기본 prefix_granularity는 64토큰입니다.
여기에 전체 이력, 슬라이딩 윈도, 순환 상태라는 세 캐시 종류가 있습니다. 순환 상태 그룹은 일반 KV 이력과 달리 “토큰 행을 담은 페이지”로 선언하지 않습니다. 모든 Attention 상태를 단일 KV 배열로 취급하면 하이브리드 모델을 설명하기 어렵습니다.
책의 “몇 쪽까지 읽었나”, 책장 칸의 크기, 창고 상자의 크기는 서로 다른 단위입니다. 상자 하나에 여러 칸의 내용물이 들어가도 독서 진도는 쪽수로 계산해야 합니다. 논리 단위와 저장 단위를 나누면 압축·캐시 이동을 바꿔도 일정 계산을 유지할 수 있습니다.
실습 아이디어: tokenspeed-scheduler/tests/cpp/test_kv_cache_lifecycle.cpp에서
전체 이력과 슬라이딩 윈도의 블록 테이블이 어떻게 달라지는지 읽습니다.
특히 슬라이딩 윈도 뒤쪽에 남는 실제 블록과 앞쪽의 0번 자리 표시자를 구분해 봅니다.
FP8은 수를 적은 비트로 담아 저장·이동 비용을 줄이는 표현입니다.
_fp8_token_group_quantize_kernel()은 그룹별 최댓값으로 scale을 구하고 입력을 나눠 변환합니다.
여러 그룹을 한 프로그램에 배치해도 각 그룹의 계산은 독립적입니다.
# 커널 계산의 의사 코드. bit8_max는 자료형의 최대 유한값이다.
scale = max(max(abs(group)), 1e-10) / bit8_max
quantized = clamp(group / scale, bit8_min, bit8_max).to(torch.float8_e4m3fn)
복원한_근삿값 = quantized.float() * scale
배울 것: 양자화 오차와 타일링 성능은 서로 다른 검증 대상입니다.
실습 아이디어: tokenspeed-kernel/test/ops/test_fp8_quantization_block.py에서
블록 경계 크기, 블록별 scale, 복원 오차를 어떻게 검사하는지 찾습니다.
이 파일의 블록 양자화 테스트를 토큰 그룹 커널의 성능 측정으로 혼동하지 않습니다.
docs/design/unified_path.md는 일반 실행과 그래프 재생이 동일한
refresh_decode_metadata() 경로를 사용한다고 설명합니다.
그래프 재생에서는 준비한 버퍼의 내용을 갱신한 뒤 기록된 실행을 재사용합니다.
그래프에 담는 배치 크기의 상한과 실제 처리 가능한 배치 크기의 상한도 분리합니다.
기록 범위를 넘는 decode는 같은 메타데이터 갱신 경로로 일반 실행을 합니다.
실습 아이디어: 문서에서 max_capture_bs와 max_decode_bs가 다른 이유를 찾아 설명합니다.
test/ci/README.md는 서버를 기동하는 CI가 기본적으로
TOKENSPEED_JIT_COMPILE_CHECK=error를 사용한다고 명시합니다.
요청마다 바뀌는 값을 컴파일 상수로 넣어 서빙 중 재컴파일을 반복하는 문제도 실패로 취급합니다.
성능 최적화의 완료 조건에는 출력의 정확성뿐 아니라 대기·재컴파일·자원 회수의 동작도 포함됩니다. 정답만 같고 지연이 간헐적으로 튀는 변경을 놓치지 않는 관점입니다.
| 목적·경로 | 확인할 요구사항 | 근거와 주의점 |
|---|---|---|
| 소스 공부 | 텍스트 편집기, Git, 파일 검색 도구 | 이 글의 읽기 과제는 GPU와 모델 다운로드 없이 가능 |
| 주 런타임 | Python >=3.11 | 공식 nightly 안내는 Python 3.11–3.13을 대상으로 설명 |
| NVIDIA | sm90/sm100/sm103/sm107으로 지원 범위 제한 | AGENTS.md의 범위. 같은 제조사의 모든 GPU를 뜻하지 않음 |
| 기본 CUDA 경로 | CUDA 13용 의존성·호환 드라이버·Linux 환경 | tokenspeed-kernel/python/requirements/cuda.txt의 cu13 DSL과 릴리스 워크플로의 Linux x86_64·ARM64 빌드 대상 |
| H100/H200 별도 경로 | Linux, Python 3.11, CUDA Toolkit 12.9 | docs/guides/hopper-cu129.md는 실험적 소스 설치법으로 명시 |
| AMD | ROCm 7.2 계열, gfx950/gfx1250 및 gfx1201의 이식성 커널 | RDNA4 gfx1201은 초기 Dense BF16 경로로 범위가 다름 |
| Ascend NPU | 호환 Torch·TorchNPU·Triton Ascend 및 별도 네이티브 연산 | 지원 모델 범위가 제한됨. GPU 경로 전체를 그대로 사용할 수 있다는 뜻은 아님 |
| 네이티브 빌드 | C++20 컴파일러, CMake·Ninja, OpenSSL 개발 파일 | 스케줄러 빌드 설정 기준. 커널 빌드는 별도 가속기 툴체인 필요 |
| 컨테이너·저장소 | GPU 접근 가능한 Docker, 충분한 공유 메모리·모델 저장 공간 | 시작 가이드의 --shm-size 32g는 예시값이며 보편적 최솟값이 아님 |
| 여러 GPU·노드 | 모델 분할 방식에 맞는 GPU 연결·통신 환경 | TP·EP·PD 전송 비용까지 포함해 모델별 레시피를 확인 |
저장소 전체에 공통으로 적용할 VRAM 최솟값은 없습니다. 필요한 메모리는 대략 가중치 + KV·순환 상태 + 연산 작업 공간 + 그래프·통신 버퍼입니다. 여기에 동시 요청 수와 최대 문맥 길이가 영향을 줍니다.
# 가중치만 보는 교육용 근사식. 실제 설치 요구량은 아니다.
가중치_바이트 ≈ 전체_매개변수_수 * 매개변수당_비트 / 8
7B 모델을 BF16으로 저장: 7,000,000,000 * 16 / 8 ≈ 14 GB
7B 모델을 4비트로 저장: 7,000,000,000 * 4 / 8 ≈ 3.5 GB
# scale·메타데이터·일부 고정밀 가중치·런타임 버퍼는 별도다.
MoE 모델의 “토큰당 활성 매개변수”가 작아도 전체 전문가 가중치의 저장 비용이 사라지지는 않습니다. 397B-A17B 같은 이름에서 17B만 메모리에 들어간다고 계산하면 안 됩니다.
# 일반적인 비압축 MHA/GQA의 KV 데이터만 계산하는 근사식
KV_바이트 ≈ 2 * 레이어_수 * KV_헤드_수 * 헤드_차원
* 저장_토큰_수 * 원소당_바이트
# 가상의 설정: 32층, KV 헤드 8개, 차원 128, 4096토큰, BF16
2 * 32 * 8 * 128 * 4096 * 2 = 536,870,912바이트
= 요청당 512 MiB
동일 조건의 요청 8개면 KV 데이터만 약 4 GiB입니다. 접두부 공유, 분할 저장, 여유 공간 등은 따로 고려해야 합니다. 이 식은 MLA 압축 캐시나 KDA의 순환 상태에 그대로 적용할 수 없습니다.
GPU 아키텍처, 모델 구현, 정밀도, Attention 백엔드, 통신 라이브러리가 모두 맞아야 합니다. 일반 노트북이나 CPU 전용 환경에서 공식 GPU 서빙 레시피가 동작한다고 볼 근거는 없습니다. 메모리 식은 예산 추정용이며 실제 실행을 보증하는 요구사항 표가 아닙니다.
Hopper 가이드는 CUDA Toolkit 12.9를 쓰면서 Torch는 2.14.0+cu126을 사용하는 특수 조합도 설명합니다.
툴킷 버전, Torch가 묶은 런타임 버전, 드라이버 버전을 하나의 숫자로 취급하지 않는 습관을 들이세요.
목표: generate_request()에서 요청이 어디로 나가고 응답은 어디서 돌아오는지 찾습니다.
방법: python/tokenspeed/runtime/engine/async_llm.py의 검증·토큰화·송신·대기 함수를 표시하고
python/tokenspeed/runtime/engine/core_client.py에서 각 소켓을 연결합니다. 이어 python/tokenspeed/runtime/engine/event_loop.py의 계획 호출을 찾습니다.
완료 기준: 요청 ID 보관 위치, 송신 객체, 계획 생성 함수, 결과 대기 함수를 적은 화살표 그림 한 장. 게이트웨이·엔진 프로세스·실행 스레드를 같은 상자로 그리지 않으면 성공입니다.
# 저장소 루트에서 소스 텍스트만 검색하는 명령
rg -n 'generate_request|_send_one_request|_wait_one_response' \
python/tokenspeed/runtime/engine/async_llm.py
rg -n 'next_execution_plan|_device.execute|advance_scheduler' \
python/tokenspeed/runtime/engine/event_loop.py
목표: Finishing이 따로 존재하는 이유를 설명합니다.
방법: tokenspeed-scheduler/csrc/fsm/forward_events.cpp의 FinishEvent·ReleaseEvent를 읽습니다.
자원을 가진 상태에서 std::move가 어디로 향하는지 표시하고,
tokenspeed-scheduler/csrc/cache/core/cache_block_ref.cpp의 마지막 참조 해제까지 연결합니다.
완료 기준: 결과가 하나 남았을 때와 모두 도착했을 때의 표를 작성합니다. 전자는 정상적인 Release 조건을 만족하지 않으며, 후자는 요청 참조를 놓을 수 있습니다. 다른 캐시·전송 참조가 남으면 물리 블록이 즉시 반환되지 않을 수도 있다는 설명까지 붙입니다.
목표: 토큰 수와 메모리 바이트 수를 구분합니다.
방법: 7절의 가상 모델로 4,096·8,192토큰, 동시성 1·8일 때 KV 크기를 계산합니다.
별도로 20,000토큰 입력을 chunked_prefill_size=8192로 나눈 이상적인 청크 길이를 적습니다.
완료 기준: KV는 각각 요청당 512 MiB·1 GiB, 동시성 8에서는 4 GiB·8 GiB라는 표를 만듭니다. 단순 분할은 8,192·8,192·3,616입니다. 실제 스케줄러는 캐시 적중·정렬·가용량에 따라 다르게 자를 수 있다는 주석을 붙입니다.
목표: 후보 필터링, 순위 결정, 강제 선택을 구별합니다.
방법: tokenspeed-kernel/python/tokenspeed_kernel/registry.py의 KernelSpec 필드로 가상의 후보 세 개를 적습니다.
하나는 장치가 맞지 않고, 하나는 필수 기능이 없고, 하나만 모두 만족하게 만듭니다.
tokenspeed-kernel/python/tokenspeed_kernel/selection.py에서 각 후보가 탈락하는 위치를 연결합니다.
완료 기준: 기본 선택과 solution=·override=의 차이를 설명한 흐름도.
override도 필수 기능 검사를 생략하지 않는다는 점과,
현재 _autotune_select()가 실제 측정을 하지 않는다는 근거를 적습니다.
목표: 코드 변경의 성능 가설과 정확성 조건을 분리합니다.
방법: _fp8_token_group_quantize()의 세 분기로 아래 실행 단위를 계산합니다.
이어 입력 정렬·마지막 타일 마스크·그룹별 scale·시작 후 재컴파일 여부를 검증 항목으로 정합니다.
완료 기준: 예상 프로그램 수 표와 측정 계획서 한 장. 향후 GPU 측정 시에는 준비 실행, 동일 입력·자료형, 반복 시간 분포, 수치 비교를 기록하도록 설계합니다. 프로그램 수 감소만으로 속도 향상 배수를 단정하지 않습니다.
| 입력·그룹 크기 128 | 그룹 수 | 프로그램당 그룹 | 프로그램 수 |
|---|---|---|---|
| [32, 128] | 32 | 1 | 32 |
| [256, 128] | 256 | 8 | 32 |
| [4096, 7168] | 229,376 | 64 | 3,584 |
실행하지 않고도 데이터 흐름·자원 수명·메모리 예산·최적화 가설을 검증하는 훈련을 할 수 있습니다. 먼저 이 다섯 결과물을 만들면, 나중에 GPU를 사용할 때 무엇을 측정할지 훨씬 명확해집니다.
Python 함수와 리스트를 읽을 수 있다면 1주차부터 시작할 수 있습니다. C++ 템플릿이나 GPU 프로그래밍을 전부 익힌 뒤 읽으려 하지 말고, 각 주차의 질문에 답하는 데 필요한 문법부터 보충합니다.
| 주차 | 학습 주제 | 연결할 소스·문서 | 남길 결과물 |
|---|---|---|---|
| 1주 | 토큰화, Attention, Prefill·Decode, 가중치와 KV의 차이 | docs/index.md, python/tokenspeed/runtime/configs/model_config.py | 한 요청의 토큰 증가와 메모리 증가 그림 |
| 2주 | asyncio, 비동기 생성기, 큐, Future, IPC | python/tokenspeed/runtime/engine/async_llm.py, python/tokenspeed/runtime/engine/core_client.py, python/tokenspeed/runtime/execution/forward_thread.py | 요청·응답 시퀀스 그림과 대기 위치 표 |
| 3주 | C++ 이동, RAII, variant, 상태 전이 | tokenspeed-scheduler/csrc/fsm/forward_states.h, tokenspeed-scheduler/csrc/fsm/forward_events.cpp, tokenspeed-scheduler/csrc/cache/core/cache_block_ref.cpp | 자원 소유자가 표시된 상태 전이표 |
| 4주 | 접두부 캐시, 논리·물리 단위, 청크 배정, 회수·복원 | docs/design/cache-concepts.md, docs/design/scheduler.md | 토큰 경계·블록 위치·참조 수를 나눈 캐시 그림 |
| 5주 | 텐서 stride, FP8 scale, GPU 타일, JIT 캐시 | tokenspeed-kernel/python/tokenspeed_kernel/ops/quantization/triton.py, tokenspeed-kernel/python/tokenspeed_kernel/compile_monitor.py, Kimi K3 투영 함수 | 수치 오차 조건과 실행 프로그램 수 계산 |
| 6주 | CUDA Graph, 병렬화, 벤치마크 통제, 정확도 회귀 | docs/design/unified_path.md, docs/serving/parallelism.md, test/ci/ | 동일 조건 비교를 위한 실험 계획서 |
3주차의 C++ 문법이 어렵다면 상태 전이 테스트에서 입력과 기대 결과를 먼저 읽어도 좋습니다. 5주차에는 FP8 수치 오차와 GPU 실행 단위의 변화를 각각 설명할 수 있는지 확인합니다.
“비동기라 빠르다” 대신 어떤 함수가 제출하고 어디서 기다리는지, “캐시가 안전하다” 대신 누가 참조를 쥐고 언제 놓는지 설명해 보세요. 근거가 연결되지 않는 화살표가 다음에 읽을 파일을 알려 줍니다.
| 용어 | 쉬운 설명 |
|---|---|
| 추론·서빙 | 추론은 학습된 모델로 출력을 계산하는 일, 서빙은 여러 사용자의 요청을 서비스로 받는 일입니다. |
| Token | 모델이 읽는 문자열 조각. 글자 수나 단어 수와 정확히 같지 않습니다. |
| Prefill | 입력 프롬프트를 처리해 다음 생성에 필요한 상태를 만드는 구간입니다. |
| Decode | 현재 문맥을 바탕으로 다음 토큰을 반복 생성하는 구간입니다. |
| KV cache | 과거 토큰의 Attention용 K·V 중간 결과. 모델 가중치와 별도인 메모리입니다. |
| Prefix cache | 여러 요청의 동일한 앞부분에 대한 계산 상태를 재사용하는 장치입니다. |
| Chunked prefill | 긴 입력을 여러 조각으로 나누어 처리합니다. 메모리 배정과 다른 요청의 진행을 함께 조절합니다. |
| 연속 배칭 | 모든 요청의 종료를 기다리지 않고, 진행 중인 작업 사이에 새 요청을 편입하는 방식입니다. |
| 제어·실행 평면 | 무엇을 언제 할지 정하는 부분과 실제 계산·전송을 수행하는 부분을 나눈 것입니다. |
| FSM | 유한 상태 머신. 요청이 가질 수 있는 상태와 허용되는 전이를 명시합니다. |
| RAII | 객체가 살아 있는 동안 자원을 보유하고, 수명이 끝날 때 반환 동작을 수행하는 C++ 방식입니다. |
| In-flight | 제출했지만 아직 결과를 반영하지 않은 작업. 자원이 여전히 사용 중일 수 있습니다. |
| Future | 나중에 완료될 작업의 결과나 예외를 전달하는 손잡이입니다. |
| FIFO | 먼저 넣은 작업을 먼저 꺼내는 순서. 스레드 제출 순서와 GPU 완료 여부는 별도로 봐야 합니다. |
| D2H | 장치에서 호스트로 결과를 복사하는 것. 여기서는 주로 GPU→CPU 이동을 말합니다. |
| Kernel | GPU 등 가속기에서 실행하는 연산 프로그램입니다. 운영체제 커널과는 문맥이 다릅니다. |
| Registry | 사용 가능한 구현과 그 조건을 모은 명부. 선택 로직이 이 명부에서 후보를 고릅니다. |
| Stride | 텐서의 다음 원소나 다음 행으로 이동할 때 메모리에서 건너뛰는 간격입니다. |
| FP8·FP4 | 8비트·4비트 부동소수점 계열 표현. scale과 형식에 따라 정확도·저장 방식이 달라집니다. |
| Tile | 큰 행렬이나 배열을 나누어 처리하는 작은 작업 덩어리입니다. |
| JIT | 실행 시점에 필요한 코드를 컴파일하는 방식. 서빙 중 새 컴파일은 지연을 만들 수 있습니다. |
| CUDA Graph | 반복할 GPU 작업 흐름을 기록해 재생하는 기능. 버퍼 수명과 메타데이터 갱신이 중요합니다. |
| MoE | 토큰별로 일부 전문가 신경망을 선택하는 모델 구조. 전체 가중치 저장과 전문가 통신도 고려합니다. |
| MLA·KDA | 서로 다른 Attention 계열입니다. MLA는 잠재 표현을 활용하고, KDA는 게이트와 순환 상태를 활용합니다. |
| TP·EP·PP | 텐서 연산, 전문가, 모델 단계라는 서로 다른 축으로 계산을 여러 장치에 나누는 방식입니다. |
| PD 분리 | Prefill과 Decode를 서로 다른 자원에서 처리하고 필요한 캐시 상태를 넘기는 구성입니다. |
| 추측 생성 | 초안 토큰을 먼저 제안하고 대상 모델이 검증해 여러 토큰의 진행을 시도하는 방식입니다. |
| TTFT·TPOT | 첫 토큰까지의 대기와 출력 토큰당 시간. 전체 토큰 처리량과 구별해서 봅니다. |
| Nanobind | C++ 기능을 Python에서 호출할 수 있게 연결하는 바인딩 도구입니다. |
| MessagePack·ZMQ | 메시지의 직렬화 형식과 전송 도구입니다. 각각 “어떻게 담는가”와 “어떻게 보내는가”를 맡습니다. |
| 항목 | 상태 | 기준 |
|---|---|---|
| 저장소 | lightseekorg/tokenspeed | 공식 GitHub |
| 선정 | GitHub Trending Python Weekly #15 | 2026-10-23 분석 기준 |
| 스타 / 포크 | 2,772 / 371 | 2026-10-23 GitHub 기준 |
| 생성일 | 2026-05-06 | 2026-10-23 GitHub 기준 |
| 최신 릴리스 | v0.1.1 · 2026-10-06 | 2026-10-23 GitHub 기준 |
| 최근 push | 2026-10-23 | 2026-10-23 GitHub 기준 |
| 최근 커밋 | 1ed2de1 · 2026-10-23 | Kimi K3의 KDA Q·K·V 복사 제거와 두 FP8 양자화기의 Prefill 타일 조정 · #2626 |
| 주 언어 / 규모 | Python · 1,024,856줄 / 2,381파일 | 커밋 기준 물리적 줄 수, 테스트·외부 코드 포함 |
| 네이티브 코드 | C/C++·CUDA 94,360줄 / 223파일 | 헤더·테스트·외부 코드 포함, 집계 확장자는 5절 참조 |
| 주 패키지 / 부품 | 런타임 0.1.1 · 스케줄러 0.1.42 · MLA 2.0.4 | 각 패키지의 프로젝트 버전 기준, 개별 배포 주기는 다름 |
| 라이선스 | MIT | LICENSE · LightSeek Foundation, 2026 |
설명과 코드 규모는 커밋 1ed2de1에 고정되어 있습니다.
이 커밋의 소스는 10월 6일 릴리스 이후 상태이므로, 릴리스 v0.1.1을 설치했다고 모든 설명과 코드가 동일하지는 않습니다.
README의 속도 기록·업계 비교·지원 발표는 프로젝트의 공개 주장입니다. 성능 수치는 모델·정밀도·하드웨어·입출력 길이·동시성 등 측정 조건과 함께 읽어야 합니다. CI 파일의 존재는 해당 커밋이 모든 장치에서 통과했다는 증거가 아니며, 실제 가용 메모리와 설치 호환성도 대상 환경에서 확인해야 합니다.
MIT 라이선스는 엔진 코드에 관한 것입니다. 모델 가중치·데이터셋·동봉한 외부 구성요소에는 별도 이용 조건이 있을 수 있습니다. 게이트웨이 같은 의존 패키지의 동작은 해당 패키지의 문서와 버전도 함께 확인해야 합니다. 설치·실험 전에는 사용할 모델과 정확한 가속기 조합의 공식 레시피를 함께 확인하세요.