~/ai-dev.md
AI_DEV

LLM 서빙 지연은 왜 prefill과 decode를 나눠 봐야 할까

10분 읽기·2026.06.10·출처 5·00
오늘의 질문

긴 프롬프트와 긴 답변이 섞이는 서비스에서 TTFT와 토큰 간 지연을 같은 병목으로 취급하면 무엇을 놓칠까?

ai-dev.md
SURVIVE.exe

LLM 서빙 지연은 왜 prefill과 decode를 나눠 봐야 할까

1. 왜 지금 봐야 하나

LLM 기능을 제품에 넣으면 처음에는 “모델이 똑똑한가”만 보이지만, 트래픽이 생기면 사용자가 체감하는 문제는 더 구체적이다. 첫 토큰이 늦게 나오거나, 첫 토큰은 빨리 나왔는데 이후 토큰이 끊기거나, 긴 문서를 넣은 사용자가 짧은 질문을 던진 사용자까지 느리게 만든다.

최근 vLLM, NVIDIA Dynamo, SGLang, llm-d 문서가 공통으로 강조하는 흐름은 LLM inference를 하나의 작업으로 보지 말고 prefill과 decode라는 다른 성격의 작업으로 나눠 운영하라는 것이다. vLLM 문서는 disaggregated prefilling을 실험 기능으로 소개하면서 TTFT와 ITL을 따로 튜닝할 수 있다고 설명하고, 동시에 “throughput을 자동으로 높이는 기능은 아니다”라고 못 박는다. llm-d 가이드는 이 방식이 모든 워크로드에 맞지 않으며, 큰 모델·긴 입력·MoE처럼 분리 비용을 상쇄할 조건이 있을 때 검토하라고 안내한다.

개발자에게 중요한 점은 “새 서빙 프레임워크가 나왔다”가 아니다. 내 서비스의 지연이 프롬프트 처리 병목인지, 토큰 생성 병목인지, KV cache 이동 병목인지를 분리해서 봐야 한다는 것이다.

2. 핵심 개념

LLM 추론은 크게 두 단계로 나뉜다.

  1. Prefill: 입력 프롬프트 전체를 한 번에 처리해 attention에 필요한 KV cache를 만든다. 긴 시스템 프롬프트, 긴 문서, 대화 히스토리가 많을수록 무거워진다. 일반적으로 계산량이 크고 병렬화 여지가 있다.
  2. Decode: 이미 만들어진 KV cache를 보면서 새 토큰을 하나씩 생성한다. 한 토큰씩 순차적으로 진행되므로 메모리 대역폭, KV cache 접근, 배치 스케줄링의 영향을 크게 받는다.

그래서 지연 지표도 둘로 나눠야 한다.

  • TTFT(Time To First Token): 요청 후 첫 토큰이 나오기까지의 시간. prefill, 큐 대기, 라우팅, KV cache hit 여부의 영향을 받는다.
  • ITL(Inter-Token Latency) 또는 TPOT(Time Per Output Token): 토큰과 토큰 사이의 시간. decode 단계의 안정성과 tail latency를 보여준다.

일체형 엔진에서는 prefill과 decode가 같은 GPU 풀에서 섞인다. 새로 들어온 긴 prefill 작업이 이미 토큰을 스트리밍 중인 decode 작업 사이에 끼어들면, 사용자는 “답변이 중간중간 멈춘다”고 느낀다. P/D disaggregation은 prefill worker와 decode worker를 분리하고, prefill이 만든 KV cache를 decode 쪽으로 넘긴다. 이때 핵심 비용은 KV cache transfer다.

즉, 이 구조의 본질은 “GPU를 더 쓰면 빨라진다”가 아니라 서로 다른 병목을 서로 다른 풀에서 관리하고, 그 사이의 상태 이동 비용을 감당할 수 있는가다.

3. 최신 이슈와 연결

vLLM의 최신 문서는 disaggregated prefilling이 prefill과 decode를 별도 vLLM 인스턴스에 놓고 TTFT와 ITL을 독립적으로 조정할 수 있게 한다고 설명한다. 하지만 같은 문서에서 이 기능은 실험적이며, throughput을 개선하는 기능이 아니라고 분명히 적는다. 이 경고가 중요하다. 분리는 공짜 최적화가 아니라 운영 복잡도를 사는 선택이다.

NVIDIA Dynamo 문서는 prefill engine이 KV cache를 만들고, decode engine이 이 KV cache를 받아 토큰 생성을 수행하는 3단계 흐름을 설명한다. 또 NIXL을 사용해 prefill GPU의 VRAM에서 decode GPU의 VRAM으로 KV cache를 직접 전송하고, PrefillRouter가 cache overlap과 load를 보고 worker를 선택한다고 설명한다. 여기서 서빙 문제는 단순 API 서버가 아니라 라우터, worker discovery, KV transfer metadata, cache-aware routing의 문제로 바뀐다.

llm-d의 P/D disaggregation 가이드는 훨씬 실무적인 조건을 제시한다. 예시는 GPT-OSS-120B급 큰 모델, 10k input / 1k output 같은 긴 입력, RDMA가 가능한 InfiniBand 또는 RoCE 환경을 전제로 한다. 반대로 200 input / 200 output 같은 짧은 요청이나 작은 모델에는 권하지 않는다. SGLang 문서도 Mooncake와 NIXL 같은 transfer engine, router, fault tolerance, prefill/decode 개별 profiling을 다룬다.

정리하면 최근 흐름은 “LLM 서빙을 더 미세한 단계로 쪼개고 있다”이다. 그러나 그만큼 관측, 라우팅, 네트워크, cache 정합성이 새로운 실패 지점이 된다.

4. 개발자 관점 해석

P/D disaggregation을 이해할 때는 먼저 내가 해결하려는 문제가 무엇인지 적어야 한다.

  • 첫 토큰이 늦은가? → prefill, queue wait, prompt length, prefix cache hit를 본다.
  • 첫 토큰 이후 스트리밍이 끊기는가? → decode ITL, decode batch, prefill interference를 본다.
  • 특정 긴 문서 요청이 전체 서비스를 느리게 하는가? → prefill 격리와 routing을 본다.
  • GPU 사용률은 높은데 토큰 처리량이 낮은가? → decode memory bandwidth, KV cache pressure, batch shape를 본다.
  • 같은 system prompt나 문서가 반복되는가? → prefix caching과 KV-aware routing을 본다.

분리 구조의 장점은 명확하다.

  1. prefill-heavy 트래픽과 decode-heavy 트래픽을 다른 비율로 스케일할 수 있다.
  2. 긴 입력 요청이 실시간 decode를 방해하는 상황을 줄일 수 있다.
  3. TTFT와 ITL을 한 숫자로 뭉개지 않고 별도로 SLO를 잡을 수 있다.
  4. 큰 모델이나 MoE에서 worker parallelism을 단계별로 다르게 줄 수 있다.

하지만 실패 모드도 명확하다.

  1. KV transfer가 새 병목이 된다. 네트워크가 느리거나 RDMA 설정이 불안정하면 분리 비용이 이득을 잡아먹는다.
  2. 라우터가 똑똑해야 한다. cache overlap만 보고 라우팅하면 load imbalance가 생기고, load만 보면 cache reuse를 놓친다.
  3. 짧은 요청에는 오히려 손해일 수 있다. prefill과 decode를 나누는 오버헤드가 실제 계산보다 클 수 있다.
  4. 디버깅 단위가 늘어난다. 단일 프로세스 로그 대신 prefill worker, decode worker, router, transfer engine, network metric을 같이 봐야 한다.
  5. 정확성도 운영 이슈가 된다. KV cache는 모델 내부 상태다. 전송, page 재사용, cache hit/miss 경계가 틀어지면 단순 지연이 아니라 이상한 출력으로 이어질 수 있다.

그래서 이 주제는 “인프라 엔지니어만의 최적화”가 아니다. AI 기능을 설계하는 개발자도 프롬프트 길이, 출력 길이, 동시성, 응답 스트리밍 UX를 정할 때 이 병목 구조를 알아야 한다.

5. 내 프로젝트에 적용할 체크포인트

아직 직접 vLLM 클러스터를 운영하지 않아도 아래 질문은 바로 쓸 수 있다.

  • 우리 서비스는 평균 지연만 보나, TTFT와 ITL을 나눠 보나?
  • 요청 로그에 input token, output token, cache hit 여부, queue wait가 남는가?
  • 긴 RAG 문서 요청과 짧은 일반 질문이 같은 모델 풀을 공유하나?
  • system prompt, policy prompt, 도구 설명처럼 반복되는 prefix가 얼마나 큰가?
  • 긴 입력이 많은 워크로드인가, 긴 출력이 많은 워크로드인가?
  • 사용자는 “첫 글자라도 빨리”를 원하나, “전체 답변 완료 시간”을 더 중요하게 보나?
  • managed API를 쓰더라도 provider가 제공하는 batch, cache, streaming 지표를 확인하고 있나?
  • self-hosting을 검토한다면 RDMA, NVLink, RoCE, EFA 같은 네트워크 전제까지 예산에 포함했나?

작은 팀이라면 당장 P/D disaggregation을 도입하는 것보다 측정 단위를 바꾸는 것이 먼저다. 평균 응답 시간 하나만 있으면 prefill 문제와 decode 문제가 섞여 보인다. 반대로 TTFT, output token rate, input/output token length를 같이 보면 “프롬프트를 줄일 문제인지, 출력 길이를 제한할 문제인지, 캐시할 문제인지, 서빙 구조를 바꿀 문제인지”가 보인다.

6. 오늘 10분 액션

오늘은 코드보다 로그 스키마를 하나 바꿔보자.

  1. 최근 LLM 요청 20개를 고른다.
  2. 각 요청에 대해 아래 표를 만든다.
requestinput_tokensoutput_tokensTTFTtotal_latencytokens/sec비고
예: 긴 문서 요약12000800RAG 문서 포함
예: 짧은 질문400120일반 채팅
  1. TTFT가 긴 요청과 tokens/sec가 낮은 요청을 분리해 표시한다.
  2. 긴 input이 TTFT를 밀어 올리는지, 긴 output이 전체 시간을 밀어 올리는지 적는다.
  3. 마지막으로 한 문장으로 결론을 쓴다.

예시 결론:

우리 서비스는 평균 지연보다 긴 RAG 입력의 TTFT가 문제다. P/D 분리보다 먼저 prefix caching, 문서 chunk 수 제한, 검색 결과 rerank를 실험한다.

또는:

첫 토큰은 빠르지만 긴 답변에서 토큰 간 지연이 커진다. 출력 길이 제한, decode 동시성, 모델 크기, 서빙 provider의 streaming token rate를 비교한다.

7. 더 볼 자료

  • vLLM 문서의 disaggregated prefilling 페이지: 기능이 실험적이며 throughput 개선책이 아니라 tail ITL 제어와 튜닝 유연성에 가깝다는 점을 확인한다.
  • NVIDIA Dynamo의 disaggregated serving 설계 문서: PrefillRouter, NIXL, transfer metadata, worker discovery가 어떤 역할을 하는지 본다.
  • llm-d P/D disaggregation 가이드: 어떤 모델 크기, 입력/출력 길이, 네트워크 조건에서 권장되는지 확인한다.
  • SGLang PD disaggregation 문서: router, Mooncake/NIXL, prefill/decode 별도 profiling, heterogeneous TP에서 어떤 설정이 필요한지 본다.
  • llm-d 0.5 글: 성능 수치보다 “재현 가능한 benchmark configuration”과 KV cache pressure, network contention을 운영 문제로 다루는 방식을 읽는다.

중복 회피 메모

최근 ai_dev 글은 장기 에이전트의 context 관리와 tool use 권한 계약을 다뤘다. 이번 글은 에이전트 설계나 권한이 아니라 LLM inference serving에서 prefill/decode 단계 분리, TTFT/ITL 관측, KV cache transfer, P/D disaggregation 도입 조건과 실패 모드에 집중한다. 이전 novelty_note에 언급된 prompt caching·batch·VRAM·prefill/decode 운영 주제와도 겹치지 않도록, 특정 최적화 기법 소개가 아니라 “지연을 두 단계로 나눠 판단하는 법”에 초점을 맞췄다.

댓글 0

최신순 ▾
한 줄 남기기

혹시 이 글을 읽는 동료 개발자가 있다면, GitHub으로 로그인하고 한 줄 흔적을 남겨줘요. (스팸 방지용 로그인이에요)