[LLMSO] 최적화는 어디에서 실행되는가: LLM Serving Framework의 구조와 선택

xgro·어제

LLMSO

목록 보기
8/10

📌 Notice

LLM Serving and Optimization Study (=LLMSO)
직접 실습을 통해 LLM 서비스 제공 및 최적화에 대한 지식에 대한 학습 및 실습 내용을 정리한 블로그입니다.

CloudNet@에서 스터디를 진행하고 있습니다.

EKS 관련 이전 스터디 내용은 아래 링크를 통해 확인할 수 있습니다.


Intro

최적화는 어디에서 실행되는가

앞선 챕터에서는 Continuous Batching, Paged KV Cache, Speculative Decoding처럼 Serving을 빠르게 만드는 방법을 살펴봤습니다.
이번에는 한 단계 아래로 내려가 그 방법을 실제로 누가 실행하는지 따라가려 합니다.

사용자는 HTTP 요청 하나를 보내지만,
그 뒤에서는 입력을 Token으로 바꾸고, 이번 Iteration에 처리할 Token을 고르고, KV Cache 공간을 예약하고, GPU Kernel을 실행하는 일이 이어집니다.
이 경로를 구현하는 것이 LLM Serving Framework 또는 Serving Runtime입니다.

처음 읽는 동안에는 하나의 요청을 계속 따라가면 이해하기 쉽습니다.
사용자가 긴 공통 문서와 질문을 /v1/chat/completions로 보내고,
답변은 JSON 형식을 지키면서 Streaming으로 돌아와야 한다고 가정합니다.
같은 순간 다른 사용자의 짧은 요청도 들어오며 서버에는 GPU Memory와 Token Budget이 제한되어 있습니다.

바깥의 Serving Platform은 이 요청을 인증하고 적절한 Replica로 보냅니다.
안쪽의 Serving Runtime은 문자열을 Token으로 바꾸고,
이번 실행에서 어느 요청의 몇 Token을 처리할지 정하며,
KV Cache 공간을 확보한 뒤 Hardware에서 Model 연산을 실행합니다.
Framework 이름은 달라도 이 책임을 어디에 두는지 살펴보면 제품 설명을 실행 흐름으로 바꿀 수 있습니다.

먼저 일반적인 Serving Platform과 LLM 전용 Runtime의 경계를 구분합니다.
그다음 vLLM의 API Server부터 LLMEngine, EngineCore, GPU Worker까지 요청 하나를 따라가고,
Scheduler가 제한된 Token Budget을 어떻게 나누는지 살펴봅니다.

TensorRT-LLM, SGLang, llama.cpp는 기능표로 줄 세우지 않습니다.
각 Framework가 어느 Hardware와 Workload 경계에서 강점을 갖는지,
대신 어떤 운영 조건과 교체 비용을 만드는지 비교합니다.

이 글의 목표는 “무엇이 가장 빠른가”를 고르는 것이 아닙니다.
우리 요청의 SLO와 입력 분포, Hardware, 운영 역량을 먼저 적고,
그 조건에서 필요한 실행 경로를 선택하는 방법을 익히는 것이 목표입니다.

일곱 학습 블록은 이 요청을 바깥에서 안쪽으로 따라갑니다.
먼저 Platform과 Runtime의 경계를 구분하고,
vLLM Component와 Scheduler를 통해 한 요청이 여러 Iteration을 도는 이유를 확인합니다.
그 뒤 TensorRT-LLM, SGLang과 llama.cpp가 서로 다른 Hardware·Workload 경계를 어떻게 선택하는지 보고,
마지막에는 기능표가 아니라 SLO와 교체 비용을 포함한 평가 계약을 만듭니다.

제품 구조와 Release 상태는 2026-09-05에 공식 문서로 다시 확인했습니다.
과거 실행 기록은 현재 Benchmark와 분리하고,
직접 실행하지 않은 결과는 실행한 것처럼 쓰지 않습니다.

M1 Air 16GB에서는 Scheduler 산술과 llama.cpp Local 경로를 학습할 수 있습니다.
CUDA Kernel, Tensor Parallel과 Datacenter GPU Framework의 Throughput 비교는 NVIDIA 환경이 필요하므로,
로컬 결과와 같은 Evidence로 취급하지 않습니다.



🚀 Step 01. 일반적인 Model Server만으로는 부족한 상태가 생긴다

일반적인 Model Server도 GPU를 호출하는데 LLM Serving에는 왜 별도의 Runtime 상태가 필요할까?

Serving System을 한 제품 이름으로 묶어 보면 역할이 쉽게 섞입니다.
요청을 인증하고 여러 Replica로 보내는 일과,
한 GPU 안에서 어떤 Token을 먼저 계산할지 정하는 일은 서로 다른 문제입니다.

일반적인 Predict API는 입력 하나를 Tensor로 바꾸고 Forward Pass를 실행한 뒤 결과를 반환할 수 있습니다.
하지만 LLM 생성은 Prompt를 읽는 Prefill 뒤에 다음 Token을 하나씩 만드는 Decode를 반복합니다.
한 요청이 끝나기 전에도 다른 요청이 도착하고,
각 요청이 지금까지 만든 Key·Value 상태는 다음 반복을 위해 계속 보존해야 합니다.
LLM Runtime은 이 오래 살아 있는 요청 상태를 Token 생성 반복과 함께 관리합니다.

Platform과 Runtime은 같은 층이 아니다

바깥쪽 Serving Platform은 보통 Endpoint, 인증, Routing, Autoscaling, Health Check, Model Repository 같은 운영 기능을 맡습니다.
NVIDIA Triton처럼 여러 Backend를 적재하는 Platform은 vLLM이나 TensorRT-LLM Backend를 품을 수도 있습니다.
따라서 Generic Platform과 LLM Runtime은 반드시 둘 중 하나만 선택하는 경쟁 관계가 아닙니다.

여기서 Backend는 Platform이 실제 Model 실행을 맡기는 구현입니다.
Replica는 같은 Model과 Runtime을 독립적으로 실행하는 Serving 사본이고,
Router는 요청을 Replica 중 하나로 보냅니다.
Replica를 늘리는 일은 바깥의 용량 문제이고,
한 Replica 안에서 요청을 섞고 KV Cache를 배치하는 일은 Runtime의 실행 문제입니다.

안쪽 LLM Runtime은 Token 생성 과정에 더 가까이 있습니다.
대표적으로 다음 상태를 함께 다룹니다.

  • 서로 길이가 다른 요청을 Iteration마다 다시 묶는 Scheduling
  • Prefill과 Decode가 경쟁할 때의 Token Budget
  • 요청마다 늘어나는 KV Cache Block의 할당과 회수
  • Prefix Cache Hit와 Preemption에 따른 실행 순서
  • Tensor Parallel Worker 사이의 동기화
  • Attention과 Sampling에 사용할 Kernel 경로

이 기능은 “GPU를 호출한다”는 설명만으로는 해결되지 않습니다.
Decode 요청은 Token 하나가 생길 때마다 다시 Scheduling 대상이 되고,
이전 Token에서 만든 KV Cache를 다음 Iteration까지 보존해야 합니다.
Framework는 이 상태를 Request 단위와 Block 단위로 계속 이어갑니다.

처음의 긴 문서 요청을 예로 들면 Platform은 사용자 인증과 Rate Limit을 확인하고 Replica 2를 선택할 수 있습니다.
Replica 2의 Runtime은 긴 Prefill을 몇 Token씩 실행할지 정하고,
이미 답변을 생성 중인 짧은 요청과 같은 Iteration에 섞을지 결정합니다.
필요한 KV Block이 부족하면 새 요청을 기다리게 하거나 다른 요청을 중단할 수도 있습니다.
이 선택은 HTTP Gateway가 아니라 Model의 Token 상태와 Memory를 아는 Runtime에서 이루어집니다.

전용 Runtime이 항상 정답은 아니다

LLM 전용 Runtime이 필요한 이유를 설명한다고 해서 일반 Server가 부적합하다는 뜻은 아닙니다.
Batch가 거의 없고 Latency 요구가 느슨한 내부 도구라면 단순한 실행 경로가 운영하기 더 쉬울 수 있습니다.
반대로 서로 다른 길이의 요청이 동시에 들어오고 GPU 비용이 중요하다면,
Continuous Scheduling과 KV Cache 관리가 없는 단순 Queue는 자원을 오래 비워둘 가능성이 큽니다.

판단 순서는 제품 이름보다 Workload가 먼저입니다.
요청 도착 형태, Prompt와 Output 길이 분포, TTFT와 TPOT 목표, 동시성, 사용할 GPU를 적고,
그 조건에서 필요한 Runtime 기능을 고르는 편이 안전합니다.

TTFT(Time to First Token)는 요청부터 첫 Token까지의 시간이고,
TPOT(Time per Output Token)는 첫 Token 이후 Token 하나를 만드는 평균 간격을 표현하는 지표입니다.
긴 보고서를 밤새 Batch로 생성한다면 전체 Throughput이 더 중요할 수 있지만,
대화형 서비스라면 사용자가 처음 기다리는 TTFT와 Streaming 중 멈춤이 더 중요할 수 있습니다.
같은 Runtime 기능도 어느 지표를 우선하느냐에 따라 좋은 설정이 달라집니다.

Runtime 밖에 남는 문제

Runtime이 Token 생성 경로를 최적화해도 서비스 전체를 운영해주지는 않습니다.
Tenant별 Rate Limit, 배포 전략, Model Rollback, Secret 관리, Audit Log, 비용 귀속은 더 바깥의 Control Plane과 Platform이 맡아야 합니다.
OpenAI-compatible Endpoint도 API 모양을 맞춰줄 뿐,
Tokenizer, Chat Template, Sampling 기본값, Error Semantics까지 자동으로 같게 만들지는 않습니다.

Tokenizer는 문자열을 Model이 처리하는 Token ID로 바꾸고,
Chat Template은 System·User·Assistant Message를 Model이 학습한 입력 형식으로 배열합니다.
Temperature나 Top-p 같은 Sampling Parameter는 다음 Token 후보를 고르는 방식을 바꿉니다.
두 Runtime이 같은 JSON Endpoint를 제공해도 이 세 요소가 달라지면 같은 Prompt에서 다른 입력과 출력을 만들 수 있습니다.

결국 Serving Stack은 층으로 보는 편이 좋습니다.
Platform은 요청을 안전하게 받을 조건을 만들고,
Runtime은 제한된 Memory와 Compute 안에서 다음 Token을 계산하며,
Worker와 Model Runner는 선택된 연산을 실제 Hardware에서 실행합니다.

이 책임 경계를 잡았으니 다음에는 추상적인 Runtime 상자를 열어 봅니다.
vLLM에서 API 요청이 어느 Component와 Process를 지나며,
어디에서 Request 상태가 만들어지고 다시 Streaming 응답으로 돌아오는지 따라갑니다.

참고한 1차 자료



🚀 Step 02. vLLM 요청 하나를 API에서 GPU까지 따라가기

vLLM을 LLM.generate() 한 줄로 사용하면 내부 Component가 모두 같은 Process에 있는 것처럼 보입니다.
하지만 Online Serving에서는 API Server, EngineCore, GPU Worker가 분리될 수 있고,
각 Component가 Request 상태를 넘겨받습니다.

Process는 독립된 Memory 공간에서 실행되는 운영체제 실행 단위이고,
Component는 하나의 Process 안에 있을 수도 별도 Process로 분리될 수도 있는 논리적 책임 단위입니다.
Architecture를 읽을 때 둘을 구분해야 Diagram의 상자 수를 실제 Process 수로 오해하지 않습니다.

2026-09-05 기준 이 글은 vLLM v0.28.0의 V1 Architecture를 기준으로 합니다.
아래 흐름은 개념적 책임 경계이며,
IPC 방식과 세부 Class 이름은 Version에 따라 달라질 수 있습니다.

1. API Server와 InputProcessor

API Server는 HTTP 요청을 받고 Streaming 연결을 관리합니다.
입력은 Tokenize되고,
Sampling Parameter와 함께 Engine이 이해할 수 있는 Request로 정규화됩니다.

처음의 Chat 요청은 Message 배열과 max_tokens, Temperature 같은 Parameter를 포함합니다.
InputProcessor는 Chat Template과 Tokenizer를 적용해 Prompt Token ID를 만들고,
허용된 Context 길이와 Parameter 형식을 검사한 뒤 Engine Request로 넘깁니다.
이 시점의 Request는 아직 GPU 결과가 아니라 앞으로 처리할 입력과 생성 조건을 담은 작업 명세입니다.

여기서 Model Registry는 Model Configuration에 맞는 구현을 선택합니다.
특정 Model 이름이 항상 다른 Architecture Class로 치환된다고 외우기보다,
설치한 vLLM Version의 Registry와 지원 목록에서 실제 Mapping을 확인해야 합니다.

Registry는 Model Configuration의 Architecture 이름을 vLLM 구현 Class와 연결합니다.
같은 Repository 이름이라도 Revision의 config.json이 달라질 수 있고,
Remote Model Code나 별도 Backend가 필요한 경우도 있으므로 이름만 보고 지원을 추론하지 않습니다.

2. LLMEngine과 EngineCore

LLMEngine은 바깥 API와 Core 실행 경로를 연결합니다.
새 Request를 EngineCore에 전달하고,
Core에서 돌아온 결과를 OutputProcessor에 넘겨 사용자 응답으로 조립합니다.

EngineCore 안에는 Scheduler, KV Cache Manager, Executor가 있습니다.
Scheduler는 이번 Iteration에서 실행할 Request와 Token 수를 정하고,
KV Cache Manager는 필요한 Block을 확보하며,
Executor는 결정된 작업을 Worker에 보냅니다.

세 Component의 관계를 요청 관점에서 다시 쓰면 다음과 같습니다.

Scheduler       이번에 계산할 Request와 Token 수 결정
KV Cache Manager 필요한 KV Block의 할당·재사용 가능 여부 확인
Executor        결정된 실행을 Local 또는 Distributed Worker에 전달

Scheduler가 512 Token을 실행하고 싶어도 KV Block을 확보하지 못하면 그대로 실행할 수 없습니다.
반대로 Memory가 남아도 이번 Iteration의 Token Budget이 소진되면 다음 Request는 기다립니다.
Compute Scheduling과 Memory Allocation이 맞물리지만 같은 제한은 아닙니다.

OutputProcessor를 EngineCore 안에 넣어 그리면 책임 경계가 흐려집니다.
vLLM v0.28.0에서 출력 조립과 Stop 조건 처리는 LLMEngine 쪽에 있고,
EngineCore는 Scheduling과 Model Execution 결과를 반환하는 데 집중합니다.

3. Worker와 ModelRunner

GPU Worker는 Device를 초기화하고,
ModelRunner를 통해 Model Forward와 Sampling에 필요한 연산을 실행합니다.
Tensor Parallel을 사용한다면 Rank마다 Worker가 생기며,
각 Worker는 자기 Weight Shard와 KV Cache를 관리합니다.

Tensor Parallel은 하나의 Model Layer 계산과 Weight를 여러 Device에 나누는 방식입니다.
각 Worker를 Rank라고 부르며,
Rank는 자신이 맡은 Weight 조각을 계산한 뒤 다른 Rank와 Collective Communication으로 결과를 맞춥니다.
GPU를 늘리면 Model을 나눠 담을 수 있지만 Communication과 동기화 비용도 추가됩니다.

예를 들어 하나의 API Server가 Data Parallel 1개와 Tensor Parallel 4개를 사용한다면,
개념적으로 API Process 1개, EngineCore Process 1개, GPU Worker 4개가 분리됩니다.
정확한 Process 수는 Deployment Mode와 Parallelism 설정에 따라 달라지므로,
“GPU 네 장이면 Process도 네 개”처럼 단순화하면 안 됩니다.

4. 결과가 다시 사용자에게 돌아오기까지

Worker의 ModelRunner가 만든 결과는 EngineCore Output으로 모이고,
LLMEngine의 OutputProcessor가 Token과 Stop 조건을 해석합니다.
Streaming 요청이라면 새로 확정된 Token이 API Server를 통해 Client로 전달되고,
아직 끝나지 않은 Request는 다음 Scheduling Iteration에 다시 들어갑니다.

첫 Iteration에서 긴 Prompt의 Prefill이 끝났다고 요청 전체가 끝나는 것은 아닙니다.
두 번째 Iteration부터는 새 Token을 만들고 Stop Token이나 최대 길이에 도달할 때까지 같은 경로를 반복합니다.
Client가 연결을 끊거나 요청을 취소하면 남아 있는 Queue 상태와 KV Block도 정리되어야 합니다.
정상 응답뿐 아니라 취소와 Worker 실패까지 포함해야 Request Lifecycle을 끝까지 설명한 것입니다.

이 흐름에서 중요한 것은 한 요청이 한 번의 Forward로 끝나지 않는다는 점입니다.
Prefill 이후 Decode Request는 매 Iteration마다 Scheduler, KV Cache, Worker 경로를 반복합니다.
그래서 Queue와 Cache State를 관리하는 EngineCore가 Serving 성능의 중심에 놓입니다.

Process를 나누면 생기는 비용

Process 분리는 API 처리와 GPU 실행을 격리하고 여러 Parallel Mode를 구성하기 쉽게 만듭니다.
대신 Request와 Tensor Metadata를 전달하는 IPC,
Process Failure 감지, Shared Memory 용량, Worker Lifecycle이라는 운영 비용이 생깁니다.

Architecture Diagram만 보고 실제 병목을 단정할 수는 없습니다.
Queue Time, Scheduler Time, Model Forward, Sampling, IPC를 같은 Trace나 Metric Timeline에서 비교해야,
어느 경계가 느린지 구분할 수 있습니다.

예를 들어 TTFT가 길어도 API Tokenization, Scheduler Queue, 긴 Prefill과 IPC 중 원인은 다를 수 있습니다.
GPU Utilization 하나만 보고 Runtime이 느리다고 결론 내리지 않고,
Request ID가 API 수신부터 첫 Token 전송까지 지나간 구간을 같은 Trace에서 연결해야 합니다.

이제 Component 경로는 보이지만 EngineCore가 매 Iteration마다 어떤 Request를 고르는지는 아직 남았습니다.
다음 블록에서는 RUNNING·WAITING Queue와 Token Budget을 실제 숫자로 계산합니다.

참고한 1차 자료



🚀 Step 03. Scheduler는 이번 Iteration에 계산할 Token을 고른다

RUNNING·WAITING Request 사이에서 제한된 Token Budget과 KV Block을 어떻게 나눌까?

Continuous Batching에서 Batch는 요청이 끝날 때까지 고정되지 않습니다.
Scheduler는 매 Iteration마다 현재 Request 상태와 KV Cache 여유를 보고,
이번 Forward에 포함할 Token 수를 다시 정합니다.

Iteration은 Scheduler가 실행 묶음을 만들고 Model Forward를 한 번 수행하는 반복 단위입니다.
RUNNING Queue에는 이미 실행을 시작해 계속 Token이 필요한 Request가 있고,
WAITING Queue에는 아직 실행을 시작하지 못했거나 다시 Scheduling되기를 기다리는 Request가 있습니다.
Queue 이름은 사용자 요청의 성공·실패가 아니라 현재 Scheduler 상태를 나타냅니다.

남은 Token부터 계산한다

vLLM v0.28.0 Scheduler의 핵심 계산을 학습용으로 단순화하면 다음과 같습니다.

remaining = num_tokens_with_spec
          + num_output_placeholders
          - num_computed_tokens

scheduled = min(remaining, long_prefill_threshold, token_budget)

long_prefill_threshold가 0이면 제한을 사용하지 않는 것으로 봅니다.
실제 구현에는 Encoder Input, Grammar, KV Cache Allocation, Preemption 같은 조건이 더 들어가지만,
먼저 “아직 계산하지 않은 Token 수”가 출발점이라는 사실을 잡는 데 유용한 식입니다.

각 항목을 풀면 다음과 같습니다.

  • num_tokens_with_spec는 Prompt와 현재 생성 결과에 Speculative Candidate를 포함한 Token 수입니다.
  • num_output_placeholders는 앞으로 계산해야 할 Output 자리를 반영합니다.
  • num_computed_tokens는 이미 Model 계산을 끝낸 Token 수입니다.
  • long_prefill_threshold는 긴 Prefill 하나가 이번 반복에 가져갈 수 있는 양을 제한합니다.
  • token_budget은 이번 Iteration 전체에서 아직 배정할 수 있는 Token 수입니다.

Decode Request가 총 11개 Token 상태를 가지고 있고 10개를 계산했다면 남은 것은 1개입니다.
반면 4,096 Token Prefill을 시작하는 Request에 Threshold 512가 적용되면,
전역 Budget이 1,024여도 이번 Iteration에는 최대 512개만 배정됩니다.

Prefix Cache Hit로 2,048개 중 1,536개를 이미 계산한 상태라면 남은 것은 512개입니다.
Scheduler가 입력 길이 전체를 다시 배정하는 것이 아니라,
이미 계산된 Prefix를 제외한 Delta를 다룬다는 뜻입니다.

세 Request를 하나의 Budget 1,024에 넣어 보겠습니다.

순서Request 상태요청량배정 뒤 남은 Budget
1B의 Decode 1 Token11,023
2C의 Decode 1 Token11,022
3A의 Long Prefill, Threshold 512512510

남은 510 Token은 다른 Request에 쓸 수 있지만,
필요한 KV Block이나 Scheduling 조건을 만족하는 Request가 없다면 반드시 모두 채워지는 것은 아닙니다.
Token Budget은 가능한 계산량의 상한이지 GPU 사용률을 보장하는 예약량이 아닙니다.

RUNNING을 먼저 보되 보장을 과장하지 않는다

vLLM v0.28.0 Scheduler는 먼저 RUNNING Request를 순회한 뒤,
남은 Budget이 있을 때 WAITING Request를 Scheduling합니다.
이 순서는 실행 흐름을 이해하는 출발점이지만,
모든 Workload에서 완전한 Fairness나 최적의 Tail Latency를 보장한다는 뜻은 아닙니다.

RUNNING Request가 많은 Token을 계속 소비하면 새 Request의 TTFT가 밀릴 수 있습니다.
Long Prefill을 Chunk로 나누는 이유는 하나의 긴 Prompt가 Budget을 독점하지 않게 만들고,
Decode와 섞을 여지를 만들기 위해서입니다.

KV Cache Block을 확보하지 못하면 Budget이 남아도 그대로 실행할 수 없습니다.
Scheduler는 필요한 Block을 할당하거나,
다른 Request를 Preempt하거나,
이번 Iteration의 Scheduling을 중단해야 합니다.
Compute Budget과 Memory Capacity는 서로 다른 제한입니다.

Preemption은 다른 Request에 자원을 내주기 위해 실행 중인 Request를 Scheduler에서 물리는 일입니다.
KV 상태를 CPU Memory로 옮기거나 나중에 다시 계산하는 방식처럼 처리 전략에 따라 비용이 달라질 수 있습니다.
Preemption 횟수만이 아니라 그 뒤 발생한 Recompute, Queue 지연과 TTFT 변화를 함께 봐야 합니다.

SchedulerOutput은 실행 계약이다

Scheduler가 고른 결과에는 Scheduled Request, Token 수, 새 Cache Block과 같은 실행 정보가 담깁니다.
Executor와 Worker는 이 결정을 받아 Tensor를 준비하고 Model Forward를 수행합니다.

성능을 읽을 때는 다음 네 층을 분리하면 편합니다.

  1. Admission: 요청을 받을지, 어느 Replica로 보낼지 정합니다.
  2. Scheduling: 이번 Iteration의 Request와 Token 수를 정합니다.
  3. Memory: KV Cache Block을 할당하고 재사용하거나 회수합니다.
  4. Execution: Model Runner와 Kernel이 실제 Tensor 연산을 수행합니다.

이 네 층은 vLLM이 공식적으로 고정한 불변 계층이 아니라,
병목을 읽기 위한 학습용 관점입니다.
Queue가 길다고 곧바로 Scheduler Algorithm만 바꾸지 않고,
Admission, Cache Capacity, Model Forward 중 실제 제한이 어디인지 확인하게 해줍니다.

무엇을 측정할까

Scheduler 변경을 평가할 때 평균 Throughput 하나만 보면 Tail Latency를 놓칠 수 있습니다.
TTFT, TPOT, Inter-token Latency, Queue Time, Preemption, Cache Usage를 Prompt·Output 길이와 동시성별로 함께 봐야 합니다.

Speculative Token은 한 Request의 remaining을 늘릴 수 있지만,
그 Token이 모두 사용자에게 확정된다는 뜻은 아닙니다.
Scheduling된 Token 수와 최종 Accepted Token 수를 구분해야 Speculative Decoding의 실제 이득을 계산할 수 있습니다.

harness/calculation-checks/ch08.py는 위 식의 Decode, Long Prefill, Prefix Hit와 Budget 고갈 예제를 외부 Package 없이 검산합니다.
M1 Air에서도 실행할 수 있지만 실제 vLLM Queue나 GPU Kernel을 실행하지 않으므로 Runtime Evidence는 아닙니다.

vLLM의 실행 경로와 Scheduling을 이해했으니 이제 다른 Framework를 같은 상자에 억지로 맞추지 않고 비교할 수 있습니다.
먼저 TensorRT-LLM의 현재 Backend 구조가 과거의 Engine Build 설명과 어떻게 달라졌는지 살펴봅니다.

참고한 1차 자료



🚀 Step 04. TensorRT-LLM을 예전 Engine Build 흐름으로만 보면 안 된다

TensorRT-LLM을 설명할 때 Checkpoint 변환 → trtllm-build → TensorRT Engine 실행 흐름이 자주 등장합니다.
이 설명은 과거 Backend에는 맞았지만,
이 글이 고정한 Pre-release 1.3.0rc24의 구조에는 그대로 적용되지 않습니다.

여기서 Engine은 최적화된 실행 Graph와 Kernel 선택을 묶은 과거 TensorRT 실행 Artifact를 뜻합니다.
Backend는 Model을 어떤 Runtime 경로로 실행할지 결정하는 구현입니다.
프로젝트 이름과 오래된 Tutorial만 보면 TensorRT Engine을 미리 Build하는 단계가 항상 필요하다고 오해하기 쉽습니다.

TensorRT Engine Backend는 제거됐다

2026-09-05에 다시 확인한 TensorRT-LLM 1.3.0rc24 문서는 기존 TensorRT Engine Backend를 제거했고,
PyTorch Backend를 유일한 실행 Backend로 사용합니다.
따라서 현재 시작 경로를 설명하면서 trtllm-build와 별도 Engine 변환을 필수 단계처럼 넣으면 오래된 안내가 됩니다.

PyTorch Backend라는 이름도 일반적인 Hugging Face Model을 아무 설정 없이 실행한다는 뜻은 아닙니다.
TensorRT-LLM이 제공하는 Model Definition과 Executor,
NVIDIA GPU용 Attention·Collective·Quantization Kernel을 PyTorch 생태계와 결합한 실행 경로로 이해해야 합니다.

이름에 TensorRT가 남아 있다고 해서 예전 Engine Build Workflow가 유지된다고 추론하면 안 됩니다.
현재 TensorRT-LLM은 PyTorch Frontend와 Runtime을 통해 NVIDIA GPU 최적화 경로를 제공하고,
Model Definition, Distributed Execution, KV Cache, Attention Backend, CUDA Graph 같은 요소를 조합합니다.

CUDA Graph는 반복되는 GPU 작업의 Launch 순서를 Capture해 CPU Launch Overhead를 줄이는 기능입니다.
입력 Shape와 실행 경로가 계속 달라지면 Graph 재사용 조건이 제한될 수 있으므로,
기능을 켰다는 사실과 실제 요청이 Capture된 Graph를 사용했다는 사실을 구분해야 합니다.

최적화는 하나의 Compile 결과가 아니다

TensorRT-LLM을 선택하는 이유는 단순히 Model 파일 형식을 바꾸기 위해서가 아닙니다.
NVIDIA GPU에서 사용할 Kernel과 Communication 경로,
Quantization과 KV Cache 기능,
Multi-GPU Serving 구성을 함께 다루기 위해서입니다.

다만 Framework에 기능 이름이 있다고 해서 모든 조합이 같은 수준으로 지원되는 것은 아닙니다.
Model Architecture, GPU Generation, Data Type, Attention Backend, Parallelism을 조합하면 지원 범위가 달라집니다.
공식 Feature Combination Matrix와 Supported Hardware 문서를 선택 전에 같이 확인해야 합니다.

Matrix의 행 하나만 확인하는 것도 충분하지 않습니다.
예를 들어 Model은 지원되지만 선택한 Quantization과 Tensor Parallel 조합이 지원되지 않을 수 있고,
GPU Architecture는 지원되지만 특정 Attention Feature가 제한될 수 있습니다.
Model × dtype × Quantization × GPU × Parallelism × Feature의 교차점이 실제 배포 조합입니다.

예를 들어 FP8이나 특정 Low-precision 경로가 문서에 등장하더라도,
현재 보유한 GPU와 Model에서 그대로 쓸 수 있다고 가정해서는 안 됩니다.
실행 가능한 조합과 성능상 유리한 조합도 서로 다릅니다.

NVIDIA 중심의 장점과 경계

TensorRT-LLM은 NVIDIA Hardware와 CUDA Software Stack을 함께 최적화하려는 팀에 자연스러운 후보입니다.
새 GPU 기능과 NVIDIA Runtime 경로를 빠르게 활용할 수 있는 대신,
Driver, CUDA, Container, Model Support Matrix를 맞추는 책임이 커집니다.

M1 Air는 이 실행 경로의 대상 Hardware가 아닙니다.
Apple Metal에서 llama.cpp가 실행된다는 결과로 TensorRT-LLM의 설치 가능성이나 성능을 추론할 수 없습니다.
로컬에서는 Architecture 문서와 Feature Matrix를 읽어 배포 조합을 작성할 수 있고,
실제 기능·성능 검증은 지원되는 NVIDIA GPU와 고정된 Container Digest에서 수행해야 합니다.

이 책임은 단순 설치 문제로 끝나지 않습니다.
Framework Upgrade가 Model Output, Quantization Accuracy, Memory Footprint, Startup Time에 어떤 영향을 주는지 회귀 검증해야 합니다.
Release Candidate를 사용한다면 Production 채택 전에 API 안정성과 Rollback Image도 따로 준비해야 합니다.

과거 기록을 읽는 방법

이 챕터를 위해 수집한 자료에는 1.3.0rc24 Container를 실행한 Transcript가 남아 있습니다.
Container Pull과 응답 예시는 당시 환경이 명령을 수행했다는 기록일 뿐,
현재 Hardware에서의 Throughput이나 vLLM 대비 우위를 증명하지 않습니다.

또한 Container Tag만 남기면 나중에 같은 Image를 보장하기 어렵습니다.
재실행할 때는 Image Digest, Driver, GPU, Model Revision, Command, Warm-up과 원본 결과를 함께 기록해야 합니다.

TensorRT-LLM을 “가장 빠른 Framework”로 고르는 대신,
NVIDIA 중심 Stack에서 필요한 Model과 기능 조합이 공식 Matrix에 있는지 먼저 확인하고,
같은 SLO 계약으로 직접 측정한 뒤 채택하는 편이 안전합니다.

TensorRT-LLM은 NVIDIA Hardware 조합을 깊게 최적화하는 선택을 보여 줍니다.
다음 SGLang은 Hardware 이름보다 반복되는 Prefix와 구조화된 LM Program이라는 Workload 모양에서 출발합니다.

참고한 1차 자료



🚀 Step 05. SGLang은 반복되는 Prefix와 생성 제약을 실행 단위로 본다

LLM Application은 한 번의 Prompt와 한 번의 응답으로만 끝나지 않습니다.
System Prompt를 공유하는 여러 요청,
Tree처럼 분기하는 Reasoning,
형식이 정해진 JSON 생성,
여러 번의 Model 호출이 하나의 Program 안에서 이어질 수 있습니다.

SGLang은 이런 구조를 Language Model Program의 실행 문제로 보고,
Runtime에서 Prefix와 Generation 제약을 함께 다룹니다.
본문의 기능 설명은 Release v0.5.18에 고정했습니다.
2026-09-05에는 v0.5.19가 새로 발행된 상태이므로,
본문의 Version Pin과 실제 실습 Version을 섞지 않고 발행 전에 다시 선택합니다.

여기서 Language Model Program은 Model 호출 하나가 아니라,
여러 Prompt·Generation·분기와 외부 Logic이 순서와 의존성을 가진 작업을 뜻합니다.
같은 System Prompt에서 여러 후보를 만들거나,
앞 단계의 결과를 다음 Prompt에 붙이는 Workflow에서는 공통 Prefix와 생성 제약이 반복됩니다.

Radix 기반 Prefix Reuse

여러 요청이 같은 앞부분을 공유하면 매번 전체 Prefix를 Prefill하는 것은 낭비입니다.
SGLang의 RadixAttention은 KV Cache를 Prefix Tree 형태로 관리해,
공유되는 Prefix를 찾아 재사용할 수 있게 합니다.

Radix Tree는 공통 Prefix를 하나의 경로로 합치고 달라지는 지점에서 Branch를 나누는 자료 구조입니다.
각 Edge가 Token Sequence 일부를 나타내면 긴 공통 System Prompt를 여러 Request가 한 번만 보관할 수 있습니다.
새 요청은 Root부터 Token을 비교하다가 일치가 끝나는 지점 이후만 새로 Prefill합니다.

공통 System Prompt
├─ 사용자 질문 A
│  └─ 후속 질문 A-1
└─ 사용자 질문 B
   └─ 후속 질문 B-1

위 구조에서는 Root에 가까운 Prefix를 여러 요청이 공유합니다.
Reuse가 실제 이득이 되려면 Prefix가 충분히 길고 반복되어야 하며,
Cache가 Eviction되기 전에 후속 요청이 도착해야 합니다.

“Radix Tree가 있다”는 사실만으로 Hit가 생기지는 않습니다.
Chat Template이나 System Prompt가 조금만 달라도 Token Prefix가 달라지고,
Routing이 동일 Cache를 가진 Worker로 요청을 보내지 못하면 재사용 경로가 끊깁니다.

처음의 긴 문서 요청에서 질문만 바뀐 후속 요청이 같은 Worker에 도착하면 문서 구간까지 Hit할 수 있습니다.
하지만 문서 앞에 매번 다른 Request ID나 Timestamp를 넣으면 Tree가 앞부분에서 갈라집니다.
Cache를 위해 의미 있는 입력을 제거할 필요는 없지만,
불필요하게 흔들리는 Template과 문서 정렬을 안정화할 수 있는지는 점검할 수 있습니다.

Cache에는 용량 상한이 있으므로 모든 Branch를 영원히 보존할 수 없습니다.
오래 사용하지 않은 Node가 Eviction되거나 Worker가 재시작되면 다시 Prefill해야 합니다.
따라서 Cache Hit Rate뿐 아니라 Hit한 Token 수, Eviction, Worker별 Queue와 Warm-up 이후 분포를 함께 봅니다.

Structured Output도 Runtime 비용이다

JSON Schema나 정규식에 맞춘 출력은 생성 뒤에 문자열을 검사하는 문제만은 아닙니다.
Runtime은 매 Token에서 허용 가능한 후보를 제한하면서,
요청한 형식을 위반하지 않도록 Decode해야 합니다.

예를 들어 {"answer": string, "confidence": number}라는 Schema를 요구하면,
문자열을 생성한 뒤 JSON Parse에 실패했는지 확인하는 대신 각 Token 시점에 현재 상태에서 허용되는 후보를 계산할 수 있습니다.
Grammar Compile은 Schema나 정규식을 Token 수준의 허용 규칙으로 바꾸는 과정이고,
Token Masking은 현재 규칙을 위반하는 후보의 선택을 막는 과정입니다.

이 방식은 Application의 재시도와 Parsing 실패를 줄일 수 있습니다.
대신 Grammar Compile, Token Masking, 복잡한 Schema 처리 비용이 생기므로,
자유 생성과 같은 Throughput을 가정하면 안 됩니다.

형식에 맞는 JSON이 나왔다는 사실도 내용이 정확하다는 뜻은 아닙니다.
구문 유효성은 Runtime이 도울 수 있지만 Field 의미, 범위와 업무 규칙은 Application이 별도로 검증해야 합니다.
Streaming 중간 Chunk는 아직 완성된 JSON이 아닐 수 있으므로 Client가 부분 응답을 어떻게 처리할지도 계약에 포함합니다.

OpenAI-compatible API를 제공하더라도 Structured Output의 지원 범위와 Semantics는 Version마다 달라질 수 있습니다.
사용할 Schema, Streaming, Tool Call 조합을 실제 Client Contract로 테스트해야 합니다.

Framework 기능과 Hardware 지원을 분리한다

SGLang은 Prefix Reuse, Structured Output, Parallelism, 여러 Attention Backend를 폭넓게 제공합니다.
그러나 모든 Model과 Accelerator에서 같은 Feature Set과 Performance를 보장하는 것은 아닙니다.

채택 전에는 다음 순서가 필요합니다.

  1. 사용할 Model과 Revision이 지원되는지 확인합니다.
  2. GPU와 Driver에서 필요한 Backend가 동작하는지 확인합니다.
  3. Prefix Reuse나 Structured Output을 켠 실제 Traffic 분포를 재현합니다.
  4. TTFT, TPOT, Cache Hit, Error와 Output Correctness를 함께 측정합니다.

수집 자료에는 외부 서비스가 SGLang 도입 뒤 몇 배 빨라졌다는 사례가 포함되어 있습니다.
하지만 Model, Hardware, Traffic, Baseline이 같은지 확인할 수 없으므로,
그 비율을 우리의 기대값으로 사용하지 않습니다.

SGLang은 반복 Prefix와 Program 구조가 뚜렷한 Workload에서 검토할 가치가 있습니다.
결정은 기능 목록이 아니라 실제 Prefix Hit와 구조화 출력의 정확성,
그리고 운영할 Hardware 조합에서의 회귀 결과로 내려야 합니다.

M1 Air에서는 RadixAttention의 Datacenter GPU 성능을 재현하기보다,
동일 Prefix와 흔들리는 Prefix의 Token 배열을 비교하고 구조화 출력 Contract Test를 작성할 수 있습니다.
실제 Cache Hit와 Throughput 주장은 SGLang이 지원되는 실행 환경에서 Metric과 Trace를 남긴 뒤 추가합니다.

반복 Program이 중요한 Runtime을 봤다면 이제 GPU Server가 없는 개인 환경으로 경계를 옮깁니다.
llama.cpp가 Datacenter Framework의 축소판이 아니라 Local·Edge를 위한 다른 선택인 이유를 살펴봅니다.

참고한 1차 자료



🚀 Step 06. llama.cpp는 GPU Server 밖의 실행 경계를 채운다

vLLM과 TensorRT-LLM이 GPU Datacenter의 높은 동시성을 주로 다룬다면,
llama.cpp는 CPU, Apple Silicon, Consumer GPU, 여러 Accelerator Backend를 포함한 Local·Edge 실행 경로를 제공합니다.
같은 기준으로 단순 순위를 매기기보다 배포 경계가 다르다고 보는 편이 정확합니다.

Datacenter Runtime은 많은 동시 요청과 여러 GPU의 Scheduling을 중요하게 봅니다.
Local Runtime은 한 장치 안에서 Model이 실제로 들어가는지,
외부 Network 없이 실행할 수 있는지,
설치와 배포가 단순한지를 더 중요하게 볼 수 있습니다.
같은 TTFT나 Token/s 숫자라도 목표와 Hardware가 다르면 직접적인 순위가 되지 않습니다.

llama.cpp는 고정된 장기 Release보다 Build가 계속 쌓이는 Rolling Project입니다.
본문의 실행 경로는 Build b10690, Commit bdf3955에 고정했습니다.
2026-09-05에는 별도의 Release v0.4.0도 발행된 상태이므로,
실습에서는 “최신” 대신 실제 Commit이나 Container Digest를 고정해야 합니다.

Rolling Project에서는 Release 이름만 고정해도 Dependency와 Build Option에 따라 실행물이 달라질 수 있습니다.
Commit, Compiler, Build Flag와 Model Hash를 함께 남겨야 다음 실행이 같은 조건에 가까워집니다.

GGUF와 실행 Backend

llama.cpp는 GGUF 형식의 Model을 사용하고,
CPU와 GPU에 Layer를 나누거나 지원되는 Backend를 통해 연산을 실행합니다.
Low-bit와 Mixed Quantization은 Memory Footprint를 줄여 더 작은 장치에서 Model을 실행할 여지를 만듭니다.

GGUF는 Model Tensor와 Tokenizer·Architecture Metadata를 하나의 실행 Artifact에 담는 형식입니다.
Backend는 CPU, Metal, CUDA처럼 실제 연산을 수행할 장치 경로이고,
Offload는 일부 Layer를 CPU가 아닌 Accelerator Memory와 Compute에서 실행하도록 옮기는 방식입니다.
M1에서는 CPU와 GPU가 통합 Memory를 공유하므로 별도 VRAM 카드와 같은 방식으로 해석하지 않습니다.

Quantization Bit 수만으로 품질과 속도를 예측할 수는 없습니다.
Model별 Calibration, Quant Type, Backend Kernel, Offload Layer, Context 길이에 따라 결과가 달라집니다.
Artifact 이름뿐 아니라 원본 Model Revision, 변환 도구, Quantization 방식과 Hash를 같이 남겨야 합니다.

M1 Air 16GB에서 처음 실습할 때는 큰 Model의 최대 Context부터 시도하지 않습니다.
작은 1B~4B급 Instruct Model의 검증된 GGUF와 짧은 Context로 Server 시작·한 요청·종료 경로를 먼저 확인하는 편이 안전합니다.
더 큰 Model이나 Context는 Weight뿐 아니라 KV Cache와 Runtime Buffer도 통합 Memory를 사용하므로,
Memory Pressure와 Swap이 안정적인 범위에서 한 단계씩 늘립니다.
이는 실행 순서에 대한 보수적인 기준이며 특정 Model의 성능 보장은 아닙니다.

llama-server와 Python Binding은 다른 Project다

공식 llama.cpp Repository에는 OpenAI-compatible HTTP Endpoint를 제공하는 llama-server가 있습니다.
반면 Python에서 from llama_cpp import Llama로 사용하는 Package는 별도 Project인 llama-cpp-python Binding입니다.

두 Project는 Version과 Build Option, Backend 설치 방법이 다릅니다.
Python 예제를 llama.cpp 자체 API처럼 소개하면 설치와 장애 원인을 잘못 추적하게 됩니다.
실습에서는 C/C++ Runtime인지 Python Binding인지 먼저 적어야 합니다.

공식 llama-server를 직접 실행하면 C/C++ Runtime의 CLI와 Server 동작을 검증합니다.
llama-cpp-python을 사용하면 Python Wheel이 어떤 Backend Option으로 Build됐는지까지 Dependency가 늘어납니다.
문제가 생겼을 때 Model Artifact, Native Runtime, Python Binding 중 어느 층인지 구분하려면 첫 Smoke Test는 경로를 하나만 선택하는 편이 좋습니다.

Local은 Privacy의 충분조건이 아니다

Model을 Laptop에서 실행하면 Prompt가 외부 Inference API로 전송되지 않게 구성할 수 있습니다.
하지만 Local이라는 단어만으로 Privacy가 보장되지는 않습니다.

Server가 0.0.0.0에 열려 있는지,
인증 없이 같은 Network에서 접근할 수 있는지,
Prompt가 Log에 남는지,
Crash Report나 Telemetry가 있는지,
Model Download와 Cache 경로가 적절한지 확인해야 합니다.

Privacy 요구가 있다면 Loopback Bind, 접근 제어, Log Redaction, Model Artifact 출처와 저장 위치를 하나의 운영 Checklist로 관리합니다.

언제 검토할까

llama.cpp는 다음 조건에서 좋은 후보가 될 수 있습니다.

  • Internet 연결 없이 Device 안에서 실행해야 합니다.
  • CPU나 Apple Silicon, 제한된 VRAM을 활용해야 합니다.
  • 동시성보다 배포 단순성과 Local Latency가 중요합니다.
  • GGUF와 Quantization으로 허용 가능한 품질을 얻을 수 있습니다.

M1 실습의 최소 성공 조건은 다음과 같이 잡을 수 있습니다.

  1. GGUF Hash와 llama.cpp Commit을 기록합니다.
  2. Server가 127.0.0.1에만 Bind됐는지 확인합니다.
  3. 고정 Prompt에서 첫 Token과 종료까지의 시간을 기록합니다.
  4. Activity Monitor로 Memory Pressure와 Swap 증가를 확인합니다.
  5. Server를 종료한 뒤 Port와 Process가 남지 않았는지 확인합니다.

이 결과는 Local 실행 가능성과 API 경로를 보여 주지만,
vLLM·TensorRT-LLM·SGLang의 CUDA Throughput 비교 결과는 아닙니다.

반대로 많은 Tenant의 높은 동시성과 복잡한 Multi-GPU Scheduling이 핵심이라면,
Datacenter 중심 Runtime과 같은 운영 모델을 기대하기 어렵습니다.
작은 Hardware에서 “실행 가능하다”와 Production SLO를 만족한다는 판단도 분리해야 합니다.

이제 네 Framework의 특징을 알았지만 아직 선택은 끝나지 않았습니다.
마지막 블록에서는 우리 SLO와 Workload를 먼저 고정하고,
성능뿐 아니라 운영성과 다른 Runtime으로 빠져나오는 비용까지 평가 계약에 넣습니다.

참고한 1차 자료



🚀 Step 07. Framework의 승자 대신 평가 계약을 만든다

기능과 Benchmark가 계속 바뀌는 Framework를 어떤 조건으로 비교하고 언제 다시 평가할까?

Framework 비교표는 빠르게 만들 수 있지만 오래 유지되기 어렵습니다.
각 Project가 같은 이름의 기능을 추가해도 지원 Model과 Hardware,
기본값과 안정화 수준이 다르기 때문입니다.

먼저 제품 이름을 지우고 필요한 실행 계약을 적는 편이 좋습니다.

평가 계약은 Candidate를 실행하기 전에 비교 조건과 성공 기준을 고정한 문서입니다.
무엇을 같은 조건으로 맞출지,
어떤 차이는 Framework의 권장 경로로 허용할지,
어느 SLO를 넘으면 탈락할지 먼저 정합니다.
결과를 본 뒤 유리한 지표만 고르는 일을 막는 역할도 합니다.

1. SLO를 숫자로 적는다

“빠른 응답” 대신 TTFT p95, TPOT p95, 최소 Throughput, Error Budget을 적습니다.
Streaming 여부와 Timeout, Output 길이도 포함해야 합니다.
TTFT가 중요한 Chat과 Batch Throughput이 중요한 Offline Generation은 같은 Winner를 만들지 않을 수 있습니다.

처음의 긴 문서 Chat이라면 예시 SLO를 다음처럼 적을 수 있습니다.

first token: TTFT p95 <= service target
streaming: TPOT p95 <= service target
correctness: valid JSON and domain checks pass
reliability: timeout and error rate stay inside error budget

여기서는 숫자를 임의로 채우지 않습니다.
실제 사용자 기대, 기존 Baseline과 비용 한도를 보고 서비스가 소유할 값을 정해야 하기 때문입니다.

2. Workload 분포를 보존한다

평균 Prompt 하나로 측정하면 Long Context와 짧은 Decode가 충돌하는 상황을 놓칩니다.
실제 Prompt·Output 길이 분포, 동시성, 도착 간격, Sampling, Prefix 반복률, Structured Output 비율을 Fixture로 남깁니다.

Cache Benchmark는 Cold와 Warm을 분리합니다.
Warm 결과만 보고 Production 성능을 예상하면 Cache Miss와 Worker 재시작 비용을 숨기게 됩니다.

Fixture에는 평균값만 넣지 않고 요청 종류별 비율을 보존합니다.
예를 들어 짧은 Chat 60%, 긴 문서 Prompt 30%, Structured Output 10%처럼 Traffic Mix를 기록하고,
각 Group의 Prompt·Output 길이 분포와 Prefix 공유 여부를 별도로 남깁니다.
그래야 Framework가 특정 Group만 빠르게 처리해 전체 평균을 개선한 경우도 확인할 수 있습니다.

3. Hardware와 Software를 하나의 조합으로 고정한다

GPU 이름만으로는 재현 조건이 충분하지 않습니다.
GPU 수와 Memory, Interconnect, Driver, CUDA, Container Digest, Framework Commit, Model Revision, Data Type과 Quantization을 기록합니다.

Framework별로 권장하는 Kernel이 다르면 완전히 같은 내부 경로를 강제할 수는 없습니다.
대신 같은 Model Quality와 API Semantics, Request 분포, SLO를 지키는지 비교합니다.

M1 결과와 NVIDIA 결과는 별도 Track으로 둡니다.
M1 Track은 llama.cpp의 Local 실행, Memory Pressure와 단일 사용자 Latency를 검증하고,
NVIDIA Track은 vLLM·TensorRT-LLM·SGLang의 CUDA Kernel, 동시성, Tensor Parallel과 GPU Memory를 비교합니다.
Hardware가 다른 결과를 같은 표의 단일 순위로 합치지 않습니다.

4. Operability를 성능과 같은 표에 넣는다

최고 Throughput이 조금 높아도 Upgrade마다 Model별 회귀가 발생하고,
Metric과 Trace가 부족하며,
Worker 장애 복구가 불명확하다면 운영 비용이 더 클 수 있습니다.

다음 항목을 별도 점수로 기록합니다.

  • 설치와 Upgrade, Rollback에 필요한 작업
  • Model과 Hardware Support Matrix의 명확성
  • Queue, Cache, Scheduler, Worker를 관찰할 수 있는 Metric과 Trace
  • Graceful Shutdown과 Request Drain
  • Security Patch와 Release 정책
  • 팀이 Debugging할 수 있는 Source와 문서 품질

관찰 가능성은 Metric이 존재하는지만 확인하지 않습니다.
한 Request ID로 Gateway Queue, Runtime Scheduling, KV Cache, Worker와 Streaming 응답을 연결할 수 있는지 봅니다.
Worker가 죽었을 때 진행 중인 요청이 실패하는 방식과 새 Replica가 Traffic을 받기까지의 시간도 작은 장애 실험으로 확인합니다.

5. Exit Cost를 미리 계산한다

OpenAI-compatible Endpoint를 사용해도 완전한 교체 가능성이 자동으로 생기지는 않습니다.
Chat Template, Tokenizer, Sampling Default, LoRA Loading, Structured Output, Metric 이름은 Framework에 묶일 수 있습니다.

Application Contract Test와 Golden Prompt를 Runtime 밖에 두고,
Model Artifact와 Deployment Configuration을 재생성 가능하게 관리하면 교체 비용을 줄일 수 있습니다.
Framework 전용 기능을 사용할 때는 얻는 이득과 Lock-in을 같은 Decision Record에 남깁니다.

예를 들어 SGLang 전용 Prefix Program API를 Application Code 전반에 직접 사용하면 반복 Prompt 성능은 좋아질 수 있지만 교체 범위가 커집니다.
반대로 내부 Adapter 뒤에 기능을 감추면 교체는 쉬워져도 Framework의 세부 Streaming·Control 기능을 모두 노출하기 어려울 수 있습니다.
Lock-in은 무조건 제거할 대상이 아니라 얻는 이득과 복구 경로를 알고 선택할 비용입니다.

Candidate를 조건부 가설로 적는다

평가 시작점은 다음처럼 쓸 수 있습니다.

Candidate먼저 검토할 조건반드시 확인할 경계
vLLM범용 GPU Serving과 Continuous Scheduling이 필요함Model·Quantization 지원, Cache와 Scheduler 운영성
TensorRT-LLMNVIDIA Stack과 최신 GPU 최적화를 깊게 활용함Version별 Feature·Hardware Matrix, Release 안정성
SGLang반복 Prefix와 구조화된 LM Program 비중이 높음실제 Prefix Hit, Structured Output 정확성, Backend 조합
llama.cppLocal·Edge·CPU·Apple Silicon 경계가 중요함GGUF 품질, 장치별 Backend, 동시성과 보안 설정

이 표는 순위가 아니라 실험을 시작할 가설입니다.
같은 Framework도 Model과 Traffic이 바뀌면 결론이 달라집니다.

재평가는 “3개월마다”처럼 고정된 날짜보다 Trigger로 관리하는 편이 낫습니다.
Model 또는 Quantization 변경, GPU·Driver 교체, Framework Major Upgrade, Traffic 분포 변화, SLO 위반이 생기면 동일한 계약으로 다시 측정합니다.

최종 선택은 가장 많은 기능을 가진 Framework가 아닙니다.
현재 Workload의 병목을 줄이면서도 팀이 관찰하고 복구하고 교체할 수 있는 실행 경로가 선택 대상입니다.

결정문에는 Winner 이름만 남기지 않습니다.
선택한 Workload와 SLO, 탈락한 Candidate의 이유, 수용한 운영 비용, Rollback Artifact와 재평가 Trigger를 함께 남깁니다.
그러면 다음 Version이나 Model에서 조건이 달라졌을 때 처음부터 제품 홍보 자료를 다시 비교하지 않고 같은 계약으로 검증할 수 있습니다.



마치며

이번 챕터에서는 Framework의 기능 목록보다 요청이 실제로 움직이는 경로를 먼저 따라갔습니다.
바깥의 Serving Platform이 Endpoint와 Routing을 맡고,
안쪽의 LLM Runtime이 Token Scheduling, KV Cache, Model Execution을 이어갑니다.

vLLM에서는 API Server가 받은 요청이 LLMEngine을 거쳐 EngineCore로 들어갑니다.
Scheduler가 이번 Iteration의 Token Budget과 KV Cache 조건을 계산하면,
Executor와 GPU Worker가 Model Forward를 수행하고 OutputProcessor가 결과를 다시 사용자 응답으로 만듭니다.

TensorRT-LLM은 현재 PyTorch Backend 중심으로 바뀌었고,
예전 Engine Build 설명을 현재 구조로 사용하면 안 됩니다.
SGLang은 반복 Prefix와 구조화된 LM Program을 실행하는 문제를 드러내며,
llama.cpp는 Local·Edge Hardware와 GGUF 중심의 다른 배포 경계를 채웁니다.

그래서 Framework 선택에는 하나의 정답이 없습니다.
TTFT와 TPOT, Prompt·Output 분포, Prefix 반복률, Structured Output, Hardware, 운영 역량을 먼저 고정해야 합니다.
같은 Benchmark 계약 안에서 Performance뿐 아니라 Upgrade와 Rollback, 관찰 가능성, Security, Exit Cost까지 비교해야 합니다.

채택 뒤에도 Framework 이름을 Application Contract로 만들지 않는 것이 중요합니다.
Tokenizer와 Chat Template, Sampling Parameter, Golden Prompt, Model Artifact를 Runtime 밖에서 관리하면,
새 Version으로 올리거나 다른 Runtime으로 옮길 때 의미가 달라지는지 확인할 수 있습니다.

다음 단계는 더 많은 Framework를 나열하는 일이 아닙니다.
선택한 Runtime에서 Queue, Cache, Scheduler, Kernel 중 실제 병목을 측정하고,
그 병목에 맞는 Configuration을 하나씩 바꾸는 것입니다.

Framework는 최적화를 대신 결정해주는 제품이 아닙니다.
우리가 세운 SLO와 실행 계약을 Hardware 위에서 구현하는 도구이며,
운영 가능한 범위 안에서 그 계약을 가장 잘 지키는 도구를 선택해야 합니다.

처음 접한 독자가 챕터를 다시 확인할 때는 다음 질문에 답할 수 있으면 됩니다.

질문확인할 수 있어야 하는 것
Platform과 Runtime은 왜 다른가?인증·Routing과 Token Scheduling·KV 관리의 책임 차이
vLLM 요청은 어디를 지나는가?API Server, LLMEngine, EngineCore, Worker와 OutputProcessor의 흐름
Scheduler는 무엇에 막히는가?남은 Token, Iteration Budget과 KV Block이라는 서로 다른 제한
Framework별 차이는 어디에서 시작하는가?NVIDIA 최적화, 반복 Prefix·Program, Local·Edge라는 적용 경계
무엇을 근거로 선택하는가?SLO, 실제 Traffic Fixture, Hardware, 운영성, Exit Cost와 재평가 Trigger

M1 Air에서는 Scheduler 계산과 llama.cpp Loopback 실습으로 요청·Server·Memory 경계를 확인할 수 있습니다.
NVIDIA GPU Framework의 Kernel·Tensor Parallel·동시성 성능은 별도의 GPU Reference 결과가 필요합니다.
둘을 하나의 순위로 합치지 않는 것까지가 이번 챕터의 선택 기준입니다.

profile
안녕하세요! DevOps 엔지니어 이재찬입니다. 블로그에 대한 피드백은 언제나 환영합니다! 기술, 개발, 운영에 관한 다양한 주제로 함께 나누며, 더 나은 협업과 효율적인 개발 환경을 만드는 과정에 대해 인사이트를 나누고 싶습니다. 함께 여행하는 기분으로, 즐겁게 읽어주시면 감사하겠습니다! 🚀

0개의 댓글