Chunked Prefill은 긴 입력 프롬프트를 작은 청크(chunk)로 나누어 단계적으로 처리하는 기법입니다.
입력: 10,000 토큰
[████████████████████████]
전체를 한 번에 처리 (1초)
↓
첫 토큰 생성
TTFT: 1초
입력: 10,000 토큰
[████] 청크1 (100ms) → 첫 토큰 생성 시작! ✨
[████] 청크2 (100ms) ↘
[████] 청크3 (100ms) 백그라운드에서
[████] 청크4 (100ms) 계속 처리...
...
TTFT: 100ms (10배 개선!)
총 시간: 여전히 ~1초
# 의사 코드
def chunked_prefill(tokens, chunk_size=512):
chunks = split_into_chunks(tokens, chunk_size)
# 첫 번째 청크만 처리
first_chunk_kv = process_chunk(chunks[0])
# 첫 토큰 생성 시작
first_token = generate_token(first_chunk_kv)
yield first_token
# 나머지 청크는 병렬로 처리
for chunk in chunks[1:]:
chunk_kv = process_chunk(chunk)
merge_kv_cache(chunk_kv)
Step 1: 첫 청크 처리
입력: "당신은 AI 어시스턴트입니다. [청크1...]"
└─ 512 토큰만 prefill
KV 캐시: [청크1의 K, V]
↓
첫 토큰 생성: "안"
Step 2: 생성하면서 동시에 청크2 처리
출력: "안녕"
백그라운드: 청크2 prefill 중...
Step 3: 계속 생성 + 청크3 처리
출력: "안녕하"
백그라운드: 청크3 prefill 중...
긴 프롬프트 문제:
RAG 시스템:
문서: 50,000 토큰
질문: 50 토큰
일반 Prefill:
TTFT = 50,050 토큰 처리 = 5초 😰
Chunked Prefill:
TTFT = 512 토큰 처리 = 50ms 😊
일반 방식:
사용자: "질문..." [Enter]
... 5초 대기 ...
"응답이 안 오네?" 😠
Chunked 방식:
사용자: "질문..." [Enter]
즉시 응답 시작! 😊
"안녕하세요..."
128K 토큰 컨텍스트:
일반: TTFT 10-30초 (사용 불가능 수준)
Chunked: TTFT 100-200ms (실용적)
작은 청크 (128-256 토큰):
✅ 매우 빠른 TTFT
❌ 많은 반복 → 오버헤드
❌ 컨텍스트 단절 가능성
중간 청크 (512-1024 토큰):
✅ 좋은 TTFT
✅ 합리적인 오버헤드
✅ 균형잡힌 선택 (가장 일반적)
큰 청크 (2048+ 토큰):
✅ 낮은 오버헤드
❌ TTFT 개선 효과 감소
문제: 청크 간 어텐션 계산
해결책 1: Causal Masking
[청크1] → [청크2] → [청크3]
각 청크는 이전 청크들만 볼 수 있음
해결책 2: Incremental Attention
새 청크가 추가될 때마다
이전 청크들에 대한 어텐션 재계산
Option A: Pipeline Parallelism
생성 청크2 청크3
[토큰1][토큰2][토큰3] prefill prefill
↓ ↓ ↓ ↓ ↓
시간 →
Option B: Async Prefill
메인 스레드: 토큰 생성
백그라운드 스레드: 나머지 청크 prefill
프롬프트 길이: 10,000 토큰
청크 크기: 512 토큰
일반 Prefill:
TTFT = 1000ms
Chunked Prefill:
TTFT = 51ms (첫 청크만)
개선율: 95%!
중요: 총 시간은 거의 동일하거나 약간 증가
일반 Prefill:
Prefill: 1000ms
Generation: 2000ms (100토큰)
총: 3000ms
Chunked Prefill:
First chunk: 51ms → 첫 토큰
Remaining: 950ms (백그라운드)
Generation: 2000ms
총: ~3010ms (약간의 오버헤드)
→ TTFT는 95% 개선, 총 시간은 유사
def rag_with_chunked_prefill(query, documents):
# 문서들을 청크로 분할
chunks = []
for doc in documents:
chunks.extend(split_into_chunks(doc, 512))
# 첫 청크 + 질문으로 즉시 응답 시작
first_chunk = chunks[0] + query
first_kv = prefill(first_chunk)
# 첫 토큰 생성
yield generate_token(first_kv)
# 나머지 청크는 비동기로 처리
async_prefill_remaining(chunks[1:])
# 계속 생성
while not done:
yield generate_token()
문서: 50페이지 (30,000 토큰)
요청: "이 문서를 요약해줘"
일반:
사용자: ... 3초 대기 ...
AI: "이 문서는..."
Chunked:
사용자: [즉시]
AI: "이 문서는..." (0.1초)
백그라운드에서 나머지 컨텍스트 로딩 중
전체 코드베이스: 100,000 토큰
질문: "이 함수의 버그는?"
Chunked Prefill:
1. 관련 파일들의 첫 청크 분석
2. 즉시 초기 분석 시작
3. 백그라운드에서 전체 컨텍스트 로드
4. 더 정확한 답변으로 점진적 개선
def adaptive_chunk_size(total_tokens):
if total_tokens < 1000:
return total_tokens # 청크 불필요
elif total_tokens < 10000:
return 512
else:
return 1024 # 매우 긴 입력
중요도 순서로 청크 처리:
1순위: 질문 직전 컨텍스트
2순위: 질문 관련 섹션
3순위: 나머지 배경 정보
→ 더 빠르고 정확한 초기 응답
청크 처리 중 압축:
- 중요하지 않은 토큰의 KV는 양자화
- 메모리 절약
- 처리 속도 향상
✅ TTFT 극적 개선 (10-20배)
✅ 긴 컨텍스트 실용적으로 사용 가능
✅ 사용자 경험 크게 향상
✅ 응답이 즉시 시작되어 "반응형" 느낌
❌ 구현 복잡도 증가
❌ 약간의 총 시간 오버헤드 (3-5%)
❌ 초기 응답이 덜 정확할 수 있음
(전체 컨텍스트 로딩 전)
❌ 메모리 관리 복잡
시나리오: 같은 문서에 여러 질문
첫 질문:
청크1 prefill → 캐시 저장
청크2 prefill → 캐시 저장
...
두 번째 질문:
청크1 캐시 HIT! ✅
청크2 캐시 HIT! ✅
→ TTFT 더욱 개선!
첫 청크 처리 중:
작은 모델로 토큰 추측 시작
→ 더욱 빠른 TTFT
❌ 나쁜 예:
"...important inform|ation about..."
↑ 단어 중간에서 자름
✅ 좋은 예:
"...important information| about..."
↑ 단어 경계에서 자름
def robust_chunked_prefill(tokens):
try:
for chunk in chunks:
process_chunk(chunk)
except MemoryError:
# 청크 크기 줄이기
return retry_with_smaller_chunks()
except TimeoutError:
# 이미 처리된 청크로 생성 시작
return generate_with_partial_context()
추적할 지표:
- 청크당 처리 시간
- 청크 간 대기 시간
- 백그라운드 완료 시점
- 초기 vs 최종 응답 품질
실제 측정값 (대략적):
| 입력 길이 | 일반 TTFT | Chunked TTFT | 개선율 |
|---|---|---|---|
| 1K 토큰 | 100ms | 100ms | 0% |
| 5K 토큰 | 500ms | 51ms | 90% |
| 10K 토큰 | 1000ms | 51ms | 95% |
| 50K 토큰 | 5000ms | 102ms | 98% |
| 100K 토큰 | 10000ms | 102ms | 99% |
Chunked Prefill은 긴 컨텍스트를 다루는 현대 LLM 애플리케이션에서 필수적인 기법입니다. 특히 RAG, 문서 분석, 코드 리뷰 등에서 사용자 경험을 극적으로 개선할 수 있습니다!