llm

BF16이 이긴 콜드 캐시 서빙 비교 - Qwen3-14B AWQ의 더 큰 KV 캐시가 TTFT를 줄이지 못한 이유

Qwen3-14B를 BF16과 AWQ로 각각 서빙해 콜드 캐시에서 처리량과 TTFT를 재봤습니다

2026.09.06 · 9 min read

가중치를 줄이면 서빙도 빨라진다는 기대

AWQ는 가중치를 4bit로 압축합니다. 같은 GPU 메모리에서 가중치가 차지하는 자리가 줄면 남는 자리는 KV 캐시로 갑니다. 여기서 나오는 자연스러운 기대는 두 가지입니다.

  1. KV 캐시가 커지면 더 많은 요청을 동시에 담을 수 있습니다.
  2. 캐시가 크면 프리픽스 재사용이 늘어나 첫 토큰까지 걸리는 시간(TTFT)도 줄어들 것입니다.

이번 실험은 이 두 번째 기대만 따로 떼어 검증합니다. Qwen3-14B의 BF16 버전과 AWQ 버전에 정확히 같은 트래픽을 보내고, 처리량과 TTFT·TPOT를 나란히 잽니다.

실험 설계 - 콜드 캐시를 어떻게 통제했나

워크로드와 시드

두 조건 모두 vllm bench serve의 prefix_repetition 데이터셋을 씁니다.

  • 프리픽스 길이 3,072 토큰
  • 서픽스 64 토큰
  • 프리픽스 종류 160개
  • 출력 길이 32 토큰
  • 프롬프트 480개
  • request-rate 10 req/s(포아송, burstiness 1.0)
  • max-concurrency 32
  • temperature 0 완전히 동일합니다.

--ignore-eos=True가 켜져 있어 출력이 32 토큰으로 강제 고정됩니다. 그 결과 이번 트래픽은 입력 1,505,280 토큰 대 출력 15,360 토큰, 대략 98:1로 prefill이 지배하는 워크로드입니다. TTFT 쪽 결과가 이 글의 핵심이 되는 이유이기도 합니다.

서버 환경

  • 두 조건 모두 이미지 docker.io/vllm/vllm-openai:v0.11.0을 쓰고, 모델명만 다를 뿐 --max-model-len=8192, --gpu-memory-utilization=0.90, --enable-prefix-caching, --block-size=16, replica 2개·GPU 1개씩까지 동일한 설정입니다.

Replica 분포와 경로

이번 벤치마크에서는 클라이언트가 파드를 직접 겨냥하지 않고 k8s Service 하나에 480개 요청을 전부 보내고 Service가 2개 replica로 분산시켜 진행했습니다.

조건 Replica 요청 분포 집계 prefix-cache 히트율
BF16 243 / 237 30.4%
AWQ 238 / 242 39.2%

(BF16: 7w92m 243건·히트율 31.7%, pm7wp 237건·히트율 28.9%. AWQ: 4qf68 238건·히트율 38.4%, t9p25 242건·히트율 40.1%. 집계는 두 pod의 hits/queries를 합산한 값입니다.)

클라이언트 (seed 1788646381 · 480 requests)
      │
      ├──▶ k8s Service  vllm-qwen3-14b
      │      ├──▶ BF16 replica 7w92m
      │      └──▶ BF16 replica pm7wp
      │
      └──▶ k8s Service  vllm-qwen3-14b-awq
             ├──▶ AWQ replica 4qf68
             └──▶ AWQ replica t9p25

4개 replica 각각에서
      └──▶ vLLM /metrics 카운터 before·after 스냅샷

클라이언트는 파드가 아니라 k8s Service를 호출하고, Service가 2개 replica로 요청을 나눕니다. 관측은 Service 바깥에서 클라이언트가 보는 지표(TTFT·TPOT·처리량)와 각 replica 안에서 찍는 /metrics 카운터, 두 갈래로 수집됩니다.

결과 - 처리량과 지연

지표 BF16 AWQ AWQ 변화
성공 요청 수 480 480 동일
벤치마크 소요 시간 85.51 s 91.40 s +6.9%
총 토큰 처리량 17,782.44 tok/s 16,637.20 tok/s -6.4%
Median TTFT 621.90 ms 796.22 ms +28.0%
P99 TTFT 3,481.34 ms 5,914.17 ms +69.9%
Mean TPOT 154.86 ms 159.14 ms +2.8%
Median TPOT 160.89 ms 161.32 ms +0.3%
Mean E2E 지연 5,558.82 ms 5,965.20 ms +7.3%
P99 E2E 지연 9,680.65 ms 14,629.57 ms +51.1%

위 표의 모든 지표에서 BF16 쪽이 낫습니다. 격차는 TPOT보다 TTFT에서 훨씬 큽니다. Mean TPOT는 2.8% 차이인데 Median TTFT는 28.0%, P99 TTFT는 69.9% 차이입니다.

관찰 - 히트율은 AWQ가 높았지만 TTFT는 더 느렸다

AWQ는 두 replica 모두 BF16보다 prefix-cache 히트율이 높습니다(집계 39.2% vs 30.4%). 프리픽스를 재사용한 비율이 더 높았다는 뜻인데, 그럼에도 TTFT는 AWQ가 더 나쁩니다. 캐시를 더 잘 맞혔는데 응답은 더 늦었습니다.

아직 모르는 것 - AWQ의 토큰 간 간격이 이상하게 갈라집니다. AWQ의 median ITL은 21.05 ms인데 mean ITL은 159.14 ms입니다. BF16은 median 190.12 ms, mean 154.86 ms로 이런 괴리가 없습니다. AWQ 쪽 토큰 간 간격 분포가 강하게 이봉(bimodal)이라는 뜻으로 읽히지만, 저장된 아티팩트에 개별 요청 단위 타임라인이 없어 원인은 확인하지 못합니다.

아직 모르는 것 - 실측 최대 동시 요청 수가 설정값을 넘습니다. 벤치마크 결과 JSON의 .max_concurrent_requests는 BF16 42, AWQ 47입니다. 벤치 설정은 max_concurrency=32였는데 실측값이 그보다 큽니다. 클라이언트 세마포어 32와 이 지표가 정확히 무엇을 세는지(요청 정의, 집계 시점 등)가 어떻게 다른지 확인할 자료가 없어, 왜 32를 넘겼는지는 미해결로 남겨둡니다.

주의 - 두 개의 "토큰 수"를 섞지 않습니다. 벤치마크 결과 JSON의 total_input_tokens는 1,505,280입니다. 같은 두 replica의 prefix_cache_queries_total 합계는 1,505,293으로 13 토큰이 다릅니다. 정의가 다른 두 카운터라 상호대체하지 않고, 처리량·지연 계산에는 JSON 값을, 히트율 계산에는 /metrics 카운터 값을 각각 그대로 씁니다.

KV 캐시 용량 - AWQ 402,400 토큰 vs BF16 283,408 토큰

AWQ의 KV 캐시 용량은 402,400 토큰, BF16은 283,408 토큰입니다.

이번 요청 하나는 prefix 3,072 + suffix 64에 출력 32를 더해 총 3,168 토큰입니다. 이 크기 기준으로 BF16은 약 89.4개, AWQ는 약 127.0개 요청분을 담습니다. 설정 동시성 32는 물론 실측 최대 동시 요청 수(BF16 42, AWQ 47)보다도 훨씬 큽니다. 이번 워크로드에서는 두 조건 모두 KV 캐시 용량이 병목이 아니었던 것으로 보입니다.

AWQ 4bit 양자화
      │
      ▼
가중치 메모리 절감 (관찰)
      │
      ▼
KV 캐시 용량 증가  283,408 → 402,400 토큰
      │
      ├──▶ 요청 3,168토큰 기준 동시 여유
      │      BF16 89.4개 · AWQ 127.0개
      │      (설정 32, 실측 42/47보다 큼)
      │
      ├╌╌▶ TTFT·처리량 개선   ← 가설, 검증 안 됨
      │
      └──▶ 실측: Median TTFT +28.0%
             P99 TTFT +69.9% · 처리량 −6.4%

실선은 관찰(가중치 절감→캐시 용량 증가, 동시 여유 계산)입니다. 점선은 "캐시가 크면 지연도 준다"는 검증되지 않은 가설이고, 실제 측정은 그 가설과 반대 방향입니다.

해석 - KV 캐시 용량과 요청 지연은 다른 질문

KV 캐시 용량은 "동시에 얼마나 많은 요청의 상태를 담아둘 수 있는가"를 답합니다. TTFT는 "한 요청이 첫 토큰을 받기까지 얼마나 걸리는가"를 답합니다. 두 질문은 서로 다른 축입니다. 캐시가 커도 이미 32개 동시성 안에서 여유가 남아 있다면, 그 여유는 지연을 줄이는 데 쓰이지 않고 그냥 미사용 상태로 남습니다.

이번 결과가 보여주는 것은 AWQ의 캐시 용량 우위가 이 워크로드(동시성 32, 프리필 지배적, 480 요청)에서는 지연 개선으로 이어지지 않았다는 것 입니다.

미확인 사항 왜 AWQ의 TTFT가 더 나빴는지는 이 자료로 단정할 수 없습니다. GPU 커널 시간, CPU 스케줄링 시간, 큐 대기 시간이 분해되어 저장돼 있지 않습니다. 이 질문에 답하려면 같은 워크로드로 BF16·AWQ 각각의 Nsight 트레이스를 떠서 GEMM/어텐션 커널 시간과 CUDA API 갭을 비교해야 합니다.

[관찰]
  총 토큰 처리량   BF16 17,782 > AWQ 16,637 tok/s
  Median TTFT     BF16 621.9 < AWQ 796.2 ms
  P99 TTFT        BF16 3,481 < AWQ 5,914 ms
  Mean TPOT       BF16 154.9 ≈ AWQ 159.1 ms
  히트율          BF16 30.4% < AWQ 39.2%
      │
      ├╌╌▶ [해석] 이 워크로드(동시성 32)에서
      │      AWQ 캐시 여유는 미사용 용량으로 보임
      │      (근거: 히트율)
      │
      └╌╌▶ [미해결] TTFT 격차의
             커널·큐잉·스케줄링 비중
             (근거: Median/P99 TTFT)

관찰(위)은 전부 벤치 JSON과 /metrics 카운터에서 직접 나온 값입니다. 해석과 미해결(아래)은 그 관찰을 넘어서는 설명이라 실선이 아니라 별도 칸으로 분리했습니다.

결론 - 이 조건에 한정

이 A100 계열로 추정되는 노드(a100-002, manifest에 GPU 기종 고정 없음) 위, vLLM 0.11.0, Qwen3-14B와 Qwen3-14B-AWQ, k8s Service를 경유한 480개 prefix_repetition 요청(프리필 지배적, 동시성 32) 조건에서는 BF16이 처리량·TTFT·TPOT·E2E 지연 모든 지표에서 AWQ를 이겼습니다.

이 결론은 이 하드웨어·모델·워크로드 조합에 한정됩니다. 다른 GPU, 다른 모델 크기, 더 긴 출력, 더 높은 동시성, 또는 pod 직접 분할 경로에서는 결과가 달라질 수 있습니다.

LLM serving 인사이트

  • 양자화의 가치는 지연보다 메모리 용량과 배포 가능성일 수 있습니다.
    • 이번 실험에서 AWQ의 KV 캐시 용량은 BF16보다 컸지만(실험 리포트 기준 402,400 vs 283,408 토큰) 이번 워크로드의 TTFT는 AWQ가 더 나빴습니다.
    • 반대로 같은 GPU 메모리에 더 큰 모델을 올리거나, 같은 모델을 더 적은 GPU로 서빙해야 하는 상황에서는 AWQ의 가치가 지연이 아니라 "애초에 배포 가능한가"에서 나올 수 있습니다.
  • prompt/output 비율, 동시성, 캐시 히트율이 결과를 뒤집을 수 있습니다.
    • 이번 워크로드는 입력 대 출력이 약 98:1로 prefill이 지배적이고 max-concurrency는 32였습니다.
    • 출력이 훨씬 긴 워크로드나 동시성이 KV 캐시 용량에 근접하는 워크로드였다면 TPOT나 처리량 쪽에서 AWQ의 여유 용량이 실제로 쓰였을 가능성이 있습니다.
  • 평균만이 아니라 median·P99, replica 분포, 캐시 상태를 함께 봐야 합니다.
    • 이번 실험에서 Mean TPOT 차이는 2.8%로 작았지만 P99 TTFT 차이는 69.9%로 컸습니다.
    • 평균 하나만 봤다면 두 조건이 비슷하다고 잘못 읽었을 것입니다.
    • replica별 요청 분포를 확인하지 않았다면 "한쪽 replica가 트래픽을 독차지해서 생긴 왜곡"이라는 의심을 배제하지 못했을 것입니다.
  • benchmark는 "무엇을 재는지"가 모델 형식만큼 결론을 좌우합니다.
    • 같은 두 서버라도 Service 경로로 재는지 pod 직접 경로로 재는지, ignore_eos로 출력 길이를 고정하는지에 따라 같은 실행에서도 다른 숫자와 다른 신뢰도가 나옵니다.
    • 이번 결과의 신뢰도는 콜드 캐시 전제 확인에서 나옵니다.

글 인덱스로 돌아가기