Qwen 27B KV 캐시 양자화로 262K 컨텍스트 80+ t/s 달성하기

TL;DR

단일 RTX 4090 환경에서 262K 컨텍스트 기반 80+ t/s를 달성한 KV 캐시 양자화와 MTP 결합 사례를 분석한다. Qwen3.6-27B 모델에 MTP(Medusa Tree Processing) 헤드를 이식하고, TBQ4_0 기반 KV 캐시 양자화로 VRAM 병목을 해소하여 추론 속도를 약 2배 향상시켰다. 실무 적용을 위한 빌드 방법과 아키텍처 상세, 그리고 손실 압축 기반 양자화의 트레이드오프를 짚어본다.

배경: 장문맥 추론의 메모리 병목과 추론 지연

대규모 언어 모델(LLM)을 단일 GPU에서 구동할 때, 200K 이상의 장문맥(Long Context) 처리는 VRAM 한계와 직결된다. 특히 KV 캐시는 시퀀스 길이에 선형 비례하여 메모리를 점유하므로, 24GB VRAM의 RTX 4090에서 27B 파라미터 모델을 262K 컨텍스트로 구동하는 것은 기본적으로 불가능에 가깝다. 또한 추론 단계에서 메모리 대역폭 병목(Memory-bound)으로 인해 디코딩 속도는 43 t/s 수준에 머무른다. 이를 해결하기 위해 메모리 절감을 위한 KV 캐시 양자화와 연산 효율 증대를 위한 MTP(Medusa Tree Processing)의 결합이 필수적인 과제로 대두되었다.

핵심 메커니즘: MTP 헤드 이식과 TBQ4_0 양자화 원리

MTP 헤드 이식 (Grafting) 아키텍처

MTP는 기존의 자기회귀(Autoregressive) 디코딩을 대체하는 Speculative Decoding의 일종이다. 기본 모델의 마지막 Transformer 레이어(예: 32번째 레이어)의 은닉 상태(Hidden State)를 공유하여, 독립적인 헤드들을 병렬로 부착해 다음 토큰 후보들을 동시에 예측한다.

이식(grafting) 과정은 기존 LM 헤드의 가중치를 동결(Freeze)한 상태에서, 새로운 Medusa 헤드(보통 1~3개의 Residual Block 구조)를 부착하여 파인튜닝하는 방식으로 이루어진다. 본 구현에서는 3개의 초안(Draft) 토큰을 생성하며, 약 73%의 초안 수락률(Draft Acceptance Rate)을 달성하여 실제 디코딩 스텝을 획기적으로 줄인다.

# MTP 헤드 이식 구조 의사코드
class QwenWithMTP(nn.Module):
    def __init__(self, base_model):
        self.base_model = base_model
        self.lm_head = base_model.lm_head
        # 기존 LM 헤드 동결
        self.lm_head.weight.requires_grad = False 

        # 마지막 레이어 출력에 Medusa 헤드 부착 (3개 초안)
        self.medusa_heads = nn.ModuleList([
            ResidualBlock(base_model.config.hidden_size, 
                          base_model.config.vocab_size) 
            for _ in range(3)
        ])

    def forward(self, input_ids):
        hidden_states = self.base_model.model(input_ids)
        # 기본 토큰 및 초안 토큰 병렬 생성
        base_logits = self.lm_head(hidden_states[-1])
        draft_logits = [head(hidden_states[-1]) for head in self.medusa_heads]
        return base_logits, draft_logits

MTP 트리 검증(Tree Verification) 알고리즘

MTP의 핵심은 초안 토큰을 단순 선형 시퀀스가 아닌 트리 구조로 구성하여 한 번의 포워드 패스로 복수의 후보 경로를 동시에 검증하는 데 있다. 각 Medusa 헤드 $i$는 위치 $t+i+1$의 토큰 분포를 독립적으로 예측하며, 이 예측들을 조합해 후보 트리를 구성한다.

[base: t]
    ├── [head_0: A] ── [head_1: X] ── [head_2: P]  (경로 1: A→X→P)
    │                └── [head_2: Q]               (경로 2: A→X→Q)
    └── [head_0: B] ── [head_1: Y] ── [head_2: R]  (경로 3: B→Y→R)

검증 단계에서는 트리의 모든 후보 경로를 하나의 어텐션 마스크(Tree Attention Mask)로 패킹하여 단일 포워드 패스로 처리한다. 각 후보 토큰은 자신의 조상 노드(Ancestor)만 어텐드할 수 있도록 마스킹되며, 기본 모델의 검증 결과와 일치하는 가장 긴 접두사(Prefix)를 수락한다.

def tree_verify(base_model, draft_tokens_tree, input_ids):
    """
    draft_tokens_tree: List[List[int]] — 각 헤드별 상위 k개 후보 토큰
    반환: 수락된 토큰 시퀀스
    """
    # 트리 후보 경로 생성 (cartesian product of top-k per head)
    candidates = build_candidates(draft_tokens_tree, top_k=2)
    # candidates shape: (num_paths, draft_len)

    # Tree Attention Mask 구성: 각 노드는 자신의 조상만 어텐드
    tree_mask = build_tree_attention_mask(candidates)

    # 단일 포워드 패스로 모든 경로 동시 검증
    with torch.no_grad():
        logits = base_model(
            input_ids=pack_candidates(input_ids, candidates),
            attention_mask=tree_mask
        )

    # 그리디 검증: 기본 모델 예측과 일치하는 최장 접두사 수락
    accepted = []
    for pos, candidate_token in enumerate(best_path(candidates, logits)):
        if candidate_token == argmax(logits[pos]):
            accepted.append(candidate_token)
        else:
            break  # 불일치 시점에서 중단, 해당 위치 토큰은 기본 모델 예측으로 대체
    return accepted

이 구조 덕분에 3개 헤드, 각 헤드 top-2 후보 기준으로 최대 7개 토큰(2³ − 1)을 단일 스텝에서 검증할 수 있으며, 73% 수락률 하에서 평균 유효 토큰 수는 스텝당 약 2.2개로 계산된다.

TBQ4_0: 손실 압축 기반 KV 캐시 양자화

4.25 bpv 비트 할당 구조

해당 구현에서 사용된 TBQ4_0(TurboQuant 4.25 bpv)은 명확한 손실 압축(Lossy Compression) 기법이다. 기존 FP16(16 bit) 대비 4.25 bpv로 비트 수를 대폭 줄이므로, 정보 손실이 필연적으로 발생한다. 4.25 bpv라는 수치는 다음과 같은 비트 할당 전략에서 비롯된다.

구성 요소 비트 수 비고
정수 양자화 값 4 bit INT4 표현 (−8 ~ +7)
블록 스케일 팩터 0.25 bit/원소 FP16 스케일 1개를 블록 64원소에 분배
디더링 오버헤드 0 bit (런타임) 저장 없이 추론 시 즉석 생성
합계 4.25 bpv

블록 크기 64를 기준으로, 64개 원소당 FP16 스케일 팩터 1개(16 bit)를 공유하면 스케일 오버헤드는 원소당 16/64 = 0.25 bit가 된다. 이를 INT4(4 bit)에 더하면 4.25 bpv가 도출된다.

TBQ4_0의 수학적 원리는 블록 단위 양자화(Block-wise Quantization)에 기반한다. KV 벡터를 블록(블록 크기 64)으로 분할하고, 각 블록의 절대 최댓값을 기준으로 스케일 팩터(Scale Factor, $S$)를 도출한다. 원본 값 $x$는 다음과 같이 양자화된다.

$$S = \frac{\max(|x_i|)}{2^{k-1} – 1}, \quad x_q = \text{round}\left(\frac{x}{S} + \epsilon\right)$$

여기서 $\epsilon \sim \mathcal{U}(-0.5, 0.5)$는 디더링 노이즈다. 디더링은 양자화 전 작은 균등 분포(Uniform) 노이즈를 의도적으로 주입해 체계적인 양자화 오차(Systematic Quantization Error)를 분산시키는 기법으로, 근사화로 인한 대역폭 손실(Banding)을 완화하지만 원본 복원은 불가능하다. 역양자화(Dequantization)는 $\hat{x} = x_q \cdot S$로 수행되며, 이 과정에서 디더링 노이즈는 제거되지 않아 복원 오차가 잔존한다.

코드 및 실행: llama.cpp 포크 빌드부터 추론까지

해당 실험은 llama.cpp의 포크 버전에서 구현되었다. 아래 명령어는 원문 저자가 공개한 실제 저장소와 파일명을 기준으로 작성되었다.

# 1. llama.cpp MTP 포크 클론 및 CUDA 빌드
git clone https://github.com/Indras-Mirror/llama.cpp-mtp
cd llama.cpp-mtp
cmake -B build -DGGML_CUDA=ON
cmake --build build --config Release -j$(nproc)

# 2. 실제 공개된 GGUF 모델 다운로드 (HuggingFace CLI 사용)
pip install huggingface_hub
huggingface-cli download \
  Indras-Mirror/Qwen3.6-27B-Heretic-v2-GGUF \
  Qwen3.6-27B-Heretic-v2-Q4_K_M.gguf \
  --local-dir ./models

# MTP 헤드 가중치 별도 다운로드
huggingface-cli download \
  Indras-Mirror/Qwen3.6-27B-Heretic-v2-GGUF \
  Qwen3.6-27B-Heretic-v2-MTP.gguf \
  --local-dir ./models

# 3. 추론 실행 (단일 RTX 4090, 262K 컨텍스트, TBQ4_0 KV 캐시 양자화 적용)
./build/bin/llama-cli \
  -m ./models/Qwen3.6-27B-Heretic-v2-Q4_K_M.gguf \
  --draft-model ./models/Qwen3.6-27B-Heretic-v2-MTP.gguf \
  -c 262144 \
  --temp 0.7 \
  -ngl 99 \
  -b 512 \
  -ctk q4_0 \
  -ctv q4_0 \
  --mlock \
  -p "Your prompt here"

재현 시 주의사항: -ctk q4_0 -ctv q4_0 플래그가 TBQ4_0 KV 캐시 양자화를 활성화하는 핵심 옵션이다. -ngl 99는 모든 레이어를 GPU에 오프로드하며, --mlock은 CPU 메모리 스왑을 방지한다. CUDA 버전은 12.1 이상, 드라이버 버전은 525.xx 이상을 권장한다.

실측 벤치마크 수치

원문 저자가 공개한 실측값과 비교 기준을 아래에 정리한다.

구성 컨텍스트 길이 디코딩 속도 VRAM 사용량
Q4_K_M, KV FP16, MTP 없음 8K 43 t/s ~20 GB
Q4_K_M, KV FP16, MTP 없음 262K OOM (불가) >24 GB
Q4_K_M, KV TBQ4_0, MTP 없음 262K ~52 t/s ~22 GB
Q4_K_M, KV TBQ4_0, MTP 적용 262K 80+ t/s ~23 GB

Perplexity 참고: 원문 저자는 출력 품질이 “견고해 보인다”고 주관적으로 평가했으나, 공식 Perplexity 수치는 공개하지 않았다. KV 캐시 양자화에 따른 Perplexity 열화는 일반적으로 FP16 대비 0.1~0.5 ppl 수준으로 보고되나, 본 구현에 대한 독립적 측정값은 현재 없다. 도입 전 자체 측정을 권장한다.

상세한 텐서 공유 및 커널 아키텍처 문서는 저자의 기술 블로그(https://indrasmirror.au/blog-mtp-shared-tensors-200k.html)에서 확인할 수 있다.

한계 및 실무 적용 시 고려사항

가장 치명적인 한계는 TBQ4_0의 손실 압축 특성이다. 4.25 bpv 수준의 KV 캐시 양자화는 문맥의 핵심 정보를 근사화 과정에서 소실시킬 위험이 존재한다. 저자는 출력 품질이 “견고해 보인다”고 주관적으로 평가했으나, 이는 체계적인 벤치마크(Perplexity 측정 등)를 통과한 수치가 아니다.

국내 실무 환경, 예컨대 토스(Toss)의 장문맥 금융 문서 RAG 파이프라인이나 네이버의 검색 증강 생성(SAG) 시스템에서는 128K 이상의 컨텍스트 처리 시 수치 데이터의 정확도가 절대적이다. 이런 도메인에서 TBQ4_0 기반 KV 캐시 양자화를 도입할 경우, 스케일 팩터 도출 시 미세한 수치 오차가 금융 지표나 계약서 특약 조항의 환각을 유발할 수 있다. 따라서 도입 전 RAG 검증 세트(Recall@K, 숫자 정보 추출 정확도) 기반의 엄격한 회귀 테스트가 선행되어야 한다.

또한 본 구현은 단일 GPU 환경에서만 검증되었으며, 멀티 GPU 텐서 병렬(Tensor Parallelism) 환경에서 MTP 헤드의 트리 검증 로직이 어떤 동기화 오버헤드를 발생시킬지는 미지수다.

결론

Qwen3.6-27B 모델에 MTP 헤드를 이식하고 TBQ4_0로 KV 캐시 양자화를 적용한 본 사례는, 단일 소비자용 GPU에서 262K 컨텍스트를 80+ t/s로 처리할 수 있다는 점에서 의의가 있다. 하지만 4.25 bpv 압축에 따른 정보 손실과 검증되지 않은 품질 평가는 치명적인 트레이드오프다. 실무자는 이를 즉각적인 프로덕션 해법으로 수용하기보다는, MTP 헤드 이식을 통한 추론 가속의 가능성을 타진하는 참고 구현(Reference Implementation)으로 바라보아야 한다. 향후 손실 양자화의 오차 한계를 엄밀히 통제하는 연구가 뒷받침될 때 비로소 장문맥 최적화의 실용성이 확보될 것이다.


출처: Got MTP + TurboQuant running — Qwen3.6-27B — 80+ t/s at 262K context on a single RTX 4090


댓글 남기기