SHIFTONE

32B 모델은 80 GB 에 들어가는데 동시 사용자는 0명이에요 — 폐쇄망 온프레미스 LLM 서빙, GPU 장수는 가중치가 아니라 KV 캐시가 정해요

견적서의 H100 8장과 2장, 어느 쪽이 맞을까요. 공개 config.json 과 KV 캐시 공식만으로 Qwen3-32B · gpt-oss-120b 가 GPU 한 장에서 받는 동시 요청 수를 계산했어요. 장수를 늘리기 전에 가중치 정밀도 · KV 정밀도 · 컨텍스트 상한을 어떤 순서로 만지는지 정리했어요.

보라색과 파란색 조명이 비친 서버 전면에 은색 냉각 팬이 줄지어 박혀 있고 뒤로 냉각 배관이 이어진 사진 — 가중치가 아니라 요청마다 쌓이는 캐시가 채우는 GPU 메모리

견적서에 H100 8장이 적혀 있어요. 왜 8장이냐고 물으면 "32B 모델이면 넉넉히 잡아야죠"라는 답이 돌아오고, 다른 쪽 견적서에는 같은 모델에 2장이 적혀 있어요. 둘 다 근거를 못 대니 결정이 안 나요.

고객사 이야기가 아니라 폐쇄망 LLM 도입 검토에서 자주 보는 장면이에요. 저희가 이 자리에서 꺼내는 건 숫자 세 개예요. 가중치 크기, 토큰당 KV 캐시, 동시 요청 수 × 컨텍스트 길이. 이 셋이면 GPU 몇 장인지는 한 줄 계산으로 나와요. 공개 모델 두 개로 직접 계산해 봤어요.

가중치는 들어가는데 사용자는 못 들어가요

GPU 메모리를 먹는 건 셋이에요. 모델 가중치, 요청마다 쌓이는 KV 캐시, 계산 중간값이 머무는 작업 공간이에요. 가중치는 파라미터 수에 바이트를 곱하면 끝이라 다들 여기까지는 계산해요. 32.8B 인 Qwen3-32B 를 BF16(2바이트)으로 올리면 65.6 GB, 80 GB 짜리 H100 한 장에 들어가요. 그래서 "1장이면 되겠네"가 돼요.

문제는 KV 캐시예요. 모델이 앞 토큰을 다시 읽지 않으려고 토큰마다 키와 값을 저장해 두는데, 이건 요청이 늘 때마다, 토큰이 늘 때마다 자라요. NVIDIA 기술 블로그의 공식은 토큰 하나당 2 × 레이어 수 × KV 헤드 수 × 헤드 차원 × 바이트예요. Qwen3-32B 의 config.json 을 열면 레이어 64, KV 헤드 8, 헤드 차원 128 이니 2 × 64 × 8 × 128 × 2바이트 = 262,144바이트, 토큰 하나에 256 KiB 예요.

컨텍스트 32K 를 꽉 채운 요청 하나가 8.6 GB 를 들고 있는 거예요. 서빙 엔진은 GPU 를 다 쓰지도 않아요. vLLM 기본값은 메모리의 92% 까지만 잡으니 예산이 73.6 GB 고, 작업 공간 2 GB 를 빼면 KV 에 남는 건 6 GB 예요. 32K 요청은 한 개도 못 들어가요.

H100 80 GB 한 장 — 가중치를 줄여야 KV 캐시 자리가 생겨요 (Qwen3-32B, 32K 컨텍스트 요청 기준) A. 가중치 BF16 가중치 65.6 GB KV 6 예약 → 32K 요청 0개8K 요청이면 2개. 가중치는 들어갔는데 사용자가 못 들어가요 B. 가중치 FP8 가중치 32.8 GB KV 캐시 예산 38.8 GB 예약 → 32K 요청 4개KV 캐시도 FP8 이면 9개. 8K 요청이면 18개 / 36개 요청 하나의 KV 캐시 = 토큰당 256 KiB (2 × 레이어 64 × KV 헤드 8 × 헤드 차원 128 × 2바이트) × 토큰 수 32K 토큰이면 8.6 GB. ■ 짙은 칸은 작업 공간 2 GB(가정값), 점선 칸은 vLLM 이 손대지 않는 8% 예약 6.4 GB

두 장을 사기 전에 FP8 을 먼저 켜요

견적서의 장수 차이는 대부분 가중치 정밀도와 동시 요청 가정의 차이예요. 말로 다투는 대신 표로 만들기로 했고, 공개 공식만 쓰니 열 줄이면 돼요.

GB = 1e9
layers, kv_heads, head_dim = 64, 8, 128          # Qwen3-32B config.json
params = 32.8e9
budget = 80 * 0.92 - 2.0                          # vLLM 기본 92%, 작업 공간 2 GB 가정

def kv_per_token(kv_bytes):                       # NVIDIA 블로그 공식
    return 2 * layers * kv_heads * head_dim * kv_bytes

for w_bytes, kv_bytes in [(2, 2), (1, 2), (1, 1), (0.5, 1)]:
    kv_budget = budget - params * w_bytes / GB
    per_req = kv_per_token(kv_bytes) * 32_768 / GB
    print(w_bytes, kv_bytes, round(kv_budget, 1), int(kv_budget // per_req))

이걸 돌리니 "몇 장"이 아니라 "몇 명"이 먼저 나왔어요.

Qwen3-32B, H100 80 GB가중치KV 예산8K 요청32K 요청
1장 · BF16 가중치 · BF16 KV65.6 GB6.0 GB2개0개
1장 · FP8 가중치 · BF16 KV32.8 GB38.8 GB18개4개
1장 · FP8 가중치 · FP8 KV32.8 GB38.8 GB36개9개
1장 · INT4 가중치 · FP8 KV16.4 GB55.2 GB51개12개
2장 텐서 병렬 · BF16 가중치 · BF16 KV65.6 GB77.6 GB36개9개
2장 텐서 병렬 · FP8 가중치 · FP8 KV32.8 GB110.4 GB102개25개

한 장에 FP8 가중치와 FP8 KV 캐시를 켠 줄과, 두 장에 BF16 으로 올린 줄이 같은 숫자예요. GPU 한 장 값이 정밀도 설정 하나와 맞먹는 거예요. vLLM 은 --kv-cache-dtype fp8 로 KV 캐시 정밀도만 따로 낮출 수 있고, 보정 데이터셋으로 스케일을 잡는다고 문서에 적혀 있어요. 그래서 저희 순서는 장수를 늘리는 게 아니라 정밀도를 낮추고, 그 모델이 골든셋을 통과하는지 먼저 보는 쪽이에요. 양자화한 모델도 "다른 모델"이라 모델 교체 평가 게이트와 같은 절차를 태워요.

117B 가 80 GB 한 장에 들어가는 이유는 아키텍처예요

파라미터 수로 GPU 를 세면 틀리는 두 번째 이유가 있어요. gpt-oss-120b 는 총 117B 인데 모델 카드가 80 GB GPU 한 장에 맞게 설계했다고 적어요. 배포된 가중치 파일 합이 65.3 GB 예요. 토큰마다 쓰이는 파라미터는 5.1B 뿐인 MoE 라 전문가 가중치를 4비트(MXFP4)로 눌렀고, 그래서 32B 모델의 BF16 가중치와 비슷한 크기예요.

KV 캐시는 더 달라요. 레이어 36, KV 헤드 8, 헤드 차원 64 라 토큰당 72 KiB 로 Qwen3-32B 의 3분의 1 이 안 돼요. 게다가 레이어 절반은 128 토큰짜리 슬라이딩 윈도라 긴 컨텍스트에서 거의 자라지 않아요. 6.3 GB 남짓한 KV 예산으로 32K 요청 5개, 128K 요청도 1개가 들어가요. 총 파라미터는 3.6배인데 긴 요청은 더 많이 받는 거예요.

올가을 나온 오픈 웨이트 모델이 대부분 이 구조예요. deepset 정리에 따르면 7월 말 이후 아홉 연구소가 모델을 냈고, Meta Muse Spark Open 은 816B 에 활성 42B, NVIDIA Nemotron 3.5 Bolt 는 74B 에 활성 9B 예요. "총 파라미터 × 2바이트"로 세면 두 배씩 틀려요. config.json 네 줄이 견적서보다 빨라요.

바로 믿으면 안 되는 세 가지

첫째, 이 계산은 들어가느냐만 답해요. 초당 토큰 수는 메모리 대역폭과 배치가 정하고, H100 SXM 의 3.35 TB/s 가 생성 속도의 천장이에요. 요청 25개가 들어간다는 것과 25명이 쓸 만하다는 건 다른 말이라 처리량은 실제 하드웨어에서 재야 해요. 저희도 이 글에서 GPU 를 돌리지 않았어요.

둘째, 작업 공간 2 GB 는 가정값이에요. 모델과 배치에 따라 달라지고 긴 프롬프트를 한 번에 처리할 때 더 커져요. 표의 요청 수는 그만큼 흔들려요.

셋째, 이 표는 PagedAttention 처럼 KV 캐시를 조각 단위로 관리하는 엔진을 전제해요. vLLM 팀은 2023년 글에서 기존 서빙 시스템이 단편화와 과잉 예약으로 KV 메모리의 60~80% 를 버린다고 썼어요. 그런 엔진이면 표의 숫자를 절반 이하로 봐야 해요.

AS-ISTO-BE
산정 근거파라미터 수 × 2바이트, 넉넉히 두 배가중치 + 토큰당 KV × (동시 요청 × 컨텍스트) + 작업 공간
조정 손잡이GPU 장수가중치 정밀도 → KV 정밀도 → 컨텍스트 상한 → 장수
검증벤더 말config.json 계산 → 골든셋 → 실측 처리량

파일럿에서 운영으로 넘어갈 때 무너지는 지점에 GPU 를 적어 둔 이유도 이거예요. 파일럿은 사용자 한 명이라 KV 캐시가 보이지 않아요. 에이전트 비용이 대화 길이의 제곱으로 붙는 것과 같은 이유로, 온프레미스에서는 그 길이가 요금이 아니라 메모리로 청구돼요.

8장과 2장 중 어느 쪽이 맞느냐로 돌아오면, 답은 "동시에 몇 명이 몇 토큰을 쓰느냐를 먼저 정하라"예요. GPU 산정은 하드웨어 문제가 아니라 사용 시나리오를 숫자로 적는 일이고, 그 숫자는 도입 뒤에도 계속 고쳐 쓰는 숫자예요.

자주 묻는 질문

Q. 폐쇄망이라 vLLM 같은 오픈소스 엔진을 못 쓰면요? A. 공식은 엔진과 무관해요. 다만 KV 캐시를 조각 단위로 관리하지 않는 엔진이면 동시 요청 수를 표의 절반 이하로 잡고, 엔진이 메모리를 몇 % 까지 쓰는지 문서에서 확인하세요.

Q. 컨텍스트를 128K 로 열어 두면 안 되나요? A. 열어 둘 수는 있지만 요청 수 계산에 최악값으로 들어가요. Qwen3-32B 는 128K 요청 하나가 34 GB 예요. 실제 사용 분포를 보고 상한을 정하고, 긴 문서는 RAG 로 자르는 쪽이 GPU 를 덜 써요.

Q. FP8 이나 INT4 로 낮추면 품질이 떨어지지 않나요? A. 떨어질 수 있어요. 그래서 "안 떨어진다"가 아니라 "우리 골든셋에서 얼마나 떨어지는지 재고 결정한다"가 원칙이에요. 슬라이딩 윈도 레이어는 KV 양자화에 민감하다고 vLLM 문서가 적어 두니 그 레이어만 제외하는 옵션부터 써 보세요.

Sources