Field Log · Entry

효율적인 LLM 추론 서빙: Prefill·Decode·KV Cache (9/10)

LLM 요청을 queue prefill decode로 나누고 KV cache scheduler와 batching을 TTFT ITL goodput SLO로 연결하는 추론 서빙 구조

오늘의 결론

  • LLM inference는 한 번의 함수 호출이 아닙니다. Queue, prefill, token-by-token decode, streaming을 분리해야 TTFT와 ITL의 원인을 찾을 수 있습니다.
  • Prefill은 prompt token을 한꺼번에 처리하는 계산 밀도 높은 구간이고, decode는 active sequence마다 매 iteration 한 token을 만드는 memory·scheduling 중심 구간입니다.
  • KV cache는 request 수명과 함께 자라는 동적 memory입니다. PagedAttention, continuous batching, admission control은 결국 이 memory를 낭비 없이 배정하는 문제입니다.
  • FlashAttention, quantization, speculative decoding, prefix caching, prefill/decode disaggregation은 서로 다른 병목을 겨냥합니다. Target workload와 SLO 없이 “몇 배 빠르다”를 옮겨 쓰면 안 됩니다.
  • Production 목표는 최대 token/s가 아니라 TTFT·ITL·완료율 SLO를 지킨 request의 goodput과 비용입니다.

앞 글에서는 학습 state와 compute를 여러 device에 나눴습니다. Serving에서는 request가 계속 도착하고 길이가 제각각이며, 첫 token과 이후 token의 지연 목표도 다릅니다. 그래서 학습용 고정 batch와 다른 scheduler가 필요합니다.

request trace
  → split queue / prefill / decode
  → budget weights and KV cache
  → schedule at every iteration
  → optimize the measured phase
  → admit only SLO-feasible work

LLM request를 queue prefill decode streaming 단계로 나누고 PagedAttention KV block, continuous batching, prefix cache, chunked prefill, FlashAttention, speculative decoding, prefill decode 분리 서빙을 TTFT ITL goodput gate로 연결한 구조

그림 1. Prefill과 decode는 shape·arithmetic intensity·SLO가 다르다. 같은 GPU utilization이라도 queueing, KV pressure, head-of-line blocking에 따라 사용자 지연은 완전히 달라질 수 있다.


요청 한 건의 Timeline부터 분해한다

End-to-end latency에는 model kernel 밖의 시간도 포함됩니다.

client sends request
  → gateway / authentication / rate limit
  → tokenize and validate
  → scheduler queue
  → prefill prompt
  → sample first token
  → repeated decode iterations
  → detokenize / stream / network
  → finish or cancel

Trace에는 적어도 다음 timestamp를 남깁니다.

t_received
t_tokenized
t_admitted
t_prefill_start
t_first_token
t_last_token
t_client_ack  # 가능하다면

Server에서 빠르지만 client가 느린 경우를 분리하려면 server emission과 client observation을 구분합니다.

지연 지표를 정확히 정의한다

Time to First Token

TTFT = t_first_token − t_received

대략 다음 성분입니다.

TTFT
  = gateway + tokenization + queue
  + prefill + first sampling + transport

긴 RAG context는 prefill 시간을 늘리고, burst traffic은 queue 시간을 늘립니다. TTFT 숫자 하나만으로 어느 쪽인지 알 수 없습니다.

Inter-token Latency

인접한 output token emission 사이의 간격입니다.

ITLᵢ = t_token(i) − t_token(i−1)

평균만 보지 않고 p50·p95·p99와 긴 pause를 봅니다. Tool call JSON이 중간에 끊기면 짧은 평균 ITL보다 단 한 번의 긴 stall이 사용자 경험과 parser timeout에 더 해로울 수 있습니다.

Time per Output Token

논문과 engine마다 TPOT 정의가 조금 다릅니다. 흔한 request-level 정의 예시는:

TPOT = (t_last_token − t_first_token) / (output_tokens − 1)

Output token이 1개면 분모가 0이므로 별도 처리합니다. ITL distribution과 TPOT aggregate를 혼용하지 않습니다.

End-to-end Latency

E2E = t_complete − t_received

Output length의 영향을 크게 받습니다. Prompt·output length cohort별로 비교합니다.

Throughput

서로 다른 단위가 있습니다.

request throughput = completed requests / second
input throughput   = prefilled tokens / second
output throughput  = decoded tokens / second
total throughput   = (input + output tokens) / second

Input token과 output token의 비용이 같지 않으므로 total token/s만으로 capacity를 판단하지 않습니다.

Goodput

SLO를 만족하며 정상 완료된 work의 처리량입니다.

request goodput
  = completed requests satisfying all required SLOs / second

TTFT만 만족하고 ITL을 어긴 request를 성공으로 볼지, 둘 다 만족해야 하는지는 product contract로 정합니다.


Prefill은 Prompt를 병렬로 처리한다

Prompt token L개를 model에 넣어 각 layer의 activation과 KV를 계산합니다.

input: tokens x₀ ... x_L₋₁
one forward over many positions
output:
  logits for next token
  K/V cache for every layer and prompt position

큰 matrix multiplication이 충분한 token dimension을 가져 GPU compute를 활용하기 쉽습니다. 하지만 sequence가 길면 causal attention compute와 memory traffic이 커집니다.

Prefill 비용의 주요 축

  • Prompt token 수
  • Model layer·hidden dimension
  • Attention implementation
  • Batch에서 함께 처리한 prompt token 수
  • Prefix cache hit 길이
  • Tensor parallel communication
  • Chunk 크기와 scheduler interference

“Prompt 8K”라고 해도 7K가 prefix cache hit인 요청과 전부 새로 계산하는 요청은 다릅니다.

Decode는 한 Iteration에 한 Token씩 전진한다

Autoregressive decode는 이전 token을 입력해 다음 token 하나를 만듭니다.

iteration 1: x_L     → logits → sample x_L+1
iteration 2: x_L+1   → logits → sample x_L+2
iteration 3: x_L+2   → logits → sample x_L+3

각 active sequence의 query position은 하나지만 모든 layer weight를 읽고 과거 KV cache를 참조합니다. 작은 batch에서는 weight·KV memory traffic에 비해 연산량이 작아 memory bandwidth 병목이 되기 쉽습니다.

Decode의 주요 축

  • Active sequence 수
  • 각 sequence의 현재 context length
  • KV cache dtype과 layout
  • Batch shape와 padding 방식
  • Sampling algorithm
  • Tensor-parallel collective latency
  • 다른 request의 prefill 간섭

Batch를 키우면 같은 weight read로 여러 sequence를 처리해 throughput이 좋아질 수 있지만, 각 request가 scheduler에서 기다리면 ITL tail이 나빠질 수 있습니다.

Prefill과 Decode 비교

구분PrefillDecode
입력 shape여러 prompt tokenActive sequence당 새 token 1개
대표 목표낮은 TTFT낮고 안정적인 ITL/TPOT
자원 성격상대적으로 compute 활용이 큼상대적으로 weight·KV bandwidth 영향이 큼
Memory 결과Prompt 전체 KV 생성Token마다 KV 한 칸 증가
Scheduler 위험긴 prompt의 head-of-line blockingBatch churn, KV pressure, preemption
대표 최적화FlashAttention, prefix reuse, chunkingContinuous batching, paged KV, speculative decode

이는 절대 분류가 아닙니다. Hardware, batch, model architecture, context length에 따라 병목이 바뀌므로 profiler로 확인합니다.


KV Cache의 Byte를 직접 계산한다

Decoder layer마다 과거 token의 key와 value를 저장합니다. GQA/MQA model에서 sequence 하나의 대략적인 KV byte는:

KV bytes
  = num_layers
  × num_tokens
  × 2                 # K and V
  × num_kv_heads
  × head_dim
  × bytes_per_element

Batch 전체는 sequence별 현재 token 수의 합을 사용합니다.

batch KV bytes
  = num_layers × 2 × num_kv_heads × head_dim × element_bytes
  × Σ sequence_lengthᵢ

예시

layers = 32
KV heads = 8
head_dim = 128
dtype = BF16 = 2 bytes
context = 8,192 tokens

KV ≈ 32 × 8,192 × 2 × 8 × 128 × 2
   ≈ 1 GiB per sequence

대략값입니다. Alignment, block metadata, tensor-parallel partition, prefix sharing, temporary workspace가 실제 memory를 바꿉니다.

흔한 계산 오류

GQA model에서 num_attention_heads를 넣으면 KV head 수를 과대 계산할 수 있습니다. 반대로 runtime이 KV를 TP rank별로 어떻게 복제·분할하는지 무시하면 device당 byte가 틀립니다.

KV Cache는 동적 Allocation 문제다

Request마다 prompt와 output 길이가 다르고, decode 중 한 token씩 늘어나며, 언제 끝날지 미리 모를 수 있습니다.

연속 memory를 최대 길이로 미리 예약하면:

reserved = max_context
used = actual prompt + generated tokens
waste = reserved − used

짧은 요청이 많은 workload에서 내부 낭비가 큽니다. 서로 다른 크기의 영역을 반복 할당·해제하면 외부 fragmentation도 생깁니다.

PagedAttention: KV를 고정 크기 Block으로 관리한다

PagedAttention은 운영체제 virtual memory의 paging에서 영감을 받아 logical token block을 non-contiguous physical KV block에 mapping합니다.

request logical blocks:  [0][1][2][3]
block table:               ↓  ↓  ↓  ↓
physical KV blocks:       [7][2][9][4]

Attention kernel은 block table을 따라 KV를 읽습니다.

얻는 것

  • 최대 길이 전체를 연속 예약하지 않아도 됩니다.
  • Request가 자랄 때 block을 추가합니다.
  • 마지막 block 내부를 제외한 낭비를 줄일 수 있습니다.
  • Copy-on-write와 block sharing으로 shared prefix를 재사용할 수 있습니다.
  • Free block을 빠르게 다른 request에 돌릴 수 있습니다.

새로 생기는 것

  • Block table과 allocator metadata
  • Block lookup을 지원하는 attention kernel
  • Block size에 따른 fragmentation·metadata trade-off
  • Eviction, preemption, copy-on-write correctness

Paged KV는 memory manager와 kernel이 함께 구현돼야 합니다. 일반 contiguous attention kernel에 block table만 추가한다고 동작하지 않습니다.

Block Size Trade-off

작은 block은 마지막 block 낭비가 작고 fine-grained eviction이 가능하지만 metadata와 scheduling overhead가 늘어납니다. 큰 block은 lookup이 단순하지만 짧은 request와 branch에서 낭비가 커집니다.

internal waste per sequence < one block  # 단순 fixed-block 가정

실제 allocator가 block group, alignment, multiple memory pools를 쓰면 이 bound가 달라질 수 있습니다.


Continuous Batching: Iteration마다 Batch를 다시 만든다

Static batching은 batch 안의 모든 request가 끝날 때까지 다음 batch를 기다립니다.

static batch
  request A: 200 output tokens
  request B:  20 output tokens → waits idle for A

Continuous 또는 iteration-level batching은 decode iteration 경계에서 완료 request를 빼고 새 request를 넣습니다.

iteration 1: [A B C]
iteration 2: [A B C]
B finishes
iteration 3: [A D E]

Scheduler가 매 Iteration 결정하는 것

  • 어느 running request를 decode할지
  • 새 prompt를 prefill할지
  • Prefill을 몇 token chunk로 처리할지
  • KV block을 몇 개 예약할지
  • Memory 부족 시 누구를 preempt할지
  • Cancelled request의 block을 언제 반환할지

Admission은 GPU Memory Budget으로 한다

단순 request 개수 limit보다 다음 예측이 필요합니다.

available KV blocks
  ≥ current live blocks
  + reserved growth for admitted requests
  + safety margin

Output 길이를 모르면 max_tokens, historical distribution, tenant limit을 이용합니다. 지나친 optimistic admission은 decode 중 KV OOM과 preemption storm을 만듭니다.

Preemption의 두 방식

Recompute

Request의 KV를 버리고 나중에 prompt와 generated tokens를 다시 prefill합니다.

  • Host transfer가 없습니다.
  • 긴 context에서는 recompute가 비쌉니다.
  • Deterministic sampling state와 generated tokens를 보존해야 합니다.

Swap/Offload

KV를 CPU memory나 remote tier로 옮깁니다.

  • Recompute를 피할 수 있습니다.
  • PCIe·network bandwidth와 latency가 듭니다.
  • Host memory pressure와 stale block 관리가 필요합니다.

둘 중 어느 쪽이 나은지는 KV byte, pause duration, transfer bandwidth, recompute FLOP로 결정합니다.

Chunked Prefill: 긴 Prompt의 독점을 줄인다

한 번의 거대한 prefill이 decode iteration을 오래 막으면 running request의 ITL이 튑니다. Prompt를 token chunk로 나눠 decode batch 사이에 섞습니다.

without chunking:
  [prefill 32K........................][decode]

with chunking:
  [P 2K][D][P 2K][D][P 2K][D]...

Trade-off

  • Decode starvation과 ITL tail을 줄일 수 있습니다.
  • 새 request의 prefill 완료가 늦어져 TTFT가 늘 수 있습니다.
  • Chunk가 너무 작으면 launch와 scheduling overhead가 커집니다.
  • Chunk와 decode token을 섞은 batch의 kernel efficiency가 달라집니다.

Sarathi-Serve는 chunked prefill과 stall-free batching으로 throughput-latency trade-off를 다룬 대표 연구입니다. 제품 workload에서 chunk size를 SLO별로 튜닝합니다.


FlashAttention은 IO-aware Exact Attention이다

표준 attention을 수식 그대로 materialize하면 score matrix의 HBM read/write가 큽니다.

S = QKᵀ
P = softmax(S)
O = PV

FlashAttention은 Q·K·V를 tile로 나눠 on-chip SRAM에서 계산하고 online softmax 통계를 유지해 큰 score matrix를 HBM에 전부 쓰지 않습니다.

중요한 구분

  • Exact attention 결과를 계산하는 IO-aware algorithm입니다.
  • Dense attention의 이론적 pairwise compute를 없애는 sparse approximation이 아닙니다.
  • Hardware, dtype, head dimension, mask, sequence shape에 맞는 kernel이 선택돼야 합니다.
  • Training prefill kernel과 paged decode kernel은 같은 이름 아래 다른 execution path일 수 있습니다.

“FlashAttention 사용”이라는 config보다 profiler의 실제 selected kernel과 fallback shape를 확인합니다.

Kernel Fusion과 CUDA Graph 계열 최적화

Decode는 작은 kernel launch가 layer마다 반복돼 launch overhead가 두드러질 수 있습니다.

  • RMSNorm, bias, activation, residual의 fusion
  • Sampling과 logit processing fusion
  • Stable shape bucket의 graph capture/replay
  • Fused rotary embedding과 KV write

Dynamic batch와 sequence 길이가 graph shape를 계속 바꾸면 capture reuse가 낮아질 수 있습니다. Padding으로 shape를 고정하면 compute 낭비가 생깁니다. Bucket hit rate와 padding FLOP를 함께 봅니다.

Sampling도 Serving 비용이다

Greedy decode, temperature, top-k, top-p, repetition penalty, constrained JSON, beam search는 비용과 state가 다릅니다.

Logit Processing

Vocabulary 전체에 penalty와 mask를 적용하고 sampling하면 작은 batch에서 무시하기 어려운 latency가 될 수 있습니다.

Constrained Decoding

JSON schema나 grammar가 허용 token set을 제한합니다.

  • CPU grammar transition이 GPU decode를 기다리게 할 수 있습니다.
  • Tokenizer byte boundary와 UTF-8 state가 필요합니다.
  • Schema complexity와 prefix state cache가 latency를 바꿉니다.
  • Valid JSON 비율뿐 아니라 ITL과 completion length도 측정합니다.

하나의 request가 여러 hypothesis와 KV branch를 유지합니다. Copy-on-write block sharing이 없다면 KV memory가 크게 늘 수 있습니다.


Prefix Caching: 같은 앞부분을 다시 계산하지 않는다

System prompt, tool schema, few-shot example처럼 동일한 token prefix가 반복되면 이미 계산한 KV block을 재사용할 수 있습니다.

request 1: [system][tools][user A]
request 2: [system][tools][user B]
            └ shared prefix ┘

Cache Key에 들어갈 Identity

텍스트 문자열만 hash하면 부족합니다.

  • Model weight와 quantization identity
  • Tokenizer와 chat template version
  • Exact token ids와 position
  • Adapter/LoRA identity
  • RoPE scaling과 attention config
  • KV dtype과 layout
  • Tenant·security namespace
  • Optional multimodal embedding identity

이 중 하나라도 달라 output KV가 달라질 수 있습니다.

Block 경계와 Copy-on-write

완전히 일치하는 immutable prefix block만 공유합니다. 마지막 partially filled block에서 request가 갈라지면 copy-on-write 또는 별도 block을 사용해 다른 request의 KV를 덮어쓰지 않습니다.

Prefix Cache의 Security

다른 tenant가 cache hit timing이나 block reuse를 통해 민감한 prefix 존재를 추론할 수 있습니다.

  • Tenant별 namespace와 quota를 둡니다.
  • Sensitive prompt는 공유 cache에서 제외할 수 있습니다.
  • Cache content를 log에 노출하지 않습니다.
  • Authorization 변경 시 stale prefix를 무효화합니다.

Hit Rate만 보면 안 된다

net benefit
  = avoided prefill time
  − lookup / routing / transfer time
  − cache memory opportunity cost

짧은 prefix의 높은 hit count보다 긴 prefix의 saved compute가 더 클 수 있습니다. Hit token과 saved prefill milliseconds를 보고합니다.

Radix Tree와 Prefix-aware Routing

Token prefix를 radix tree로 관리하면 공통 prefix를 찾고 block을 공유하기 쉽습니다. 여러 replica가 있을 때 cache affinity가 있는 worker로 routing하면 재사용이 늘지만 load imbalance가 생길 수 있습니다.

routing score
  = estimated prefix reuse benefit
  − queue delay penalty
  − KV pressure penalty

Preble은 distributed prompt scheduling에서 prefix reuse와 load·fairness를 함께 다룬 연구입니다.


Speculative Decoding: 작은 Draft를 큰 Model이 검증한다

Autoregressive target model은 한 번에 한 token씩 직렬로 생성합니다. Speculative decoding은 더 싼 draft model이 여러 후보를 만들고 target model이 한 번의 병렬 forward로 검증합니다.

draft proposes:   d₁ d₂ d₃ d₄
target verifies:  p(d₁), p(d₂), p(d₃), p(d₄) in parallel
accept prefix:    d₁ d₂
reject/correct:         x₃
continue

원 논문의 accept/reject sampling rule을 정확히 구현하면 target distribution을 바꾸지 않고 가속할 수 있습니다. 단순히 target argmax와 일치하는 token만 취하는 구현은 sampling 조건에 따라 다른 algorithm일 수 있습니다.

속도를 결정하는 항목

speedup depends on
  accepted tokens per target verification
  − draft generation cost
  − verification cost
  − KV commit/rollback cost
  − scheduler and batching interference

Acceptance rate가 높아도 draft가 비싸면 이득이 작습니다. 반대로 draft가 매우 싸도 target distribution과 잘 맞지 않으면 자주 거절됩니다.

Draft 선택

  • 작은 별도 language model
  • Target에서 파생한 assistant head
  • N-gram 또는 prompt lookup 후보
  • Tree 형태의 여러 후보

각 방식은 memory, training, distribution correctness, batching 복잡도가 다릅니다.

KV Cache 처리

Draft KV와 target KV가 필요할 수 있고, rejected suffix의 KV를 버려야 합니다. Paged block allocator가 commit boundary를 정확히 추적해야 memory leak이나 stale attention을 막을 수 있습니다.

무엇을 측정할까

  • Acceptance length distribution
  • Draft와 verification latency
  • Target calls per output token
  • Output token/s와 ITL tail
  • Additional weight·KV memory
  • Greedy와 stochastic sampling parity
  • Domain·language·tool JSON별 acceptance

Code와 정형 text에서는 acceptance가 높고, 창의적 sampling이나 domain mismatch에서는 낮을 수 있습니다. 실제 trace로 결정합니다.


Model Parallel과 Replica Parallel을 Serving에 배치한다

Tensor Parallel

Model weight와 layer compute를 여러 GPU에 나누지만 layer마다 collective가 필요합니다.

  • 한 GPU에 model이 들어가지 않을 때 필요합니다.
  • 빠른 intra-node interconnect가 중요합니다.
  • TP degree가 커지면 GPU당 GEMM이 너무 작아질 수 있습니다.
  • Decode ITL은 collective latency에 민감합니다.

Pipeline Parallel

Layer stage를 나눕니다. 여러 microbatch/request로 pipeline을 채워 throughput을 얻을 수 있지만 단일 request token은 모든 stage를 순서대로 지나 latency가 늘 수 있습니다.

Data/Replica Parallel

독립 model replica에 서로 다른 request를 보냅니다. Model이 한 replica group에 들어간다면 request throughput 확장과 장애 격리에 단순합니다.

Serving Group의 단위

one serving replica
  = TP degree × PP degree GPUs

fleet capacity
  = number of independent serving replicas

Replica 수와 replica당 parallel degree를 혼동하면 autoscaling 단위와 장애 blast radius가 틀립니다.

Prefill/Decode Disaggregation

Prefill worker와 decode worker를 분리하고 prefill이 만든 KV를 decode 쪽으로 전송합니다.

request → prefill pool
           compute prompt KV
           transfer KV blocks
         → decode pool
           stream output tokens

기대 효과

  • 긴 prefill이 decode iteration을 방해하는 것을 줄입니다.
  • Prefill과 decode에 다른 parallel degree·batch·hardware를 사용할 수 있습니다.
  • 두 phase를 별도로 autoscale할 수 있습니다.

새 비용

  • 큰 KV transfer byte와 latency
  • KV layout·dtype·model identity compatibility
  • 두 queue 사이 backpressure
  • Worker failure 중 ownership과 cleanup
  • Prefix cache locality와 routing 복잡성
  • 유휴 capacity가 phase 사이 이동하기 어려움

손익 식

disaggregate when
  saved interference + better phase utilization
  > KV transfer + extra queueing + fragmentation + idle capacity

DistServe, Splitwise, Mooncake는 phase 분리와 KV 중심 architecture를 탐구한 대표 연구입니다. 2025년 TaiChi preprint는 aggregation과 disaggregation 중 어느 하나가 항상 이기는 것이 아니라 TTFT·TPOT SLO 조합에 따라 hybrid가 유리할 수 있음을 연구합니다. 2026년 6월의 accelerator 비교 preprint도 phase별 hardware 강점과 network 조건을 따로 평가합니다. 최신 preprint의 결과를 보편 법칙으로 쓰지 말고 자신의 model·hardware·arrival trace로 재현합니다.


Scheduler Policy는 Product Policy다

FCFS

도착 순서대로 처리해 이해하기 쉽지만 긴 prompt가 뒤의 짧은 요청을 막을 수 있습니다.

Shortest-job 계열

짧은 요청을 먼저 처리하면 평균 latency가 좋아질 수 있지만 output length를 미리 모르고 긴 request가 굶을 수 있습니다.

Deadline/SLO-aware

TTFT 또는 다음 token deadline이 가까운 request에 우선순위를 줍니다. 느린 request를 구하기 위해 이미 SLO를 만족할 request의 자원을 과도하게 빼앗지 않도록 admission과 함께 설계합니다.

Tenant Fairness

Token budget, concurrency, weighted fair queue를 적용합니다. Request 개수만 제한하면 한 tenant의 매우 긴 context가 KV memory를 독점할 수 있습니다.

Priority의 역전

High-priority request가 필요한 KV block이나 shared resource를 low-priority request가 점유할 수 있습니다. Preemption 비용까지 포함한 priority policy가 필요합니다.

Backpressure와 Load Shedding

모든 요청을 queue에 넣는 것은 친절한 동작이 아닙니다. 예상 완료 시간이 client deadline을 이미 넘는다면 빠르게 거절하거나 degraded tier로 routing하는 편이 낫습니다.

admit if
  predicted_queue + predicted_prefill + predicted_decode
  ≤ request_deadline
and KV budget is feasible

예측이 완벽할 필요는 없지만 prompt tokens, max_tokens, active KV, recent service rate를 사용해 overload를 감지합니다.

Queue Limit의 단위

  • Request count
  • Waiting prompt tokens
  • Reserved KV tokens
  • Estimated GPU-seconds
  • Tenant weighted budget

긴 prompt와 짧은 prompt를 같은 request 1개로 세면 overload를 늦게 감지합니다.

Cancellation은 자원 회수 Protocol이다

Client가 연결을 끊거나 agent가 tool result 때문에 generation을 중단할 수 있습니다.

  1. Gateway가 cancel signal을 전달합니다.
  2. Scheduler가 다음 safe iteration에서 request를 제거합니다.
  3. KV와 sampling state reference count를 감소시킵니다.
  4. Shared prefix block은 마지막 owner일 때만 해제합니다.
  5. Streaming task와 network buffer를 종료합니다.
  6. Cancellation latency와 leaked block을 관찰합니다.

Cancel된 request가 background에서 계속 decode되면 capacity와 비용을 소모합니다.


RAG Agent Serving의 특별한 Workload

RAG agent는 일반 chat보다 phase 변화가 큽니다.

long retrieved context → prefill
short reasoning/tool JSON → decode
external tool wait → pause
tool result appended → another prefill
continue decode → maybe another tool

반복 Prefix

System prompt와 tool schema는 cache reuse 후보입니다. 하지만 retrieval 결과와 authorization scope는 request마다 다르므로 cache key와 tenant boundary를 엄격히 둡니다.

Tool Interruption

Tool이 실행되는 동안 KV를 GPU에 계속 두면 memory를 점유합니다. 버리면 복귀 시 recompute가 필요하고, offload하면 transfer가 필요합니다.

choose per pause:
  keep on GPU     if pause short and KV scarce cost acceptable
  offload         if transfer cheaper than recompute
  evict/recompute if pause long or host tier saturated

INFERCEPT는 augmented LLM의 외부 interaction 중 KV lifetime을 다룬 연구입니다. 실제 agent에서는 tool latency prediction과 cancellation 확률도 policy에 넣습니다.

Context가 계속 자란다

Tool output을 매번 전체 transcript에 붙이면 KV와 attention cost가 증가합니다.

  • 필요한 tool field만 보존합니다.
  • Retrieval evidence를 deduplicate합니다.
  • State summary는 원문 pointer와 함께 관리합니다.
  • Context compaction 후 answer quality를 평가합니다.
  • Stable prefix와 changing suffix 경계를 고려합니다.

Structured Output SLO

Tool 호출은 첫 token보다 closing brace가 도착해야 실행할 수 있습니다.

time_to_valid_tool_call
  = request received → schema-valid complete arguments

Agent serving에서는 이 지표가 TTFT보다 실제 workflow latency에 더 가깝습니다.


Autoscaling은 GPU Utilization 하나로 하지 않는다

GPU utilization이 높아도 goodput이 좋을 수 있고, 낮아도 KV memory가 꽉 차 admission이 막힐 수 있습니다.

관찰할 Signal

  • Waiting prompt tokens와 queue age
  • Prefill tokens/sec와 prefill utilization
  • Decode active sequences와 output tokens/sec
  • TTFT·ITL SLO miss rate
  • Free/used/fragmented KV blocks
  • Prefix cache hit tokens와 eviction
  • Preemption·recompute·swap rate
  • Replica별 load와 tail
  • Model load/cold-start time

Scale-out Delay

새 replica는 GPU 할당, process start, weight load, graph capture, cache warmup이 필요합니다. 수 분짜리 cold start가 있다면 현재 queue만 보고 반응하는 autoscaler는 늦습니다.

  • Scheduled traffic와 leading indicator를 사용합니다.
  • Warm spare의 비용과 SLO 가치를 비교합니다.
  • Weight artifact를 node-local cache에 둘 수 있습니다.
  • Readiness는 port open이 아니라 warmup inference와 checksum 뒤에 표시합니다.

Scale-in Drain

Active request를 즉시 끊지 않습니다.

  1. 새 admission을 중단합니다.
  2. Prefix-aware router에서 replica를 제외합니다.
  3. 짧은 request를 drain합니다.
  4. 긴 request migration 지원 여부를 확인합니다.
  5. Deadline 뒤 취소·재시도 policy를 적용합니다.
  6. KV와 shared block reference를 정리합니다.

장애와 복구

Worker Crash

In-memory KV는 사라집니다. Request를 다른 replica에서 prompt부터 재실행할지, durable session state로 복구할지 결정합니다. Stochastic sampling은 RNG와 이미 stream한 token 때문에 bit-identical 복구가 어려울 수 있습니다.

Partial Stream

Client가 일부 token을 받은 뒤 retry하면 중복 text나 중복 tool call이 생깁니다.

  • Request id와 generation attempt id를 둡니다.
  • Tool execution에는 idempotency key를 사용합니다.
  • Stream offset 또는 accumulated token hash를 기록합니다.
  • Retry가 새 answer인지 이어받기인지 contract로 정합니다.

OOM

Engine process 전체가 죽기 전에 admission을 닫고 low-priority work를 preempt합니다. Allocator free block과 framework reported memory가 불일치하는지 확인합니다.

Bad Model Artifact

Tokenizer, chat template, quantized weight, engine kernel이 호환되지 않을 수 있습니다. Model identity manifest와 startup parity prompt로 fail-closed합니다.


Benchmark는 Arrival Trace를 재현해야 한다

Offline batch throughput은 online serving을 대표하지 않습니다.

Workload Matrix

최소 구간 예시
Prompt length128, 1K, 8K, 32K+
Output length16, 128, 512+
ArrivalFixed concurrency, Poisson-like, burst, replay trace
Prefix reuse0%, partial, high shared prefix
SamplingGreedy, top-p, constrained JSON
Model variantBF16, quantized, adapter
Agent pausenone, short tool, long tool

값은 예시입니다. Production histogram과 tail을 반영합니다.

Open-loop와 Closed-loop

  • Closed-loop: Client가 응답을 받은 뒤 다음 요청을 보냅니다. Server가 느려지면 arrival도 줄어 overload가 가려집니다.
  • Open-loop: 정해진 arrival schedule로 요청을 보냅니다. Queue collapse와 tail을 보기 좋습니다.

둘을 구분해 보고합니다.

Warm과 Cold

  • Cold weight load
  • Empty prefix cache
  • Warm prefix cache
  • Graph capture 전후
  • Allocator steady state와 장시간 fragmentation

짧은 warm benchmark만으로 release하지 않습니다.

Correctness를 함께 측정한다

성능 최적화가 output을 바꿀 수 있습니다.

  • Greedy token parity
  • Sampling distribution test
  • Stop sequence와 EOS 처리
  • Logprob correctness
  • Structured-output validity
  • Prefix-cache isolation
  • Cancellation 후 block reclamation
  • Speculative accept/reject parity
  • Quantized model task quality

비용 지표

cost per completed request
  = infrastructure cost / successful completed requests

cost per valid output token
  = infrastructure cost / valid streamed output tokens

SLO-adjusted cost
  = infrastructure cost / requests satisfying required SLOs

Cancelled, failed, duplicate, SLO-missed request를 분모에 넣어 비용을 좋게 보이지 않게 합니다.


Serving Manifest 예시

deployment:
  id: rag-agent-serving-v9
  model_manifest: model-quant-v7
  tokenizer: tokenizer-v5
  chat_template: agent-template-v11
engine:
  version: serving-engine-v8
  tensor_parallel: 4
  pipeline_parallel: 1
  kv_dtype: bfloat16
  kv_block_tokens: 16
  max_model_len: 32768
scheduler:
  policy: slo-aware-fair
  continuous_batching: true
  chunked_prefill_tokens: 2048
  max_waiting_prompt_tokens: 500000
  preemption: recompute_then_reject
cache:
  prefix_cache: true
  tenant_namespaced: true
  key_schema: prefix-key-v4
speculative:
  enabled: true
  draft_manifest: draft-v3
  max_draft_tokens: 5
observability:
  report_ttft_itl_by_length: true
  report_kv_blocks: true
  report_goodput: true
release_gate:
  workload_trace: prod-redacted-2026q2
  correctness_suite: rag-agent-serving-v12

값은 예시입니다. Engine option 이름과 의미는 실제 version 문서에 맞춥니다.

흔한 오해와 처방

1. GPU Utilization이 높으면 Serving이 좋다

긴 prefill로 GPU가 가득 차도 decode ITL은 악화될 수 있습니다.

처방: Phase별 utilization과 TTFT·ITL goodput을 함께 봅니다.

2. Token/s가 높으면 사용자 지연도 낮다

큰 batch는 throughput을 높이면서 queue와 ITL tail을 키울 수 있습니다.

처방: Throughput-latency curve와 SLO-satisfying goodput을 그립니다.

3. KV Cache는 Model Weight에 비해 작다

Long context와 높은 concurrency에서 sequence마다 누적됩니다.

처방: GQA KV head 수와 live token 합으로 byte를 계산합니다.

4. PagedAttention은 Attention Compute를 없앤다

주요 목적은 동적 KV memory allocation과 sharing입니다.

처방: KV fragmentation 개선과 attention FLOP를 분리합니다.

5. FlashAttention은 Approximate Attention이다

대표 FlashAttention은 IO-aware exact dense attention algorithm입니다.

처방: 선택된 exact kernel, mask, dtype, fallback을 확인합니다.

6. Prefix Cache Hit Rate가 높으면 무조건 이득이다

짧은 prefix hit, remote transfer, cache pressure가 있을 수 있습니다.

처방: Hit token과 saved prefill time, eviction cost를 측정합니다.

7. Speculative Decoding은 항상 두 배 빠르다

Acceptance, draft cost, verification batch, 추가 memory에 좌우됩니다.

처방: Domain·sampling별 accepted tokens/verification과 end-to-end ITL을 측정합니다.

8. Prefill/Decode를 분리하면 항상 좋다

KV transfer와 phase별 idle capacity가 새 비용입니다.

처방: Aggregated·disaggregated·hybrid를 같은 SLO와 trace에서 비교합니다.

9. Request Count만으로 Admission하면 된다

Prompt와 output budget이 수백 배 다를 수 있습니다.

처방: Waiting token, KV block, estimated GPU time으로 제한합니다.

10. Client Disconnect면 계산도 자동으로 끝난다

Cancel propagation이 없으면 backend가 계속 decode할 수 있습니다.

처방: End-to-end cancellation과 block reclamation latency를 테스트합니다.

Production 체크리스트

  • Gateway·tokenize·queue·prefill·decode·network timestamp를 분리한다.
  • TTFT, ITL, TPOT, E2E의 정확한 정의를 문서화했다.
  • Prompt·output length cohort별 p50·p95·p99를 본다.
  • Request, input token, output token throughput을 구분한다.
  • 모든 필수 SLO를 만족한 request goodput을 계산한다.
  • GQA/MQA의 num_kv_heads로 KV byte를 계산했다.
  • Paged KV block size와 metadata·fragmentation을 측정했다.
  • Continuous batching의 iteration admission과 preemption을 trace한다.
  • Chunked prefill 크기를 TTFT·ITL curve로 정했다.
  • 실제 Flash/paged attention kernel과 fallback을 profile한다.
  • Sampling·grammar processing latency를 포함한다.
  • Prefix cache key에 model·tokenizer·template·adapter identity가 있다.
  • Prefix cache를 tenant namespace와 authorization boundary로 격리한다.
  • Hit count가 아니라 reused token과 saved compute를 측정한다.
  • Speculative decoding의 distribution parity와 KV rollback을 검증했다.
  • Acceptance length, draft cost, target verification cost를 기록한다.
  • TP·PP degree가 single-request latency와 replica capacity에 미치는 영향을 측정한다.
  • P/D 분리 시 KV transfer byte·latency와 두 queue backpressure를 측정한다.
  • Scheduler fairness와 starvation bound가 있다.
  • Request count 외 waiting token·KV budget으로 admission한다.
  • Client deadline을 넘길 work에 빠른 reject와 retry hint를 준다.
  • Cancellation이 scheduler와 KV allocator까지 전달된다.
  • Agent tool wait 중 KV keep/offload/recompute policy가 있다.
  • Open-loop burst와 production trace로 benchmark한다.
  • Cold start, cache cold, 장시간 allocator steady state를 시험했다.
  • Partial stream retry와 tool idempotency를 검증했다.
  • Autoscaling이 queue age·KV pressure·SLO miss를 함께 본다.
  • Scale-in drain과 worker crash 복구를 연습했다.
  • 성능 gate와 output correctness gate를 동시에 통과한다.
  • SLO-adjusted cost를 release 비교 지표로 사용한다.

스스로 확인하기

  1. TTFT를 queue와 prefill로 분해해야 하는 이유는 무엇인가?
  2. Prefill과 decode가 다른 arithmetic·scheduling 성격을 갖는 이유를 설명할 수 있는가?
  3. GQA model의 sequence당 KV cache byte를 계산할 수 있는가?
  4. PagedAttention의 logical block table이 fragmentation을 어떻게 줄이는가?
  5. Continuous batching과 static batching의 차이는 무엇인가?
  6. Chunked prefill이 TTFT와 ITL 사이에서 만드는 trade-off는 무엇인가?
  7. FlashAttention이 줄이는 것은 attention FLOP인가 HBM IO인가?
  8. Prefix cache key에 text 외 model·tokenizer·adapter identity가 필요한 이유는 무엇인가?
  9. Speculative decoding의 speedup을 acceptance rate 하나로 설명할 수 없는 이유는 무엇인가?
  10. P/D disaggregation이 KV transfer 비용보다 이득인지 어떤 식으로 판단하는가?
  11. RAG agent가 tool을 기다리는 동안 KV를 keep·offload·recompute하는 기준은 무엇인가?
  12. Throughput과 goodput을 서로 다르게 만드는 SLO failure 예시를 들 수 있는가?

앞 글에서 training-time parallelism을, 이 글에서 online serving scheduler를 다뤘습니다. 다음 글은 RAG Agent 모델 선택과 배포: 평가에서 Canary까지입니다. Benchmark 점수, tool calling, grounding, context, latency, cost를 하나의 release decision으로 묶습니다.

참고자료