Chunked Prefill

김동준·2025년 10월 20일

Chunked Prefill은 긴 입력 프롬프트를 작은 청크(chunk)로 나누어 단계적으로 처리하는 기법입니다.

기본 개념

기존 방식 (Monolithic Prefill)

입력: 10,000 토큰

[████████████████████████] 
전체를 한 번에 처리 (1초)
                          ↓
                    첫 토큰 생성

TTFT: 1초

Chunked Prefill 방식

입력: 10,000 토큰

[████] 청크1 (100ms) → 첫 토큰 생성 시작! ✨
[████] 청크2 (100ms) ↘
[████] 청크3 (100ms)  백그라운드에서
[████] 청크4 (100ms)  계속 처리...
...

TTFT: 100ms (10배 개선!)
총 시간: 여전히 ~1초

작동 원리

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)

2. 단계별 처리

Step 1: 첫 청크 처리
입력: "당신은 AI 어시스턴트입니다. [청크1...]"
      └─ 512 토큰만 prefill
KV 캐시: [청크1의 K, V]
↓
첫 토큰 생성: "안"

Step 2: 생성하면서 동시에 청크2 처리
출력: "안녕"
백그라운드: 청크2 prefill 중...

Step 3: 계속 생성 + 청크3 처리
출력: "안녕하"
백그라운드: 청크3 prefill 중...

왜 필요한가?

1. TTFT 개선

긴 프롬프트 문제:

RAG 시스템:
문서: 50,000 토큰
질문: 50 토큰

일반 Prefill:
TTFT = 50,050 토큰 처리 = 5초 😰

Chunked Prefill:
TTFT = 512 토큰 처리 = 50ms 😊

2. 사용자 경험

일반 방식:
사용자: "질문..." [Enter]
        ... 5초 대기 ...
        "응답이 안 오네?" 😠

Chunked 방식:
사용자: "질문..." [Enter]
        즉시 응답 시작! 😊
        "안녕하세요..."

3. 긴 컨텍스트 지원

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

성능 비교

TTFT 개선

프롬프트 길이: 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% 개선, 총 시간은 유사

실제 적용 사례

1. RAG 시스템

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()

2. 긴 문서 요약

문서: 50페이지 (30,000 토큰)
요청: "이 문서를 요약해줘"

일반:
사용자: ... 3초 대기 ...
AI: "이 문서는..."

Chunked:
사용자: [즉시]
AI: "이 문서는..." (0.1초)
    백그라운드에서 나머지 컨텍스트 로딩 중

3. 코드 분석

전체 코드베이스: 100,000 토큰
질문: "이 함수의 버그는?"

Chunked Prefill:
1. 관련 파일들의 첫 청크 분석
2. 즉시 초기 분석 시작
3. 백그라운드에서 전체 컨텍스트 로드
4. 더 정확한 답변으로 점진적 개선

최적화 기법

1. 적응형 청크 크기

def adaptive_chunk_size(total_tokens):
    if total_tokens < 1000:
        return total_tokens  # 청크 불필요
    elif total_tokens < 10000:
        return 512
    else:
        return 1024  # 매우 긴 입력

2. 우선순위 기반 청크 처리

중요도 순서로 청크 처리:

1순위: 질문 직전 컨텍스트
2순위: 질문 관련 섹션
3순위: 나머지 배경 정보

→ 더 빠르고 정확한 초기 응답

3. KV 캐시 압축

청크 처리 중 압축:
- 중요하지 않은 토큰의 KV는 양자화
- 메모리 절약
- 처리 속도 향상

장단점

장점

✅ TTFT 극적 개선 (10-20배)
✅ 긴 컨텍스트 실용적으로 사용 가능
✅ 사용자 경험 크게 향상
✅ 응답이 즉시 시작되어 "반응형" 느낌

단점

❌ 구현 복잡도 증가
❌ 약간의 총 시간 오버헤드 (3-5%)
❌ 초기 응답이 덜 정확할 수 있음
   (전체 컨텍스트 로딩 전)
❌ 메모리 관리 복잡

다른 기법과의 결합

Chunked Prefill + Prefix Caching

시나리오: 같은 문서에 여러 질문

첫 질문:
청크1 prefill → 캐시 저장
청크2 prefill → 캐시 저장
...

두 번째 질문:
청크1 캐시 HIT! ✅
청크2 캐시 HIT! ✅
→ TTFT 더욱 개선!

Chunked Prefill + Speculative Decoding

첫 청크 처리 중:
작은 모델로 토큰 추측 시작
→ 더욱 빠른 TTFT

실전 구현 고려사항

1. 청크 경계

❌ 나쁜 예:
"...important inform|ation about..."
           ↑ 단어 중간에서 자름

✅ 좋은 예:
"...important information| about..."
           ↑ 단어 경계에서 자름

2. 에러 처리

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()

3. 모니터링

추적할 지표:
- 청크당 처리 시간
- 청크 간 대기 시간
- 백그라운드 완료 시점
- 초기 vs 최종 응답 품질

벤치마크

실제 측정값 (대략적):

입력 길이일반 TTFTChunked TTFT개선율
1K 토큰100ms100ms0%
5K 토큰500ms51ms90%
10K 토큰1000ms51ms95%
50K 토큰5000ms102ms98%
100K 토큰10000ms102ms99%

Chunked Prefill은 긴 컨텍스트를 다루는 현대 LLM 애플리케이션에서 필수적인 기법입니다. 특히 RAG, 문서 분석, 코드 리뷰 등에서 사용자 경험을 극적으로 개선할 수 있습니다!

profile
Story Engineer

0개의 댓글