Gemma 4 E2B on a Single TPU v6e Chip: A Serving Deep Dive
What it took to deploy, why the QAT checkpoints refuse to load, and what one flex-start v6e chip is actually worth — measured live.
개요 #
20억 파라미터짜리 Gemma 4 E2B를 TPU v6e 칩 딱 한 장에 올리면 어디까지 나올까. 이 글은 그 질문에 실측으로 답한다. 결론부터 말하면 기본 체크포인트 google/gemma-4-E2B-it는 무난하게 서빙된다. 단일 사용자 기준 초당 213토큰, 첫 토큰까지 16ms, 동시 스트림을 늘리면 출력 처리량이 초당 2,200토큰 근처까지 올라간다. 함수 호출과 간단한 비전 질문도 문제없이 처리했다.
문제는 양자화 버전(QAT)이다. int4 압축 텐서 버전은 아직 구현되지 않은 양자화 경로에 걸리고, bf16 QAT 버전은 로더 버그에 막혀 아예 뜨지 않았다. 측정은 2026년 7월 21일 vllm/vllm-tpu:nightly(vLLM 0.23.1rc1.dev1076), europe-west4-a 리전의 flex-start ct6e-standard-1t(TPU v6e 1칩, HBM 32GB) 환경에서 이뤄졌다.
서빙 엔드포인트 띄우기 #
호스트는 GCE flex-start VM이다. 요청하면 용량을 받아 쓰고, 삭제할 때까지 과금되며, 최대 4시간이 지나면 강제로 멈춘다. 요금은 칩당 시간당 1.35달러다. 시작 스크립트가 Docker를 설치하고 vllm/vllm-tpu:nightly 이미지를 받은 뒤, 메타데이터 서버를 거쳐 Secret Manager에서 Hugging Face 토큰을 가져와 vLLM을 띄운다.
부팅 타임라인은 이렇다. VM RUNNING이 t+0(부트 디스크는 200GB로 잡았다. 기본 10GB로는 vLLM 이미지가 안 들어간다) → Docker 설치가 약 t+1:00 → 이미지 풀이 약 t+6:00 → 가중치 다운로드와 XLA 컴파일까지 끝나고 헬스체크가 초록불이 되는 시점이 약 t+8:30이다.
환경에서 미리 알아둘 함정이 둘 있다.
- 직접 SSH는 조용히 타임아웃난다. VPC가 tcp:22를 허용해도 마찬가지인데, 차단 지점이 VPC 위쪽이기 때문이다. IAP 터널링(
gcloud compute ssh --tunnel-through-iap)은 HTTPS 위로 타고 넘어가 정상 동작한다. API 포트를gcloud compute start-iap-tunnel <vm> 8000으로 터널링하는 방법도 된다. - vLLM이 v6e에서 fp8_e5m2 KV 캐시를 자동으로 고른다. 가중치 양자화를 논하기도 전에, 메모리를 가장 많이 먹는 KV 캐시부터 이미 8비트라는 뜻이다.
서빙 플래그는 --max-model-len 65536 --gpu-memory-utilization 0.9 --max_num_batched_tokens 4096 --enable-auto-tool-choice --tool-call-parser gemma4 --reasoning-parser gemma4, 가중치는 bf16, 텐서 병렬은 1이다.
QAT 체크포인트가 로드에 실패하는 세 갈래 #
| 체크포인트 / 경로 | 실패 원인 | 결과 |
|---|---|---|
-qat-w4a16-ct · JAX | E2B의 per_layer_model_projection에 int4 압축 텐서 방식 미구현 | ✕ 로드 실패 |
-qat-q4_0-unquantized · JAX | 15~34번 레이어의 k_norm.weight "누락" | ✕ 로드 실패 |
-qat-q4_0-unquantized · torchax | MODEL_IMPL_TYPE=vllm 경로에서도 같은 누락 오류 | ✕ 로드 실패 |
gemma-4-E2B-it (기본) · JAX | 로드되어 정상 서빙 | ✓ 서빙 |
문제의 뿌리는 체크포인트가 아니라 로더에 있다. 두 저장소의 safetensors 헤더를 뜯어보면, 기본 버전은 35개 레이어 전부에 self_attn.k_norm을 싣고 있는 반면 QAT 버전은 KV를 공유하지 않는 15개 레이어에만 싣는다. 두 설정 파일은 num_kv_shared_layers: 20을 포함해 완전히 동일하다. 15~34번 레이어는 아래층에서 K/V를 재사용하므로 자기 몫의 k-norm이 없다. 그러니 구조적으로 정직한 쪽은 오히려 QAT 버전이고, 기본 체크포인트는 쓰지도 않는 텐서를 그냥 들고 있어서 로드에 성공하는 것뿐이다. 저자는 KV 공유 레이어에서는 K/V 쪽 파라미터를 아예 만들지 않도록 하자는 수정안을 tpu-inference #3225에 올렸다.
패치가 들어오기 전까지는 기본 체크포인트를 쓰면 된다. 어차피 2B 파라미터(bf16 기준 약 5GB, HBM 32GB 대비)에서는 4비트 가중치가 얻는 게 별로 없다. 메모리 압박은 KV 캐시에 몰려 있는데, 그건 이미 fp8이기 때문이다.
칩 한 장의 값어치: 동시성 스윕 #
모든 구간에서 워크로드는 동일하게 잡았다. 1,024토큰 프롬프트, 128토큰 완성, vllm bench serve, 랜덤 데이터셋이다.
| 동시성 | Req/s | 출력 tok/s | 총 tok/s | TTFT 중앙 | TTFT p99 | TPOT 중앙 | 스트림당 tok/s |
|---|---|---|---|---|---|---|---|
| 1 | 1.64 | 209 | 1,884 | 16 ms | 17 ms | 4.7 ms | 213 |
| 8 | 9.44 | 1,209 | 10,878 | 27 ms | 99 ms | 6.2 ms | 161 |
| 32 | 12.78 | 1,636 | 14,721 | 155 ms | 189 ms | 17.5 ms | 57 |
| 64 | 16.72 | 2,140 | 19,262 | 122 ms | 349 ms | 25.3 ms | 39 |
| 100 (버스트) | 17.31 | 2,215 | 19,938 | 833 ms | 1,573 ms | 36.8 ms | 27 |
곡선을 읽어보면 이렇다.
- 저부하에서 프리필은 사실상 공짜다. 1,024토큰 프롬프트가 16ms 만에 첫 토큰을 낸다. 단일 스트림 기준 프리필이 초당 6.4만 토큰쯤 되는 속도다.
- 동시성 8은 거의 공짜로 얻는 병렬성이다. 토큰당 +1.5ms만 더 쓰고 단일 스트림의 6배 처리량을 뽑는다. 8명 각자가 여전히 초당 160토큰 안팎을 본다.
- 변곡점은 32와 64 사이다. 32→64로 가면 토큰당 지연이 45% 늘어나는 대가로 처리량이 31% 오른다. 64→버스트는 또 45%의 지연을 내주고 겨우 3.5%를 더 얻는다.
- 용량 계획용 숫자: 사용자 경험을 매끄럽게 유지하려면 동시 스트림 64개 이하로 돌려라. 이 워크로드 형태에서 상한은 초당 약 17요청이다.
(구성별로 한 번씩만 측정했다. 여기서는 그게 평소보다 더 믿을 만한데, 같은 스택 위에서 진행된 커널 연구(§7 참조)가 정적 shape·greedy 디코딩 조건에서 실행 간 변동계수를 0.3% 이하로 측정했기 때문이다.)
2B 규모의 함수 호출 #
--tool-call-parser gemma4 --enable-auto-tool-choice로 서빙하고, OpenAI 스타일 도구 두 개를 temperature 0에서 찔러봤다. 다섯 시나리오 전부 깔끔하게 통과했다.
| 시나리오 | 동작 | 지연 |
|---|---|---|
| 단순 호출 | 올바른 도구 선택, 문장 뉘앙스에서 선택 인자 unit을 추론 | 166 ms |
| 결과 종합 | 도구 결과를 되먹여 자연어 답변으로 정리 | 140 ms |
| 호출 자제 | 불필요한 호출 없이 바로 답변 | 97 ms |
| 병렬 호출 | 한 턴에 두 호출을 함께 내보내고 각 인자도 정확 | 150 ms |
| 정보 부족 | 호출을 지어내는 대신 "어느 도시가 궁금하세요?"라고 되물음 | 44 ms |
2B 모델이 형식에 맞는 tool_calls JSON을 만들어내고, 호출과 답변 사이에서 올바르게 선택하며, 병렬 호출을 묶고, 빠진 인자를 되묻는다. 그것도 두 자릿수 밀리초 지연으로. 고빈도·저복잡도 에이전트 단계에서라면 품질 하한선이 파라미터 숫자에서 짐작하는 것보다 높다.
구조화 출력은 되지만, thinking을 켜야만 걸린다 #
| 시나리오 | 관측된 동작 | 결과 |
|---|---|---|
json_schema, thinking off (기본) | 200 상태로 자유 산문 반환. strict: true, guided_json, structured_outputs 어떤 표기도 강제되지 않음 | ✕ 조용히 건너뜀 |
json_object, thinking off | 코드 펜스 블록, 객체를 요청했는데 배열, 없는 enum 값 발명 | ± 프롬프트 수준 |
json_schema + enable_thinking | 스키마 정확 준수 — 순수 JSON 객체, 정수 타입, "ASAP"을 high enum으로 정확히 매핑 | ✓ 강제됨 |
| 추론, 기본값 | 어떤 프롬프트에서도 추론 흔적 없음 | 기본은 off |
추론, enable_thinking | 파서가 사고 흔적과 짧은 답변을 깔끔하게 분리, 완성 토큰 약 2.4배 | ✓ 동작 |
원리는 이렇다. --reasoning-parser gemma4가 설정되면 vLLM은 추론 구간이 끝날 때까지 문법 강제를 미룬다. thinking이 꺼져 있으면 추론 종료 지점이 없으니 문법이 영영 걸리지 않고, 제약 없는 산문이 200 상태로 나가버린다. thinking을 켜면("chat_template_kwargs": {"enable_thinking": true}) 같은 요청에 정확히 스키마가 강제된다.
운영 지침: 구조화 출력은 reasoning 파서 아래에서 enable_thinking: true와 짝지어 쓰거나, 필요 없는 서버라면 --reasoning-parser를 아예 빼라. 그리고 어느 쪽이든 클라이언트 검증은 유지하라. 조용히 건너뛰는 실패 모드 탓에, 믿기만 하는 클라이언트는 강제된 응답과 운 좋게 맞은 응답을 구분하지 못한다.
2B의 비전: 정확하고 거의 공짜 #
--limit-mm-per-prompt '{"image":4,"audio":1}'로 한 번 재시작하면 비전 서버가 된다. COCO 검증 이미지를 base64 데이터 URI로, temperature 0에서 넣어봤다.
| 시나리오 | 답변 (요약) | 지연 |
|---|---|---|
| 묘사 (고양이 두 마리) | "밝은 분홍 표면 위 태비 고양이 두 마리… 리모컨이 보임" | 197 ms |
| 개수 + 속성 | 동물 2마리, 둘 다 고양이, 리모컨·담요 식별 | 421 ms |
| 장면 (곰) | "풀밭 야외에 엎드려 있는 곰" | 329 ms |
| 방 목록화 | 벽걸이 TV, 선반, 가구를 정확히 열거 | 874 ms |
이미지 하나가 프롬프트 토큰 약 280개를 쓰고, 텍스트 요청 대비 추가 비용이 거의 없다. 두 가지 유의점. 외부 이미지 URL을 서버가 대신 가져오는 방식은 불안정했다(간헐적으로 422). base64 데이터 URI가 안정적인 경로다. 그리고 부팅 직후 첫 멀티모달 요청은 프로세서가 예열되는 동안 422가 날 수 있으니 한 번 재시도하면 된다.
fp8 KV 캐시, HBM 해부, 그리고 관련 커널 결과 #
fp8 vs bf16 KV: greedy 프롬프트 6개(설명, 코드, 목록, 번역, 산술, 요약 — 완성 토큰 889개)를 기본 fp8_e5m2 캐시로 돌린 뒤, 재시작해 --kv-cache-dtype bfloat16로 다시 돌렸다. 결과는 6개 전부 바이트 단위로 동일. 이 (작고 greedy한) 표본에서는 압축이 정말로 공짜다. fp8 캐시를 쓰면 된다.
32GB가 어디로 가는가(bf16-KV 부팅, 최대 컨텍스트 65,536): 사용 가능 HBM은 31.24GiB로 잡히고, 0.9 활용률에서 vLLM은 28.12GiB 안에서 움직인다. 가중치 약 5.5~6GiB, KV 캐시 16.3GiB(8,713블록 × 128토큰 × 15레이어 × 128KiB), 워크스페이스 약 6GiB 정도로 갈린다.
흥미로운 대목은 물리다. E2B의 KV 공유가 할당기에 그대로 드러난다. 35개 레이어 중 15개만 KV 텐서를 들고 있고, 각 레이어의 KV 헤드는 256차원 하나뿐이다. 그래서 토큰 하나가 bf16에서 약 15KiB(fp8에서는 약 7.5KiB)의 KV만 쓴다. 결과적으로 약 110만 토큰의 KV가 칩에 상주한다. 65K 컨텍스트 대화 열일곱 개 분량이다. 스윕이 메모리가 아니라 연산에서 포화된 이유가 여기 있다. 덧붙여, 404초의 엔진 초기화 중 329초가 XLA 컴파일이다. 약 10분짜리 콜드 스타트를 지배하는 항이다.
관련 연구: Zimbres의 커널 치환 연구(DOI 10.5281/zenodo.21404069)는 RPA v3 커널의 디코드 블록 크기 휴리스틱이 v6e의 27B/31B 모델에서 대배치 처리량의 27.7~68.7%를 잡아먹는다고 보고했다. E2B는 이 효과의 노출이 낮은 쪽 끝에 있다. c=64 운영 지점에서 어텐션이 메모리 트래픽의 약 9%인데(그들의 조건에서는 약 41%), 앞서 본 KV 공유 설계 덕이다. 가장 작은 모델·한 칩·KV 공유라는 조건의 E2B에서 오버라이드를 시험해보는 건 자연스러운 후속 과제다.
비용 정리 #
요율은 Google이 공개한 Dynamic Workload Scheduler 가격과 대조해 확인했다. v6e flex-start는 칩당 시간당 1.35달러다(europe-west4, us-east1, us-east5, asia-northeast1).
| 운영 지점 | 출력 tok/s | 출력 100만 토큰당 $ |
|---|---|---|
| 포화 (버스트) | 2,215 | $0.17 |
| 스위트 스폿 (c=64) | 2,140 | $0.18 |
| 대화형 (c=8) | 1,209 | $0.31 |
| 단일 스트림 | 209 | $1.79 |
- API 대비 손익분기: Gemini 2.5 Flash-Lite의 출력 100만 토큰당 0.40달러 기준으로, 초당 약 940출력 토큰을 꾸준히 유지하면 자체 호스팅이 이긴다. 대략 c=8 운영 지점을 계속 붙잡고 있는 수준이다.
- 콜드 스타트(약 8.5
10.5분, 대부분 XLA 컴파일)는 프로비저닝당 0.190.24달러가 든다. - 4시간 세션 전체는 5.40달러이고, 포화 상태에서 약 3천만 출력 토큰을 뽑는다. Flash-Lite 가격으로 환산하면 약 12달러어치다. flex-start는 배치 버스트에 맞지, 놀고 있는 상시 엔드포인트에는 맞지 않는다.
재현 방법 #
# 서빙 (TPU VM에서)
docker run --name vllm-gemma4 --privileged --net=host -d \
-v /dev/shm:/dev/shm --shm-size 10gb -e HF_HOME=/dev/shm \
-e HF_TOKEN=$(gcloud secrets versions access latest --secret=hf-token) \
vllm/vllm-tpu:nightly vllm serve google/gemma-4-E2B-it \
--tensor-parallel-size 1 --max-model-len 65536 \
--gpu-memory-utilization 0.9 --max_num_batched_tokens 4096 \
--disable_chunked_mm_input --enable-auto-tool-choice \
--tool-call-parser gemma4 --reasoning-parser gemma4 \
--limit-mm-per-prompt '{"image":4,"audio":1}' # 텍스트 전용은 {"image":0,"audio":0}
# KV 비교: --kv-cache-dtype bfloat16 추가 (v6e 기본값은 fp8_e5m2)
# 벤치마크 (동시성 레벨 C별)
vllm bench serve --backend vllm --model google/gemma-4-E2B-it \
--dataset-name random --num-prompts 100 \
--random-input-len 1024 --random-output-len 128 --max-concurrency C
# 직접 트래픽이 막힌 네트워크에서 엔드포인트에 접근
gcloud compute start-iap-tunnel vllm-gemma4-e2b 8000 \
--local-host-port=localhost:8000 --zone=europe-west4-a환경: vLLM 0.23.1rc1.dev1076+g5c342876a (vllm-tpu:nightly, tpu-inference 백엔드) · TPU v6e-1 (ct6e-standard-1t, HBM 32GB, GCE flex-start) · bf16 가중치, fp8_e5m2 KV 캐시. 구성별 단일 벤치마크 실행이므로 약 10% 미만 차이는 노이즈로 보라.
이 글은 위 출처를 바탕으로 한국 독자를 위해 재작성한 기사입니다. 원문의 사실과 수치에 근거하며, 별도의 견해를 포함하지 않습니다.
