운영 문서에서 IP·호스트명·계정·자격증명을 제거한 버전. 노드는 node1~node4로 표기.
계기
X에서 이런 게시물을 봤다 (@net_termina, 2026-09-04):
GLM 5.3 Flash TP=4 — 5718 tok/s @ 512 Concurrent Users, 6610 tok/s @ C=768 (Engine Decode). Tried c=1024, don’t fit.
같은 모델, 같은 텐서 병렬 4-way. 우리 4노드 DGX Spark(GB10) 클러스터는 동시 6명에서 합산 90 tok/s였다. 73배 차이. 하드웨어가 다르다는 건 알지만, 73배가 전부 하드웨어일 리는 없었다. 우리 설정에는 --max-num-seqs 6이 박혀 있었다.
작성자가 답글에 공개한 하드웨어 사양은 RTX PRO 6000 Blackwell Workstation Edition 4장(96GB GDDR7 ×4 = 384GB), Threadripper PRO 9985WX, DDR5 768GB. 추론 엔진·양자화 방식은 공개되지 않았다.
두 하드웨어의 결정적 차이
| RTX PRO 6000 Blackwell ×4 | DGX Spark GB10 ×4 (우리) | |
|---|---|---|
| GPU 메모리 | GDDR7 96GB ×4 | LPDDR5x 128GB 통합 ×4 |
| 메모리 대역폭 (GPU당) | 약 1.8 TB/s | 약 273 GB/s |
| NVFP4 텐서코어 | 있음 | 있음 (SM 12.1, 아래 정정 참조) |
| MoE FP4 커널 | 미공개 | marlin(weight-only, bf16 연산) — 레시피 설정 |
| TP 통신 | 같은 호스트 PCIe | 노드 간 RoCE(200GbE) |
| CUDA 그래프 | 사용 가능 | --enforce-eager(SM121 안정성 이유로 레시피가 선택) |
디코딩은 메모리 대역폭에 묶인다. 대역폭 차이만 약 6.6배다. 나머지 요인(weight-only FP4 경로, eager 모드, 노드 간 통신)까지 합치면 하드웨어·런타임 차이로 설명되는 격차는 10~20배 정도로 추정했다. 73배 중 나머지는 설정이어야 했다.
실험
--max-num-seqs를 6에서 64로 올리고 재기동했다. 재기동은 지난 글에서 만든 워치독의 --restart-now --force로 수행했고, 부팅 약 10분. KV 캐시 풀은 2,596,374 토큰으로 변화 없음(이 값은 --kv-cache-memory가 정하고 max-num-seqs와 무관하다). 워커 노드 메모리 여유도 26~27GB로 변화 없음.
측정: 서로 다른 20종의 코드 구현 과제 × 6개 언어를 조합한 고유 프롬프트, 출력 300 토큰, temperature 0, 동시 요청을 한 번에 투입해 벽시계 기준 합산 처리량과 요청별 평균 속도를 기록. 트윗과 같은 “각자 다른 코드를 생성하는 벽” 조건을 맞췄다.
| 동시 요청 | 벽시계 | 합산 처리량 | 사용자당 평균 |
|---|---|---|---|
| 1 | 4.3초 | 69 tok/s | 69 tok/s |
| 6 (기존 상한) | 16.1초 | 111 tok/s | 21 tok/s |
| 16 | 29.3초 | 164 tok/s | 12.2 tok/s |
| 32 | 39.0초 | 243 tok/s | 9.4 tok/s |
| 64 | 68.0초 | 280 tok/s | 7.2 tok/s |
| 64 (출력 600 토큰) | 118.5초 | 305 tok/s | 7.8 tok/s |
64건 모두 성공, 오류 0. 서빙 중 크래시나 Xid 이벤트 없음.
읽기
상한 6은 병목이었다. 같은 하드웨어에서 합산 처리량이 90 → 305 tok/s, 3.4배. 곡선은 32→64 구간에서 +15%로 완만해지므로 이 클러스터의 포화점은 64 부근이다. 벽시계 측정은 마지막 요청이 끝날 때까지의 꼬리를 포함하므로 엔진 정상 상태 디코딩은 이보다 높다. 트윗의 “Engine Decode” 6,610 tok/s는 엔진 쪽 수치라 직접 비교 시 우리 쪽이 다소 불리하게 잡힌다.
남은 격차는 약 20배. 73배에서 20배로 줄었고, 이 20배는 위 표의 하드웨어·런타임 차이와 대체로 맞는다. 더 줄일 여지는 --enforce-eager 제거(CUDA 그래프) 정도인데, 레시피가 안정성 이유로 eager를 택했으므로 지금은 건드리지 않는다.
사용자당 속도는 양쪽이 비슷한 지점에서 포화한다. 트윗 답글에서 누군가 계산했듯 C=768에서 6,610 tok/s는 사용자당 약 8.6 tok/s다. 우리는 C=64에서 7~8 tok/s. 즉 두 시스템 모두 “한 사람이 읽는 속도” 근처까지 동시성을 밀어 넣은 상태이고, 차이는 그 상태를 몇 명에게 제공하느냐다. 768명 대 64명.
대화형과 배치는 다른 설정을 원한다. 사용자당 12 tok/s 이상을 유지하려면 동시 16 이하가 적정하고, 응답 시간이 덜 중요한 배치 작업은 64까지 밀어도 된다. 상한을 64로 두고 대화형 부하가 그 안에서 자연스럽게 낮은 동시성으로 돌아가게 하는 쪽을 택했다. 상한 64를 유지한다.
한계
- 상대 측의 추론 엔진, 양자화, 프롬프트 길이, 출력 길이가 공개되지 않아 조건이 같다고 볼 수 없다.
- 우리 측정은 요청을 동시에 투입하는 폐쇄형 부하이며, 실제 서비스의 도착 분포와 다르다.
- 1회 측정. 반복 측정과 p99 지연은 재지 않았다.
정정 (2026-09-05 추가)
처음 올린 판에는 위 표에 “NVFP4: 에뮬레이션(marlin 커널)“이라고 썼다. 틀렸다. GB10(SM 12.1)은 FP4 텐서코어를 갖고 있고 우리 vLLM 빌드도 SM121용 CUTLASS NVFP4 커널을 갖고 있다. marlin은 레시피 설정이었다. 표와 본문은 고쳤고, 확인 과정과 FP4 텐서코어 경로로 바꿔 잰 결과(전 구간 같거나 느림)는 별도 글에 정리했다.
운영 메모
계획 재기동을 워치독으로 수행하면 재기동 이력에 항목이 남아 자동 재기동 예산(6시간 2회)을 소모한다. 실험 후 해당 항목을 수동으로 제거했다. 다음 실험부터는 이 정리를 절차에 포함한다.