처리량이 올랐다는 건
계산이 빨라진 걸까,
덜 계산한 걸까.
Qwen3-14B 한 대를 vLLM으로 띄워놓고, 여덟 단계에 걸쳐 처리량을 2.7배까지 끌어올리는 과정입니다. 그리고 그 2.7배가 어디에서 왔는지를 프로파일러로 끝까지 따라가 봅니다.
들어가며
서빙 최적화 문서를 읽다 보면 항상 같은 곳에서 막힙니다. "AWQ를 켰더니 처리량이 2.7배가 됐다"까지는 읽히는데, 왜 2.7배인지가 안 읽힙니다. 4비트로 줄였으니 계산이 4배 빨라진 걸까요? 그렇게 생각하면 그다음 실험이 전부 어긋납니다.
이 장은 그 어긋남을 세 번 정면으로 깨뜨립니다. 같은 모델·같은 설정인데 트래픽만 바꿔도 처리량이 2.4배 차이가 나고, 양자화를 해도 GPU가 실제로 계산한 시간은 거의 그대로이며, GPU를 네 장 붙이면 어떤 서버에서는 빨라지고 어떤 서버에서는 오히려 느려집니다.
- 처리량이 오르는 진짜 경로는 "메모리가 남는다 → KV 캐시가 커진다 → 배칭이 촘촘해진다 → 스케줄링이 효율화된다"입니다. 연산이 빨라진 게 아닙니다.
- GPU를 더 붙일지는 GPU 개수가 아니라 GPU 사이를 잇는 선(NVLink냐 PCIe냐)이 결정합니다. PCIe만 있는 서버에서는 한 장이 네 장보다 빠릅니다.
- 처리량만 목표라면 분산 서빙보다 단순 복제가 3배 유리할 수 있습니다. 분산 서빙의 값어치는 처리량이 아니라 지연시간과 모델 크기에 있습니다.
접는 상자(▸)는 안 열어도 됩니다. 안에는 실제 명령어, 로그 원문, 계산식이 들어 있습니다. 본문만 읽어도 이야기는 끝까지 이어집니다.
그림은 눈으로 보는 게 아니라 만지는 겁니다. 슬라이더를 극단까지 밀어보세요. 노란 상자에 "해볼 것"이 적혀 있습니다.
이 페이지의 장 번호와 책의 장 번호는 다릅니다. 책을 가리킬 때는 "책 6장"처럼 앞에 '책'을 붙였습니다.
다루는 것
vLLM + Qwen3-14B를 예제로 한 8단계 최적화 절차 — 하드웨어 점검, 벤치마크 트래픽 설계, 지표 정의, 베이스라인 측정, AWQ 양자화, 워크로드별 튜닝, 분산 서빙 비교, 그리고 다섯 가지 트레이드오프입니다.
다루지 않는 것
양자화가 정확도를 얼마나 떨어뜨리는지는 다루지 않습니다(책 6장 소관입니다). CUDA 커널을 직접 짜는 법, 멀티 노드 구성(prefill–decode 분리), 그리고 LMCache·speculative decoding의 실습 코드도 여기 없습니다(책 7장). 이 페이지는 측정하고 해석하는 방법에 집중합니다.
이 장에 나오는 모든 수치는 특정 GPU · 특정 트래픽에서 나온 하나의 사례입니다. 저자들도 마지막에 "이 실습 자체가 전형적인 정적 최적화였다"고 고백합니다. 가져갈 것은 474나 1,280 같은 숫자가 아니라, 그 숫자를 만들어낸 인과의 사슬입니다.
먼저 목표를 정합니다 — 무엇을 올리려고 하는 건지부터.
01최적화 계획 — 무엇을 올릴 것인가
한 줄 요약: 목표를 "처리량"으로 못 박고, 지연시간은 허용 범위 안에서만 지킵니다.
"빠르다"는 말이 두 가지를 동시에 가리키기 때문입니다. 초당 몇 토큰을 처리하느냐(처리량)와 내 요청 하나가 몇 밀리초 만에 답하느냐(지연시간)는 서로 다른 값이고, 자주 서로를 밀어냅니다.
둘 중 무엇을 올릴지 정하지 않고 튜닝을 시작하면, 숫자가 올라가도 좋아진 건지 나빠진 건지 판단할 수가 없습니다.
- throughput (처리량)
- 단위 시간에 처리한 토큰 수. 보통 TPS(tokens per second)로 씁니다. 대부분의 LLM 과금이 토큰 기준이라, 처리량이 곧 토큰당 비용입니다.
- latency (지연시간)
- 요청 하나가 기다린 시간. 첫 글자가 나올 때까지(TTFT), 글자와 글자 사이(ITL) 등으로 나눠 봅니다.
- batching (배칭)
- 여러 요청을 한 번에 묶어 GPU에 넣는 것. 묶으면 GPU가 놀지 않아 처리량이 오르지만, 묶이기를 기다리는 시간이 생깁니다.
- concurrency (동시성)
- 서버가 동시에 붙잡고 있는 요청 수. 배칭의 재료입니다.
비유 — 엘리베이터
1층 로비의 엘리베이터를 생각해 보세요. 사람이 오는 대로 즉시 출발시키면 기다림은 없지만 한 번에 한 명씩만 실어 나릅니다. 반대로 정원이 찰 때까지 문을 붙잡고 있으면 한 번에 열 명을 실어 나르지만, 먼저 탄 사람은 그만큼 문 앞에서 기다립니다.
처리량을 올린다는 건 엘리베이터를 꽉 채운다는 뜻이고, 지연시간을 줄인다는 건 문을 빨리 닫는다는 뜻입니다. 이 장은 "승객이 못 참을 만큼은 기다리게 하지 않으면서 최대한 꽉 채우기"를 목표로 삼습니다.
작은 숫자로 한 번
같은 GPU 한 장에서 서버 A는 초당 500토큰, 서버 B는 초당 1,500토큰을 처리한다고 해봅시다.
하드웨어를 한 대도 더 사지 않고 비용이 3분의 1이 됩니다. 이것이 처리량을 첫 번째 목표로 두는 이유입니다.
동시성을 올리면 무슨 일이 일어나는가
↔ 슬라이더동시성 10에서 474 TPS · TTFT 104ms · ITL 43ms라는 실측값에 곡선을 맞춘 모형입니다. 다른 지점은 실측이 아닙니다.
여덟 단계 계획
- 하드웨어 점검GPU 메모리 크기, 대역폭, 인터커넥트를 먼저 확인합니다. 나중에 결과를 해석할 근거가 됩니다.
- 벤치마크 트래픽 생성실제 트래픽을 닮은 데이터셋을 고릅니다. 여기서 최적화 방향이 갈립니다.
- 평가 지표 정의비교 기준을 미리 못 박습니다. 나중에 정하면 유리한 숫자만 고르게 됩니다.
- 서빙 서버 구성기본 설정 그대로 vLLM을 띄우고, 로그에서 메모리 분배를 읽습니다.
- 베이스라인 벤치마크튜닝 없는 상태의 숫자를 확보합니다. 모든 비교의 원점입니다.
- 양자화 벤치마크AWQ 4비트로 같은 실험을 반복해 차이를 봅니다.
- 추가 기법 적용워크로드 성격에 맞는 캐싱·배칭 옵션을 고릅니다.
- 분산 서빙 벤치마크GPU를 2장·4장으로 늘려보고, 하드웨어가 다르면 결론이 뒤집히는 걸 확인합니다.
실습 환경 — GPU가 없어도 따라갈 수 있습니다
원문 실습은 AWS g6e.2xlarge(NVIDIA L40S, VRAM 약 46GB)에서 진행합니다.
멀티 GPU 구간에서는 g6e.12xlarge(L40S ×4)와 p4d.24xlarge(A100 ×8)를 씁니다.
대안으로는 Runpod A40(48GB)이 시간당 $0.44 수준이고,
RTX 4070(16GB) 같은 데스크톱이라면 모델을 Qwen3-4B → Qwen3-4B-AWQ로 낮춰 같은 논리를 재현할 수 있습니다(8장에 그 결과가 있습니다).
저자들이 각 단계의 실행 결과가 담긴 노트북(model_optimization_in_practice.ipynb)을 함께 제공하므로, GPU 없이도 과정과 출력은 그대로 따라갈 수 있습니다.
- 정의 — 처리량은 초당 토큰 수, 지연시간은 요청 하나가 기다린 시간. 목표는 "처리량 우선, 지연시간은 허용 범위 유지".
- 성질 — 배칭은 처리량을 올리고 지연시간을 늘립니다. 동시성을 올릴수록 처리량 이득은 줄고 지연 손해는 늘어납니다.
- 쓰임 — 대부분의 과금이 토큰 기준이므로, 처리량은 곧 토큰당 비용입니다. 3배 처리량 = 3분의 1 비용.
목표가 정해졌으니, 이제 무대를 확인할 차례입니다 — 어떤 GPU 위에서 하는 실험인가.
02Step 1 — 코드를 짜기 전에 GPU부터 본다
한 줄 요약: nvidia-smi 한 줄이 나중에 "왜 느린가"를 가르는 진단 기준이 됩니다.
나중에 느린 이유를 구분하기 위해서입니다. "GPU 사용률이 40%인데 처리량이 낮다"와 "GPU 사용률이 97%인데 처리량이 낮다"는 완전히 다른 병입니다. 앞의 것은 배칭·스케줄링 문제고, 뒤의 것은 메모리나 커널 문제입니다.
기준선이 없으면 이 구분을 할 수 없습니다. 그래서 벤치마크 전 사전 점검(pre-flight check)을 먼저 합니다.
- performance state (P-state)
- GPU의 전력·클럭 상태. P8은 유휴, P0/P1은 최고 속도로 일하는 중입니다. 벤치마크를 돌리는데 P8이면 요청이 안 들어오고 있다는 뜻입니다.
- GPU utilization (사용률)
- GPU가 일을 하고 있던 시간의 비율. "얼마나 열심히"가 아니라 "얼마나 자주"에 가깝습니다.
- compute capability
- GPU 세대를 나타내는 숫자(예: 8.9). 어떤 커널·양자화 방식을 쓸 수 있는지가 여기서 갈립니다.
- VRAM
- GPU에 붙어 있는 메모리. 모델 가중치와 KV 캐시가 여기를 나눠 씁니다. 5장의 주인공입니다.
비유 — 병원 도착 직후의 활력징후
환자가 오면 의사는 증상을 듣기 전에 체온·맥박·혈압부터 잽니다. 열이 39도인 상태에서 "머리가 아프다"는 말은 정상 체온일 때와 전혀 다른 의미를 갖기 때문입니다.
nvidia-smi가 GPU의 활력징후입니다. 전력·사용률·메모리·온도를 한 화면에서 보여주고, 이 네 개의 조합이 진단을 만듭니다.
작은 숫자로 한 번
사용률과 전력, 두 숫자로 병을 가른다
↔ 슬라이더 2개같은 "느리다"는 증상이 네 개의 다른 진단으로 갈립니다. 사용률 하나만 보면 이 구분을 못 합니다.
실제 명령과 출력
필요한 항목만 골라 CSV로 뽑을 수도 있습니다.
nvidia-smi --query-gpu=name,compute_cap,memory.free,memory.used,memory.total --format=csv name, compute_cap, memory.free [MiB], memory.used [MiB], memory.total [MiB] NVIDIA L40S, 8.9, 46050 MiB, 18 MiB, 46068 MiB
확인할 네 가지는 이렇습니다.
CUDA·드라이버 버전 — 쓰려는 서빙 프레임워크와 호환되는지.
P-state — P8이면 유휴, P0/P1이면 최고 속도로 동작 중.
전력과 사용률 — 이 둘의 조합이 진단입니다(위 그림).
메모리 — 지금 몇 GB가 비어 있는지. 모델이 들어갈 자리가 있는지.
- 정의 —
nvidia-smi는 GPU의 활력징후. 이름·메모리·P-state·전력·사용률을 한 번에 보여줍니다. - 성질 — 사용률과 전력을 같이 봐야 진단이 갈립니다. 사용률↓+부하 있음 = 배칭/스케줄링 문제, 전력↑+처리량↓ = 메모리 병목이나 커널 비효율.
- 쓰임 — 벤치마크 전 사전 점검. 총 VRAM 46GB를 확인해 뒀기 때문에 5장에서 "27.5 + 11 = 38.5"라는 분배를 읽어낼 수 있습니다.
무대를 확인했습니다. 이제 무대에 올릴 관객, 즉 트래픽을 만들 차례입니다.
03Step 2 — 어떤 트래픽으로 잴 것인가
한 줄 요약: 데이터셋이 최적화 방향을 결정합니다. 아무 프롬프트나 넣고 재면 실서비스와 다른 답이 나옵니다.
입력이 길고 출력이 짧은 워크로드(prefill 위주)와, 입력이 짧고 출력이 긴 워크로드(decode 위주)는 서로 다른 기법을 요구합니다. 앞의 것은 캐싱으로, 뒤의 것은 speculative decoding으로 개선됩니다.
그러니 내 서비스를 닮지 않은 트래픽으로 튜닝하면, 튜닝한 만큼 엉뚱한 방향으로 갑니다.
- prefill / decode
- LLM 추론의 두 단계. prefill은 입력 프롬프트를 한꺼번에 읽는 단계, decode는 출력 토큰을 한 개씩 뱉는 단계입니다.
- prefix
- 프롬프트의 앞부분. 시스템 프롬프트나 공통 지시문처럼 여러 요청이 똑같이 갖고 시작하는 부분입니다.
- prefix caching
- 같은 prefix를 다시 계산하지 않고 앞서 계산해 둔 KV를 재사용하는 것. 이 장 전체에서 가장 큰 효과를 내는 기법입니다.
- synthetic dataset (합성 데이터셋)
- 실제 데이터의 성질만 흉내 내서 기계로 만든 데이터. 원하는 성질(반복률 등)을 정확히 조절할 수 있습니다.
- request rate / burstiness
- 초당 몇 개를 보낼지, 그리고 그 도착이 고르게 오는지 몰려서 오는지.
비유 — 도서관 대출대
도서관 대출 창구의 처리 속도를 재려고 합니다. 서로 다른 책을 든 사람 100명이 오는 것과, 같은 교재를 든 수업 수강생 100명이 오는 것은 완전히 다른 실험입니다. 뒤의 경우 직원은 책 정보를 한 번만 찾아 놓고 100명에게 재사용할 수 있으니까요.
이 장은 두 종류의 줄을 모두 세워봅니다. ShareGPT는 제각각인 손님, Prefix Repetition은 같은 교재를 든 손님입니다.
작은 숫자로 한 번
ShareGPT 100개 샘플의 길이 분포는 이렇습니다.
평균과 중앙값이 크게 벌어져 있다는 점(232.6 vs 141.5)도 중요합니다. 짧은 프롬프트가 다수이고 아주 긴 것 몇 개가 평균을 끌어올리고 있다는 뜻입니다. 실제 대화가 그렇습니다.
고유 prefix를 줄이면 재계산이 줄어든다
↔ 슬라이더 + 버튼재사용률이 오르면 GPU가 해야 할 prefill 계산량 자체가 줄어듭니다. 6장에서 이게 2.4배 처리량 차이로 나타납니다.
두 데이터셋과 부하 생성 명령
ShareGPT — 실제 사용자가 LLM과 나눈 대화를 모은 데이터셋. 다양한 문체와 대화 구조가 섞여 있어 실사용자 트래픽에 가깝습니다.
Prefix Repetition — 합성 데이터셋. 각 프롬프트가 공통 prefix로 시작한 뒤 고유한 suffix가 붙습니다.
--prefix-repetition-num-prefixes를 줄일수록 반복이 심해져 캐시 재사용 트래픽이 강해집니다.
vllm bench serve \ --backend vllm --base-url "http://localhost:8000" \ --model Qwen/Qwen3-14B \ --dataset-name prefix_repetition \ --num-prompts 1000 --request-rate 5 \ --prefix-repetition-prefix-len 256 \ --prefix-repetition-suffix-len 256 \ --prefix-repetition-num-prefixes 10 \ --prefix-repetition-output-len 128 \ --max-concurrency 10 \ --save-result --result-filename test_serve_results.txt
벤치마크 전에 inspect_dataset.py로 길이 분포와 샘플 프롬프트를 먼저 확인합니다.
모델을 추론시키는 코드가 아니라, "내가 지금 서버에 무엇을 보내려는가"를 미리 들여다보는 도구입니다.
- 정의 — ShareGPT는 실제 대화를 모은 데이터셋, Prefix Repetition은 공통 prefix를 인위적으로 넣은 합성 데이터셋입니다.
- 성질 — 고유 prefix가 적을수록 재사용률이 오르고, 서버가 실제로 계산해야 할 prefill 토큰이 줄어듭니다.
- 쓰임 — 대화형 서비스라면 ShareGPT로 사용자 경험을, Prefix Repetition으로 캐시 동작을 각각 잽니다. 둘은 보완재입니다.
트래픽이 준비됐습니다. 이제 무엇을 숫자로 남길지 미리 정해야 합니다.
04Step 3 — 네 개의 숫자만 본다
한 줄 요약: 총 처리량 · 출력 처리량 · TTFT · ITL. 비교는 이 넷으로만 합니다.
지표가 많으면 비교가 흐려집니다. 실험이 여덟 번인데 지표가 열두 개면, 어느 실험이 더 나은지 판단할 수 없습니다. 또 지표를 나중에 고르면 유리하게 나온 숫자만 고르게 됩니다.
그래서 실험을 시작하기 전에 네 개로 못 박습니다.
- TTFT — Time To First Token
- 요청을 보내고 첫 글자가 나올 때까지의 시간. 토크나이징·큐 대기·prefill이 전부 여기 들어갑니다. 사용자가 "반응이 빠르다"고 느끼는 지점.
- ITL — Inter-Token Latency
- 글자와 글자 사이의 시간. 스트리밍이 매끄러운지를 결정합니다.
- TPOT — Time Per Output Token
- 출력 토큰 하나당 평균 시간. ITL과 거의 같은 것을 다른 각도에서 본 값입니다. 이 실습에서는 생략합니다.
- p99
- 전체 요청을 느린 순으로 줄 세웠을 때 상위 1%에 해당하는 나쁜 값. "가장 운 나쁜 사용자"가 겪은 시간입니다.
비유 — 카페의 세 가지 시계
카페 사장은 세 개의 시계를 봅니다. 한 시간에 몇 잔을 팔았나(처리량), 주문하고 첫 잔이 나오기까지 몇 분인가(TTFT), 일행 4명 잔이 하나씩 나오는 간격(ITL)입니다.
세 시계가 서로를 밀어냅니다. 주문을 몰아서 한 번에 내리면 시간당 판매량은 오르지만 첫 잔은 늦어집니다. 그리고 평균만 보면 안 됩니다 — 100명 중 1명이 20분을 기다렸다면 그 사람은 다시 오지 않습니다. 그게 p99입니다.
작은 숫자로 한 번
출력이 길어지면 TTFT는 거의 의미가 없어지고 ITL이 전부가 됩니다. 반대로 짧은 답변이 대부분인 서비스라면 TTFT가 사용자 경험을 지배합니다.
요청 하나의 시간을 뜯어보기
↔ 슬라이더 2개다섯 요청의 흔들림은 고정 씨앗 난수로 만든 예시입니다. p99가 평균보다 크다는 성질을 보여주기 위한 모형입니다.
전체 지표 체계 — 이 중 넷만 씁니다
| 범주 | 주요 지표 | 이 실습에서 |
|---|---|---|
| 처리량 | 총 토큰 TPS, 출력 토큰 TPS, 요청/초 | 총 TPS · 출력 TPS 사용 |
| 지연시간 | TTFT, TPOT, ITL (평균 + P99) | 평균 TTFT · 평균 ITL 사용 |
| 자원 활용도 | GPU 사용률, 메모리 사용량 | 참고용으로만 |
| 워크로드 특성 | 입출력 비율, 동시성, 요청률 | 실험 조건으로 고정 |
| 신뢰성 / 비용 | 오류율, 비용 효율 | 생략 |
평균(mean)은 이상치에 흔들리고, 중앙값(median)은 보통 사용자의 경험을 보여주며, p99는 가장 나쁜 경험을 보여줍니다. SLO를 정할 때는 보통 p99를 씁니다.
- 정의 — 총 TPS(입력+출력), 출력 TPS, TTFT(첫 토큰까지), ITL(토큰 사이). 이 넷으로 모든 실험을 비교합니다.
- 성질 — TTFT는 prefill 효율을, ITL은 decode 효율을 대변합니다. 출력이 길수록 체감 시간은 ITL이 지배합니다.
- 쓰임 —
vllm bench serve --save-result가 이 값들을 자동으로 남깁니다. 지표를 미리 못 박아야 6장 이후의 비교가 성립합니다.
이제 서버를 띄웁니다. 그리고 로그 다섯 줄에서 이 장 전체의 병목을 발견하게 됩니다.
05Step 4 — 로그 다섯 줄이 병목을 알려준다
한 줄 요약: 46GB 중 27.5GB를 모델이 먹고, KV 캐시에는 11GB만 남습니다. 이 장 전체의 병목입니다.
봐야 합니다. KV 캐시에 남은 공간이 곧 동시에 처리할 수 있는 요청 수이기 때문입니다. 캐시가 좁으면 서버는 배치를 줄이거나 캐시를 버려야 하고, 버린 건 다시 계산해야 합니다.
즉 메모리 배분표가 곧 처리량 예보입니다. 벤치마크를 돌리기 전에 이미 결과의 절반이 정해져 있습니다.
- KV cache
- 이미 읽은 토큰의 계산 결과를 저장해 둔 것. 다음 토큰을 만들 때 앞 토큰들을 다시 계산하지 않으려고 들고 있습니다. 요청마다, 토큰마다 자리를 차지합니다.
- eviction (축출)
- 캐시가 꽉 차서 기존 항목을 버리는 것. 버린 걸 나중에 또 쓰게 되면 다시 계산해야 합니다.
- recomputation (재계산)
- 버렸던 KV를 다시 만드는 일. 순수한 낭비입니다.
- max concurrency
- vLLM이 로그에 찍어 주는 값. "최대 길이 요청 기준으로 몇 개를 동시에 붙잡을 수 있는가"입니다. 1.76x는 두 개도 못 든다는 뜻입니다.
- gpu-memory-utilization
- 전체 VRAM 중 vLLM이 쓰겠다고 잡는 비율. 기본값은 0.9입니다.
비유 — 이삿짐 트럭
46평 트럭에 냉장고(모델 가중치)를 실었더니 27.5평을 차지했습니다. 남은 18.5평에 작업 공간(KV 캐시)을 만들어야 하는데, 실제로 확보된 건 11평입니다.
작업 공간이 좁으면 짐을 한 번에 두 개씩만 옮길 수 있고, 잠깐 내려놨던 짐을 다시 들어 올려야 합니다. 냉장고를 작은 걸로 바꾸면(양자화) 작업 공간이 넓어지고, 그러면 한 번에 다섯 개씩 옮길 수 있게 됩니다. 이게 7장의 이야기입니다.
작은 숫자로 한 번
1.76x라는 숫자를 조금 더 밀어붙여 봅시다. 동시에 처리하고 싶은 요청이 10개인데 캐시가 두 개 분량뿐이면, 다음이 연쇄적으로 일어납니다.
VRAM 46GB를 무엇이 나눠 쓰는가
↔ 슬라이더 2개gpu-memory-utilization을 0.7까지 내리면 남는 VRAM이 그냥 놀게 됩니다. ③ 가중치를 40GB까지 밀면 KV가 0이 되고 서버가 아예 못 뜹니다.가중치를 18GB 줄이면 KV 토큰이 2.6배가 됩니다. 메모리는 선형으로 줄었는데 캐시는 훨씬 크게 늘어납니다 — 남는 자리를 전부 캐시가 가져가기 때문입니다.
모델을 작게 만들면 단순히 VRAM이 절약되는 게 아닙니다. 남는 VRAM이 전부 KV 캐시로 넘어가 동시성이 오르고, 배칭이 촘촘해지고, 처리량이 오릅니다. 이것이 7장(양자화)을 하는 이유입니다.
- 정의 — vLLM 기동 로그는 모델 가중치 / KV 캐시 / KV 토큰 수 / 최대 동시성을 순서대로 찍어 줍니다.
- 성질 — KV 캐시 용량이 곧 동시성 한도입니다. 좁으면 축출 → 재계산 → 처리량 저하로 이어집니다.
- 쓰임 — 27.5 + 11 = 38.5 / 46. 모델이 60%를 차지한 이 배분표가 "양자화를 해야 한다"는 결론의 근거입니다.
병목을 확인했으니, 이제 원점이 될 숫자를 뽑습니다.
06Step 5 — 베이스라인, 그리고 2.4배의 정체
한 줄 요약: 모델도 설정도 그대로인데 트래픽만 바꿨더니 처리량이 2.4배가 됐습니다.
됩니다. 그리고 이 사실이 이 장에서 가장 중요합니다. 처리량은 GPU가 얼마나 빠른가가 아니라, 같은 계산을 얼마나 안 반복하는가로도 결정됩니다.
GPU 사용률은 두 실험 모두 97%였습니다. GPU는 똑같이 열심히 일했는데, 한쪽은 안 해도 될 일을 하고 있었던 겁니다.
- baseline (베이스라인)
- 튜닝 전 원점. 이후 모든 실험은 이 값과 비교합니다.
- continuous batching
- 배치가 다 끝나기를 기다리지 않고, 먼저 끝난 자리에 새 요청을 바로 채워 넣는 배칭. vLLM이 자동으로 합니다.
- memory block sharing
- 같은 내용의 KV를 여러 요청이 한 벌만 두고 나눠 쓰는 것.
- max-concurrency
- 벤치마크 클라이언트가 동시에 열어 두는 요청 수. 서버 쪽 max concurrency와 이름이 비슷하지만 다른 값입니다.
비유 — 같은 자료를 열 번 복사하기
사무실 복사기 앞에 열 명이 줄을 섰습니다. 각자 다른 문서를 들고 있으면 열 번을 다 복사해야 합니다. 그런데 앞 20장이 전부 똑같은 표지라면? 표지는 한 번만 찍어 두고 열 명에게 나눠주면 됩니다.
복사기 속도는 그대로인데 총 처리량은 크게 오릅니다. 이게 prefix caching입니다.
작은 숫자로 한 번
두 트래픽의 네 지표를 나란히
↔ 버튼 + 슬라이더동시성 10일 때의 값만 실측(474 / 1,123 TPS)입니다. 다른 동시성 값은 곡선을 맞춘 모형입니다.
벤치마크 명령과 원문 출력
vllm bench serve \
--backend vllm --base-url "http://localhost:8000" \
--model Qwen/Qwen3-14B \
--dataset-name sharegpt \
--dataset-path ShareGPT_V3_unfiltered_cleaned_split.json \
--num-prompts 2000 --request-rate 10 --burstiness 1.0 \
--max-concurrency 10 \
--save-result --append-result --result-filename test_serve_results.txt
============ Serving Benchmark Result ============ Successful requests: 2000 Maximum request concurrency: 10 Benchmark duration (s): 1810.09 Total input tokens: 446619 Total generated tokens: 412052 Output Throughput : 227.64 tok/s Total Throughput : 474.38 tok/s Mean TTFT : 104.15 ms Mean ITL : 43.24 ms P99 ITL : 72.15 ms
이 다섯 줄이 모든 후속 실험의 원점입니다. 앞으로 어떤 옵션을 켜든 이 숫자와 비교합니다.
같은 계산을 반복하지 않는 것이 가장 좋은 최적화 중 하나입니다. 실제 트래픽에 시스템 프롬프트 공유나 반복 질문이 많다면, 하드웨어를 한 대도 늘리지 않고 처리량을 크게 올릴 수 있습니다.
- 정의 — 베이스라인은 474 TPS · TTFT 104ms · ITL 43ms(ShareGPT, 동시성 10)입니다.
- 성질 — 같은 설정에서 Prefix Repetition으로 바꾸면 1,123 TPS(2.37배). 지연시간은 거의 그대로입니다.
- 쓰임 — 처리량 상승의 원인은 연산 가속이 아니라 prefix caching · continuous batching · memory block sharing에 의한 재계산 회피입니다.
트래픽을 바꾸는 건 우리 손에 없을 때가 많습니다. 이번엔 모델 쪽을 건드려 봅니다.
07Step 6 — 양자화, 그리고 2.7배
한 줄 요약: 가중치를 4비트로 줄이면 남는 17GB가 전부 KV 캐시로 갑니다. 처리량 2.7배, TTFT 42% 개선.
속도가 빨라진 게 아니라 자리가 남은 것이기 때문입니다. 가중치가 27.5GB에서 9.6GB로 줄면 그 차이 17GB가 통째로 KV 캐시로 넘어가고, 동시성이 1.76x에서 4.66x로 뜁니다.
"작게 만든다 → 많이 담는다 → 촘촘히 묶는다 → 처리량이 오른다"가 정확한 순서입니다. 여기 "계산이 빨라진다"는 단계는 없습니다.
- quantization (양자화)
- 숫자를 더 적은 비트로 표현하는 것. bf16(16비트)을 4비트로 줄이면 저장 공간이 약 4분의 1이 됩니다.
- AWQ — Activation-aware Weight Quantization
- 가중치를 양자화하되, 중요한 가중치는 덜 뭉개는 방식. 이름 그대로 활성값(activation)을 보고 중요도를 판단합니다.
- weight quantization vs activation quantization
- 앞의 것은 저장·이동량을 줄이고, 뒤의 것은 연산량 자체를 줄입니다. AWQ는 앞쪽입니다.
- Marlin kernel
- 4비트 가중치를 되풀어서(dequantize) 곱하는 전용 커널. 8장에서 다시 나옵니다.
비유 — 사전을 문고판으로 바꾸기
책상 위에 두꺼운 사전이 놓여 있습니다. 문고판으로 바꾸면 찾는 속도가 빨라지는 게 아니라 책상이 넓어집니다. 넓어진 책상에 작업 중인 서류를 더 많이 펼칠 수 있고, 그 결과 하루에 처리하는 일이 늘어납니다.
사전을 줄여서 글자가 조금 흐려진 건 사실입니다(정확도 손실). 그 대가로 책상을 얻은 겁니다.
작은 숫자로 한 번
주목할 점은 처리량과 지연시간이 동시에 좋아졌다는 것입니다. 1장에서 둘은 자주 상충한다고 했지만, "낭비를 없애는" 종류의 최적화는 둘을 한꺼번에 개선합니다.
비트를 줄이면 어디까지 따라 움직이는가
↔ 슬라이더16비트와 4비트만 실측입니다(27.5GB/474TPS, 9.6GB/1,280TPS). 가운데 8비트는 두 점 사이를 이은 추정입니다.
메모리 효율은 크게 좋아지지만 정확도 손실, 토큰 불안정성, 출력 품질 변화가 생길 수 있습니다. 4비트·8비트·FP8 중 무엇을 쓸지는 "성능 이득 대비 품질 저하를 얼마나 감내할 수 있는가"로 결정해야 합니다. 이 실습은 품질 평가를 하지 않았습니다 — 그건 책 6장(양자화 장)의 몫입니다.
인과의 사슬 — 이 순서를 외우면 됩니다
Quantization ↓ 모델 메모리 감소 (27.5 → 9.6 GB) ↓ KV Cache 증가 (72,064 → 191,056 tokens) ↓ 동시 Request 증가 (1.76x → 4.66x) ↓ Batch 효율 증가 ↓ Throughput 증가 (474 → 1,280 TPS)
이 사슬 어디에도 "행렬곱이 빨라진다"는 항목이 없습니다. AWQ는 가중치 양자화라서 데이터 이동량과 메모리 사용량을 줄이는 데 초점이 있습니다. 연산량 자체를 줄이려면 활성화(activation) 양자화가 필요하고, 그건 책 6장 소관입니다. 8장에서 이 주장을 프로파일러로 직접 검증합니다.
- 정의 — AWQ는 활성값을 고려한 4비트 가중치 양자화. 가중치를 27.5GB에서 9.6GB로 줄입니다.
- 성질 — 확보된 17GB가 KV 캐시로 넘어가 토큰 용량 2.65배, 동시성 2.65배, 처리량 2.7배, TTFT 42% 개선.
- 쓰임 — 하드웨어를 늘리지 않고 처리량과 지연시간을 동시에 개선하는 가장 가성비 높은 한 수. 단, 품질 저하 가능성은 별도 검증 필요.
여기까지가 "누구에게나 통하는" 최적화입니다. 이제부터는 내 트래픽에 맞는 선택입니다.
08Step 7 — 워크로드에 맞춰 고르기, 그리고 프로파일러가 밝힌 진짜 이유
한 줄 요약: 기법은 병목을 보고 고릅니다. 그리고 2배의 원인은 GEMM이 아니라 동기화 대기였습니다.
옵션 값을 찾는 게 아니라 기법을 고르는 문제이기 때문입니다. 입력이 길고 공유 컨텍스트가 많으면 캐싱 계열을, 출력이 길면 speculative decoding 계열을 씁니다. 무엇이 병목인지 모른 채 노브를 돌리면 특정 GPU에만 맞는 설정을 만들어 놓고 끝납니다.
그리고 이 장 후반에서, 지금까지 우리가 믿어 온 인과 사슬을 프로파일러로 검증합니다.
- chunked prefill
- 긴 입력의 prefill을 여러 조각으로 나눠 처리하는 것. 긴 요청 하나가 다른 요청들을 막지 않게 합니다.
- speculative decoding
- 작은 초안 모델로 여러 토큰을 미리 찍어보고 큰 모델이 한꺼번에 검증하는 방식. decode 위주 워크로드에 효과적입니다.
- LMCache
- 계산해 둔 KV를 GPU 밖(CPU·디스크)까지 확장해 재사용하는 캐시. 멀티턴 채팅이나 RAG처럼 공유 컨텍스트가 많을 때 씁니다.
- block-size
- KV 캐시를 관리하는 블록 단위. 작을수록 자투리 낭비가 줄어 캐시 활용도가 오릅니다.
- Nsight Systems / Nsight Compute
- NVIDIA 프로파일러. 앞의 것은 전체 타임라인, 뒤의 것은 커널 하나의 속사정을 봅니다.
- occupancy (점유율)
- GPU의 연산 유닛이 얼마나 채워져 돌고 있는지. 낮다고 무조건 나쁜 건 아닙니다.
- cudaEventSynchronize
- 호스트(CPU)가 GPU 작업이 끝나기를 기다리는 호출. 여기 쌓인 시간이 곧 "기다린 시간"입니다.
비유 — 주방의 병목은 매번 다른 곳에 있다
주문이 밀리는 식당을 개선한다고 해봅시다. 손질(prefill)에서 밀리는 집과 굽기(decode)에서 밀리는 집은 처방이 완전히 다릅니다. 손질에서 밀리면 미리 손질해 둔 재료를 재사용하면 되고(LMCache), 굽기에서 밀리면 보조 요리사가 미리 초벌을 해두는 편이 낫습니다(speculative decoding).
어디서 밀리는지 모르는 채 칼을 새로 사면 아무 일도 일어나지 않습니다.
작은 숫자로 한 번
vLLM의 주요 노브는 크게 두 갈래입니다.
특정 GPU와 특정 트래픽에 완벽히 맞춘 설정은 다른 하드웨어에서 성능이 나쁘거나 아예 실패합니다. 최신 서빙 프레임워크는 기동 시 하드웨어를 보고 합리적인 기본값을 스스로 추론합니다. 실무의 노력은 "완벽한 파라미터 값 찾기"가 아니라 "어떤 기법을 적용할지 판단하기"에 써야 합니다.
그래서, 2.7배는 정말 어디서 왔나
여기서 이야기가 한 번 더 꺾입니다. RTX 4070 Ti SUPER(16GB) 환경에서 Qwen3-4B를 대상으로 같은 실험을 재현하고, Nsight Systems로 GPU 안쪽을 열어봤습니다. 결과는 반직관적이었습니다.
총 커널 시간은 같은데 체감 속도는 2.2배입니다. 그리고 호출 1회당 동기화 대기 시간의 단축(2.45배)이 클라이언트가 측정한 TPOT 개선(2.19배)과 거의 정확히 일치합니다.
즉 "AWQ 행렬곱이 bf16보다 계산량이 적어서 빨라졌다"가 아니라, KV 캐시 여유가 생겨 디코딩 스케줄링이 촘촘해졌고, 한 스텝의 호스트–GPU 왕복 대기가 짧아졌다는 것이 실제 메커니즘입니다. 5장에서 세운 인과 사슬이 커널 레벨에서 그대로 확인된 셈입니다.
계산한 시간 vs 기다린 시간
↔ 버튼 + 슬라이더AWQ는 커널 호출 횟수가 더 많습니다(79,165 → 113,169회). 그런데도 총 시간은 짧습니다 — 한 스텝의 턴어라운드가 짧으면 처리량은 오릅니다.
커널 구성과 Nsight Compute 결과
| 커널 유형 | base (bf16) | AWQ (4bit) |
|---|---|---|
| GEMM (bf16/f16, cutlass) | ~75% | 35.5% — 커널 하나, 2,024회. 양자화되지 않은 레이어(lm_head 등)로 추정 |
| Marlin GEMM (4bit 역양자화+행렬곱) | 0% | ~35.6% — 12,240회. 훨씬 잦고 짧은 커널 |
| FlashAttention | 15.3% | 15.7% — 거의 동일(어텐션은 양자화 대상이 아님) |
AWQ에서도 약 35%가 여전히 bf16 GEMM으로 남습니다. attention projection이나 lm_head 같은 일부 레이어는 원본 정밀도로 남는 경우가 많기 때문입니다. 책 8장(FP8 Qwen2.5-7B) 실습에서 본 것과 같은 패턴입니다.
Nsight Compute 커널 단위 분석에서는 GEMM 커널의 SM 점유율이 bf16과 4bit(Marlin)에서 사실상 동일했습니다(~16.7%). 완전히 다른 구현인데 점유율이 같다는 건 커널 하나하나의 효율은 양자화로 달라지지 않았다는 뜻입니다.
KV 캐시 쓰기 커널도 base/AWQ가 거의 동일했습니다(7.30μs vs 7.85μs, occupancy 65.9% vs 65.8%). AWQ는 가중치만 4비트로 바꾸고 KV 캐시 자체는 그대로이므로 당연한 결과지만, "양자화가 어텐션·캐시 경로에는 영향을 주지 않는다"를 프로파일러로 직접 확인한 셈입니다.
(참고) RTX 4070 16GB 환경 재현 결과
| 지표 | ShareGPT (base) | ShareGPT (AWQ) | Prefix Rep. (base) | Prefix Rep. (AWQ) |
|---|---|---|---|---|
| 모델 가중치 | 7.56 GiB | 2.5 GiB | 동일 | 동일 |
| KV 캐시 / 토큰 | 4.74 GiB / 34,512 | 10.45 GiB / 76,064 | 동일 | 동일 |
| 최대 동시성 | 4.21x | 9.29x | 동일 | 동일 |
| 총 처리량 (TPS) | 1,178 | 2,107 (+79%) | 2,733 | 3,140 (+13%) |
| 평균 TTFT (ms) | 66.2 | 43.5 | 87.2 | 49.0 |
| 평균 ITL (ms) | 16.0 | 7.5 | 17.0 | 7.2 |
책의 논리가 소비자용 GPU에서도 그대로 재현됩니다. 다만 AWQ 상태에서는 캐싱의 추가 이득이 작아집니다(+13%). 이미 GPU가 포화에 가까워졌기 때문으로 해석됩니다.
- 정의 — 노브는 메모리/캐시 계열과 배칭/스케줄링 계열 두 갈래. 하지만 먼저 고를 것은 값이 아니라 기법입니다.
- 성질 — prefill 위주면 LMCache 계열, decode 위주면 speculative decoding 계열. 특정 환경에 과적합된 설정은 이식성을 잃습니다.
- 쓰임 — 프로파일러로 확인한 결과, 처리량 개선의 실제 원인은 GEMM 가속이 아니라 동기화 대기 시간 단축이었습니다. 인과 사슬을 커널 레벨에서 검증한 것입니다.
한 장으로 할 수 있는 건 다 했습니다. 이제 GPU를 더 붙여봅니다 — 그리고 결론이 뒤집힙니다.
09Step 8 — GPU를 더 붙이면 빨라지는가
한 줄 요약: GPU 개수가 아니라 GPU 사이를 잇는 선이 결정합니다. PCIe만 있으면 한 장이 네 장을 이깁니다.
같은 모델·같은 설정으로 두 종류의 서버에서 실험했더니 정반대 결과가 나왔습니다. L40S 4장이 달린 서버에서는 1장이 가장 빨랐고, A100 8장이 달린 서버에서는 4장이 가장 빨랐습니다.
단일 GPU 추론에서는 L40S가 A100보다 효율이 좋은데도 그렇습니다. 이 역전의 이유가 이 장의 핵심입니다.
- tensor parallelism (TP, 텐서 병렬)
- 모델 한 벌을 여러 GPU에 쪼개서 올리고 함께 계산하는 방식. 매 레이어마다 GPU끼리 결과를 주고받아야 합니다.
- interconnect (인터커넥트)
- GPU와 GPU를 잇는 통신 경로. NVLink는 넓고 빠르고, PCIe는 상대적으로 좁고 느립니다.
- vertical scaling (수직 확장)
- GPU 여러 장이 모델 하나를 나눠 맡는 것. TP가 여기 속합니다.
- horizontal scaling (수평 확장)
- GPU마다 독립된 모델 복제본을 하나씩 올리는 것. 앞에 라우터를 두고 나눠 보냅니다.
- replica (레플리카)
- 독립적으로 돌아가는 모델 인스턴스 한 벌.
비유 — 한 문서를 넷이 나눠 쓰기
보고서 한 편을 네 사람이 나눠 씁니다. 넷이 같은 방에 앉아 있으면 한 문단 쓸 때마다 옆 사람에게 말로 확인하면 되니 빠릅니다(NVLink). 그런데 넷이 서로 다른 층에 앉아 있고 메일로만 소통한다면? 한 문단마다 메일을 주고받느라 혼자 쓰는 것보다 느려집니다(PCIe).
텐서 병렬은 매 레이어마다 GPU끼리 결과를 동기화합니다. 그 통신 비용이 병렬화 이득보다 크면 GPU를 늘릴수록 손해입니다.
작은 숫자로 한 번
NVLink가 있는 서버에서조차, 처리량만 노린다면 텐서 병렬보다 단순 복제가 3배 유리합니다. 그렇다면 분산 서빙은 왜 하는 걸까요?
딱 두 가지입니다. (1) 모델이 GPU 한 장에 안 들어갈 때, (2) 요청 하나의 지연시간 자체를 줄여야 할 때. p4d에서 1→4 GPU로 TTFT가 66ms에서 33ms로 준 것은 레플리카를 아무리 늘려도 얻을 수 없는 텐서 병렬만의 이득입니다. 복제본을 더 놓는 것은 요청 하나가 빨라지게 만들지 못합니다.
GPU 수 × 인터커넥트 — 결론이 뒤집히는 지점
↔ 슬라이더 + 버튼실측은 p4d TP=4의 3,926 TPS · 독립 4개의 9,816 TPS · TTFT 66→33ms와 "g6e는 1장이 최고"라는 방향뿐입니다. 나머지 중간값은 그 사이를 이은 모형입니다.
수직 확장 vs 수평 확장 — 언제 무엇을
수직 확장(Vertical / TP) — 여러 GPU가 모델 인스턴스 하나를 처리합니다.
장점: 한 장에 안 들어가는 큰 모델을 올릴 수 있고, 요청당 지연시간을 줄일 수 있습니다.
단점: GPU 간 통신 오버헤드, 구성 복잡도 증가.
수평 확장(Horizontal / Replica) — GPU마다 독립 복제본을 둡니다.
장점: 총 처리량이 크고, 한 대가 죽어도 나머지가 삽니다(장애 격리), 구성이 단순합니다.
단점: 요청 하나의 지연시간은 GPU를 늘려도 줄지 않습니다.
# 2장으로 모델을 나눠 올리기 vllm serve Qwen/Qwen3-14B-AWQ --tensor-parallel-size 2 > vllm.log 2>&1 & # --tensor-parallel-size 2 하나로 vLLM이 분산 그룹 초기화와 모델 분할을 알아서 합니다.
실무 판단 순서는 이렇습니다. (1) 인터커넥트가 NVLink급인가 PCIe뿐인가 → (2) 목표가 처리량인가 지연시간·모델 크기인가. 이 두 질문에 답하기 전에는 "GPU를 늘릴까"를 물을 수 없습니다.
- 정의 — 텐서 병렬은 모델 하나를 여러 GPU에 쪼개는 수직 확장, 레플리카는 GPU마다 독립 인스턴스를 두는 수평 확장입니다.
- 성질 — TP는 매 레이어마다 GPU 간 동기화가 필요합니다. NVLink면 이득이 통신비를 넘고, PCIe뿐이면 넘지 못합니다.
- 쓰임 — 처리량이 목표면 수평 확장(최대 3배 유리), 모델이 안 들어가거나 TTFT를 줄여야 하면 수직 확장. 이 판단이 먼저입니다.
여덟 단계가 끝났습니다. 남은 건 이 여정에서 계속 마주쳤던 선택의 갈림길들입니다.
10다섯 가지 트레이드오프 — 정답은 없습니다
한 줄 요약: "완벽한 설정"을 찾는 게 아니라 "어떤 기법을 쓸지" 정하는 게 최적화입니다.
이 질문에는 답이 없습니다. 그리고 답이 없다는 사실 자체가 답입니다. 앞선 아홉 장에서 우리는 같은 설정이 상황에 따라 이기기도 지기도 하는 장면을 반복해서 봤습니다.
그래서 마지막으로 할 일은 설정을 외우는 게 아니라, 내 제약 조건을 먼저 파악하는 습관을 갖는 것입니다.
- overfitting (과적합)
- 원래 머신러닝 용어지만 여기서는 설정이 특정 하드웨어·트래픽에만 맞아떨어지는 상태를 가리킵니다.
- static optimization (정적 최적화)
- 파라미터를 고정해 두고 쓰는 방식. 예측 가능하지만 상황 변화에 못 따라갑니다.
- adaptive serving (적응형 서빙)
- 실시간 트래픽·하드웨어 지표를 보고 배치 크기·캐시 정책·스케줄링을 스스로 조정하는 방식. 서빙 프레임워크의 다음 단계입니다.
- SLO
- 서비스 수준 목표. "p99 TTFT 300ms 이하" 같은 지켜야 할 선입니다.
비유 — 자전거 기어
자전거에 "가장 좋은 기어"는 없습니다. 오르막에서는 가벼운 기어가, 평지에서는 무거운 기어가 맞습니다. 초보자가 하는 실수는 평지에서 잘 나가던 기어를 오르막에서도 고집하는 것입니다.
서빙 설정도 같습니다. 그리고 "지형"에 해당하는 것이 워크로드 성격, 하드웨어 인터커넥트, 품질 허용 범위입니다.
다섯 가지 갈림길
| 트레이드오프 | 이 페이지에서 직접 확인한 장면 |
|---|---|
| 처리량 ↔ 지연시간 | 1장 그림에서 동시성을 올릴수록 처리량 이득은 줄고 지연 손해는 늘었습니다. 인터랙티브 서비스라면 TTFT·ITL도 같이 챙겨야 합니다. |
| 메모리 효율 ↔ 모델 품질 | 7장 AWQ 4비트로 메모리를 65% 절감하고 처리량 2.7배. 단, 정확도 손실 가능성은 이 실습에서 측정하지 않았습니다. |
| 하드웨어 활용도 ↔ 유연성 | 8장의 "과최적화 금지" 경고. 특정 GPU·트래픽에 딱 맞춘 설정은 다른 곳에서 실패합니다. |
| 수직 확장 ↔ 수평 확장 | 9장에서 PCIe 서버는 분산이 손해였고, NVLink 서버에서도 독립 레플리카 4개가 TP=4보다 처리량 3배였습니다. |
| 정적 최적화 ↔ 적응형 서빙 | 이 페이지의 실습 전체가 고정 파라미터로 돌린 정적 최적화였습니다. 프레임워크는 자동 튜닝 쪽으로 가고 있습니다. |
내 상황이면 수직인가 수평인가
↔ 슬라이더 2개 + 버튼같은 하드웨어인데 목표를 바꾸면 답이 바뀝니다. 그래서 "무엇을 올릴 것인가"(1장)를 먼저 정해야 합니다.
- 정의 — 다섯 갈림길: 처리량↔지연시간, 메모리↔품질, 활용도↔유연성, 수직↔수평, 정적↔적응형.
- 성질 — 어느 것도 한쪽이 항상 옳지 않습니다. 워크로드 성격·하드웨어 인터커넥트·품질 허용 범위가 답을 바꿉니다.
- 쓰임 — 실무의 노력 대부분은 파라미터 값이 아니라 어떤 기법을 적용할지 결정하는 데 씁니다.
마치며
처음 던진 질문으로 돌아갑니다. 처리량이 올랐다는 건 GPU가 더 빨리 계산해서인가, 그냥 덜 다시 계산해서인가?
답은 후자입니다. 그것도 세 번 확인했습니다. 트래픽만 바꿨는데 2.4배(6장), 양자화했더니 2.7배(7장), 그리고 프로파일러로 열어보니 GPU 커널 실행 시간은 그대로였습니다(8장). 세 실험 모두 같은 함정을 반박합니다 — "빨라졌다 = 연산이 빨라졌다"는 직관은 여기서 통하지 않습니다.
실제로 작동한 경로는 이것이었습니다. 메모리 여유 → KV 캐시 확보 → 배칭 촘촘해짐 → 스케줄링 효율화 → 스텝당 대기 감소. 이 사슬을 손에 쥐고 있으면, 새로운 옵션을 만났을 때 "이건 어느 고리를 건드리는 기법인가"로 판단할 수 있습니다.
두 번째 질문 — GPU를 더 붙이면 무조건 더 빠른가? 아니었습니다. 같은 모델이 PCIe 서버에서는 한 장일 때 가장 빨랐고, NVLink 서버에서는 네 장일 때 가장 빨랐습니다. 그리고 처리량만 본다면 어느 쪽에서도 단순 복제가 분산 서빙보다 낫습니다.
세 번째 질문 — 여기서 찾은 최적 설정이 다른 환경에서도 최적인가? 저자들 스스로 아니라고 말합니다. 그러니 이 페이지에서 가져갈 것은 474나 1,280이 아니라, 그 숫자를 만들고 해석한 방법입니다.
이 페이지는 CH9: LLM Optimization in Practice의 스터디 정리본입니다. 실습 코드와 각 단계의 실행 결과는 orca3/llm-model-inference ch09에 있습니다. 모델은 Qwen3-14B와 Qwen3-14B-AWQ, 부하 도구는 vllm bench serve입니다.
수치 해석이 틀렸거나, 여러분의 환경에서 다른 결과가 나왔다면 알려주세요.
'AI > [LLMSO] Hands-On LLM 서빙과 최적화 스터디' 카테고리의 다른 글
| [6주차-LLMSO] Scaling LLM Inference with vLLM and AWS Trainium (2026.09.06) (1) | 2026.09.13 |
|---|---|
| [5주차-LLMSO]CH10. Advancements in LLM Serving (2026.08.30) (1) | 2026.09.05 |
| [4주차-LLMSO]CH8. LLM Serving Frameworks (2026.08.23) (0) | 2026.08.30 |
| [4주차-LLMSO]CH7. Advanced LLM Optimization Techniques (2026.08.23) (0) | 2026.08.30 |
| [3주차-LLMSO]CH6. Essential LLM Optimization Techniques (2026.08.16) (0) | 2026.08.21 |