ASUS Ascent GX10 계열 2노드를 묶어 DeepSeek V4 Flash 0731 원본 FP8을 서빙하고, 원격에서 코딩 에이전트로 쓰는 상태까지 만들었다. 양자화로 타협한 모델이 아니라 166.9GB 원본 체크포인트다.
1M 컨텍스트로 서빙되며 원격 에이전트에서 바로 쓴다. 벤치는 95개 케이스를 측정했다.
핵심 셋. 디코드는 컨텍스트에 거의 무관하다(256토큰 56.1 → 512k 45.9 tok/s). 디코드는 클럭 바운드도 아니다(클럭 40% 인상에 5% 반응, 전력은 두 배). 프리필만 클럭에 반응하는데 2400MHz에서 포화한다.
권고. 전성비는 최저 클럭에서 최고다. 디코드 1.41 tok/s/W(2000MHz) ↔ 0.70(해제)로 두 배 차이. TTFT가 절실하지 않으면 2000 유지가 답이다.
| 항목 | 값 |
|---|---|
| 모델 | deepseek-ai/DeepSeek-V4-Flash-0731, FP8 e4m3 블록양자화(128×128) |
| 검증 | 리비전 9e165c30 해시 전량 일치 — sha256 48/48 + git sha1 26/26, 양 노드 각각 |
| 구조 | 43층, routed expert 256개, 토큰당 6개 활성, MTP 레이어 1 |
| 런타임 | vLLM mp + --nnodes 2, TP=2 |
| 컨텍스트 | 1,048,576 (실측 512k 통과) |
| 투기 디코딩 | DSpark k=5, 초안 수용률 83~87% |
| KV 캐시 | nvfp4_ds_mla |
| 인터커넥트 | ConnectX-7 200GbE 직결, RoCE v2 듀얼 rail, 실측 196 Gb/s |
NAS에는 DS4F 계열이 다섯 갈래로 쌓여 있었다. 어느 걸 올릴지가 첫 갈림길이었다.
| 체크포인트 | 크기 | 1노드(121GB) | 판단 |
|---|---|---|---|
원본 FP8 deepseek-ai/…-0731 | 166.9 GB | 불가 | 채택 |
| antirez MXFP4 GGUF | 156 GB | 불가 | llama.cpp 경로 후보 |
| huihui Q4_K abliterated | 164.6 GB | 불가 | 무검열 실험용 |
| unsloth UD-Q2_K_XL | 90 GB | 가능 | 1 vs 2노드 A/B 기준 |
| unsloth UD-Q3~Q8_K_XL | 178~245 GB | 불가 | 2노드에도 빠듯 |
목표가 "타협 없는 원본"이었으므로 답은 정해져 있었다. 그리고 이 선택이 곧 2노드를 강제한다. 166.9GB는 121GB 한 대에 안 들어간다. 커뮤니티가 2대를 스위트 스팟이라 부르는 이유가 이것이다. 1대로 가려면 IQ2급까지 내려야 하는데, 그건 다른 모델을 돌리는 것에 가깝다.
받은 뒤에는 진짜인지 확인했다. HF API로 리비전 9e165c30의 파일 해시를 받아 로컬과 대조했다. safetensors 48샤드 sha256과 작은 파일 26개의 git sha1이 양 노드에서 각각 전량 일치했다.
X 공개 게시물에서 실제로 돌려본 1차 보고만 추렸다. 두 가지가 방향을 바꿨다.
2× DGX Spark에 DS4F를 올린 레시피를 공개하고 실측을 이어 보고했다. 싱글 스트림 72 tok/s, 6동시 137 tok/s 집계. 2대가 이 모델의 스위트 스팟이라는 관측.
첫째, NVIDIA 공식 NGC vLLM 이미지로는 안 된다. 공식 DGX Spark 플레이북에 2노드 vLLM 절차가 있지만 예제가 Llama이고 DS4F 지원이 없다. 실제 성공 사례는 예외 없이 GB10 전용으로 빌드된 커뮤니티 이미지를 쓴다.
둘째, Ray가 아니다. 공식 문서는 Ray 클러스터를 안내하는데, DS4F 2노드 1차 보고들은 전부 --distributed-executor-backend mp에 --nnodes 2를 쓰고 worker를 먼저 띄운 뒤 head를 올린다.
RoCE v2 토폴로지 체크리스트. 두 포트 모두에 IP가 필요하고(GID가 IP에서 생성됨), 같은 서브넷에 두면 라우팅이 한쪽으로 고정된다. 한쪽 rail만 쓰면 처리량이 약 110 Gb/s로 반감. LACP 본딩은 불필요.
네트워크는 이 체크리스트를 그대로 따랐다. 두 rail에 서로 다른 서브넷을 주고 본딩은 하지 않았다. 실측 196 Gb/s가 나온 건 이 구성 덕이다.
세 축을 골랐다. 컨텍스트 길이(256~512k)는 이 모델을 쓰는 이유가 1M 컨텍스트라서, 동시성(1~12)은 서버로 쓸 때 몇 명까지 받을 수 있는지가 궁금해서, 클럭 상한(2000~해제)은 전력 튜닝이 성능을 얼마나 깎는지 확인해야 해서다.
벤치 도구는 레시피에 동봉된 것을 그대로 썼다. 공표 수치를 만든 스크립트와 같아야 비교가 성립한다. 다만 두 곳을 고쳤다.
max_tokens=2048을 걸었다. 원본은 출력 상한이 없는데 서버 기본값이 DEFAULT_THINKING=max라, 요청 하나가 추론 토큰을 무한정 뽑았다. 레시피가 공표한 방법론도 완성 토큰 2,048 기준이다.클럭 스윕에는 안전장치를 뒀다. 지점마다 70°C 미만까지 식히고, 84°C를 넘으면 남은 스윕을 중단하며, 어떤 경로로 끝나든 원래 캡으로 복구한다. 커뮤니티에 과열 셧다운 보고가 있어서다.
2× MSI EdgeXpert에서 SM 클럭을 2450 → 2200으로 낮춘 A/B. 온도 정점 −10°C, 전력 −29%인데 디코드는 거의 동일하고 프리필만 +4% 느려졌다.
클럭 스윕의 출발 가설이 이 보고였다. 우리 결과는 방향은 같고 폭은 더 컸다. 2000~2800 구간에서 디코드는 노이즈 범위였고 프리필만 2400까지 19% 움직였다.
위에 인용한 것 외에 구성·검증에 쓴 자료들이다.
ghcr.io/anemll/dspark-vllm-gx10:0.1.1X@MiaAI_lab2노드 DSpark 레시피·1× vs 2× 품질 비교2대 스위트 스팟이 DS4F임을 제시. 싱글 ~72 tok/s, 6동시 ~137 agg 보고GitHubNVIDIAdgx-spark-playbooks — connect-two-sparks · vllm네트워크 구성(두 rail·분리 서브넷)은 이걸 따랐다. 다만 vLLM 2노드 예제는 Llama용이라 DS4F에는 못 씀GitHubvllm-projectPR #41834 — sm_121 지원GB10용 vLLM 빌드 경로. 0731 + DSpark serve 인자 원본GitHubtonyd2wildDeepseek-v4-Flash-TP2-DGX-Spark-500k-CTX대안 레시피(mtp k=2, fp8 KV). mp + --nnodes 2 패턴을 교차 확인GitHubeugrspark-vllm-docker — NETWORKING.mdRoCE twin 구조와 서브넷 분리 필요성 설명X@takuz0_RoCE v2 토폴로지 체크리스트두 포트 모두 IP 필요·같은 서브넷 금지·한쪽만 쓰면 대역 반감. 우리 구성의 근거X@Beast_Llama_FP8 TP=2 12스트림 실측12 동시 스트림 150.8 tok/s agg. 우리 206.9와 비교 기준X@kaustabpal클럭 캡 전후 A/BSM 2450→2200에서 decode 거의 동일·prefill +4% 보고. 우리 클럭 스윕의 출발 가설X@esteban_x64CUDA 13 · SM121 빌드 + DSpark k=5raw 27 → DSpark 50~75 tok/s. 투기 디코딩 이득의 근거X@HuiYang41183NCCL 대역폭 실측양 rail 사용 시 NCCL 21.6 GB/s. GDR 없음이 병목이라는 오해를 정정X@mochi_mochi_lab1대 컨텍스트 한계1× 128GB에서 768k 통과·1M OOM. 우리 512k 통과의 비교 기준이 시스템의 성격을 정하는 사실이다. 컨텍스트를 2,000배 늘려도 생성 속도가 18%만 떨어진다.
그림 1. 컨텍스트 길이별 디코드 처리량(동시성 1). 막대에 마우스를 올리면 해당 케이스의 전체 수치가 뜬다.
그림 2. 프롬프트 길이(가로, 로그) 대 TTFT(세로, 로그). 프리필 처리량이 평탄하므로 TTFT는 길이에 정비례한다. 점선은 "1,000토큰당 0.5초" 기준선. 빈 원(프롬프트 2,048)은 프리필이 9,480 tok/s로 이웃의 5배라 프리픽스 캐시 적중으로 보고 추세선에서 뺐다.
프롬프트 1,000토큰당 약 0.5초다. 65k면 37초, 512k면 9분. 프리필 처리량은 4k~32k에서 1,860~1,980으로 정점을 찍고 512k에서 944로 감쇠한다.
MAX_NUM_SEQS=6에서 139.3 tok/s가 천장처럼 보였지만 설정 한계였다. 12로 열자 206.9 tok/s까지 오른다.
그림 3. 동시성별 집계 처리량. 서버가 초당 뽑아내는 총 토큰이다.
그림 4. 같은 실험의 세션당 생성 속도. 사용자 한 명이 체감하는 속도로, 그림 3과 정확히 반대로 움직인다.
프롬프트 256에서 동시성 1→12로 가면 집계는 4.5배가 되지만 세션당은 49.8 → 19.2 tok/s로 떨어진다. 체감 속도 2.6배를 내주고 서버 처리량 4.5배를 얻는 거래다. 프롬프트 2,048에서는 10→12 이득이 이미 사라진다.
동시성의 이득은 프롬프트 길이에 반비례한다. 256에서 1→6이 집계를 2.5배 올리지만 131k에서는 1.1배에 그치고 TTFT만 84초에서 224초로 나빠진다. 긴 컨텍스트 작업은 동시성을 낮게 둬야 한다.
그림 5. 클럭 상한별 디코드·프리필·전력·전성비. 전성비(맨 아래)는 클럭이 오를수록 단조 감소하며 2000MHz가 최고다. 막대에 마우스를 올리면 양 노드 실클럭·전력·온도가 뜬다.
| 상한 MHz | 실클럭 A/B | 디코드 t/s | 프리필 t/s (16k) | 전력 W A/B | 온도 °C A/B | 전성비 t/s/W |
|---|---|---|---|---|---|---|
| 2000 | 1963 / 1982 | 54.4 | 1,794 | 20.9 / 17.7 | 67 / 62 | 1.41 |
| 2200 | 2184 / 2190 | 52.8 | 1,876 | 25.7 / 21.8 | 68 / 65 | 1.11 |
| 2400 | 2392 / 2385 | 56.4 | 2,134 | 32.1 / 26.6 | 69 / 65 | 0.96 |
| 2600 | 2496 / 2444 | 56.0 | 2,156 | 39.7 / 33.4 | 72 / 71 | 0.77 |
| 2800 | 2476 / 2418 | 57.3 | 2,150 | 40.8 / 32.8 | 73 / 69 | 0.78 |
| 해제 | 2476 / 2418 | 55.0 | 1,953 | 42.7 / 35.7 | 75 / 72 | 0.70 |
디코드는 40% 클럭 인상에 5% 반응하고 개별 값이 49.8~63.8로 흩어져 노이즈와 구분되지 않는다. 메모리 대역폭 바운드다. 프리필은 2400까지 19% 오르고 그 위로는 평평한데, 상한을 2600 이상으로 올려도 실클럭이 2476~2496에서 멈추기 때문이다.
전성비를 보면 결론이 분명해진다. 디코드 기준 2000MHz에서 1.41 tok/s/W, 캡 해제 시 0.70으로 정확히 절반이다. 프리필도 46.5 → 24.9로 같은 방향이다. 성능이 거의 안 오르는데 전력만 오르니 당연한 결과다.
그림 6. 벤치 구간(04:33~06:44, 2시간 11분)의 온도 추이. 세로 점선이 페이즈 경계다. 후반 봉우리들이 클럭 스윕이고 상한이 오를수록 정점이 높아진다. 그래프 위에서 마우스를 움직이면 해당 시각의 두 노드 온도·전력·클럭이 뜬다.
유휴 45~47°C·8W, 부하 시 평균 65~67°C·최고 80°C로 스로틀 시작선 82°C를 넘지 않았다. 노드 A가 B보다 1~3°C 높은 것은 API 서버와 스케줄링을 함께 지기 때문으로 보인다.
200G 링크에서 RDMA가 13.3 Gb/s였다. 공식 예시는 합산 189.85 Gb/s다. 그런데 모든 상태 지표가 정상이었다. 링크 200000Mb/s, RoCE 4X × 50 Gbps, PCIe Gen5 x4, err·drop·pause 전부 0, 점보프레임 통과. 조회로는 잡히지 않는다.
범위는 두 실험으로 좁혔다. 같은 디바이스에 프로세스를 둘 띄우니 6.89 + 6.89로 나눠 가졌고(디바이스당 상한), 단일 노드 안에서 함수끼리 RDMA를 걸어도 13.19가 나왔다(케이블·상대 노드 배제). NVMe DMA는 같은 SMMU 아래 40 Gb/s가 나오므로 SMMU 일반의 문제도 아니다.
해결은 AC 완전 차단·방전이었다. 전원을 뽑고 방전한 뒤 1분 후 재연결하니 13.26 → 108.33 Gb/s로 복구됐다. 두 rail 동시 196.02 Gb/s. 재부팅으로는 풀리지 않는다.
permanent MAC address doesn't match로 활성화가 실패한다.NCCL_IB_GID_AUTO=0으로 인덱스를 직접 핀해야 한다.첫 벤치는 max_tokens가 없고 서버 기본값이 DEFAULT_THINKING=max여서 요청 하나가 추론 토큰을 무한정 생성했다. 케이스 하나에 2시간 45분이 걸렸다.
두 번째는 더 나빴다. 죽인 줄 알았던 클럭 스윕 스크립트가 살아남아 다른 벤치와 동시에 서버를 때리면서 GPU 클럭까지 바꾸고 있었다. pkill -f를 ssh 원격 명령으로 실행했더니 그 패턴이 원격 셸 자신과 매칭돼 셸이 먼저 죽은 것이 원인이다. 같은 조건을 재측정하니 집계 처리량이 92.5 → 139.3 tok/s로 달라졌다.
이후 스크립트를 단일 직렬 실행으로 통합하고 flock 락을 넣었다. 프리필 값은 프리픽스 캐시 적중까지 걸러내야 해서, 실행마다 프롬프트를 고유화해 따로 다시 쟀다.
pi --provider <클러스터> --model deepseek-v4-flash-0731 -p "..."
tailnet 내부 HTTP 엔드포인트로 붙는다. fizzbuzz 과제로 검증했고 파일 쓰기·명령 실행까지 동작했으므로 deepseek_v4 툴 콜 파서가 정상이다.
MAX_NUM_SEQS 운용값 결정: 다중 사용자면 12, 단독 장기 컨텍스트면 6| 프롬프트 | 동시성 | 집계 t/s | 세션당 t/s | 프리필 t/s | TTFT |
|---|---|---|---|---|---|
| 256 | 1 | 55.8 | 56.1 | 1,744 | 0.21s |
| 256 | 2 | 72.6 | 38.6 | 1,122 | 0.33s |
| 256 | 4 | 114.1 | 34.4 | 965 | 0.39s |
| 256 | 6 | 139.3 | 24.7 | 307 | 1.21s |
| 1,024 | 1 | 54.7 | 56.3 | 1,306 | 0.87s |
| 2,048 | 1 | 49.8 | 50.1 | 9,480 | 0.23s |
| 2,048 | 2 | 72.8 | 38.4 | 997 | 2.17s |
| 2,048 | 4 | 107.9 | 29.0 | 489 | 4.42s |
| 2,048 | 6 | 117.2 | 22.3 | 556 | 4.08s |
| 4,096 | 1 | 44.9 | 47.1 | 1,933 | 2.18s |
| 8,192 | 1 | 48.6 | 54.1 | 1,946 | 4.27s |
| 8,192 | 2 | 71.0 | 42.7 | 956 | 9.19s |
| 8,192 | 4 | 87.9 | 27.0 | 560 | 15.17s |
| 8,192 | 6 | 97.1 | 20.5 | 409 | 20.51s |
| 16,384 | 1 | 44.4 | 54.2 | 1,980 | 8.33s |
| 32,768 | 1 | 37.5 | 55.5 | 1,860 | 17.68s |
| 32,768 | 2 | 45.3 | 35.7 | 1,143 | 30.13s |
| 32,768 | 4 | 57.7 | 22.5 | 704 | 48.12s |
| 32,768 | 6 | 61.0 | 15.5 | 507 | 66.33s |
| 65,536 | 1 | 25.5 | 47.9 | 1,749 | 37.53s |
| 131,072 | 1 | 16.7 | 54.0 | 1,553 | 84.49s |
| 131,072 | 2 | 17.7 | 26.4 | 1,063 | 135.91s |
| 131,072 | 4 | 19.2 | 10.6 | 610 | 223.55s |
| 262,144 | 1 | 8.1 | 50.3 | 1,233 | 212.70s |
| 524,288 | 1 | 3.4 | 45.9 | 944 | 555.55s |
| 프롬프트 | 동시성 | 집계 t/s | 세션당 t/s | TTFT |
|---|---|---|---|---|
| 256 | 1 | 46.4 | 49.8 | 2.98s |
| 256 | 2 | 85.2 | 46.9 | 4.12s |
| 256 | 4 | 105.6 | 30.0 | 5.80s |
| 256 | 6 | 136.1 | 25.1 | 1.39s |
| 256 | 8 | 169.5 | 22.6 | 1.95s |
| 256 | 10 | 174.2 | 19.0 | 2.79s |
| 256 | 12 | 206.9 | 19.2 | 2.68s |
| 2,048 | 1 | 55.1 | 57.0 | 1.18s |
| 2,048 | 2 | 75.2 | 39.4 | 1.79s |
| 2,048 | 4 | 98.1 | 27.9 | 5.30s |
| 2,048 | 6 | 128.5 | 23.6 | 5.12s |
| 2,048 | 8 | 152.9 | 21.2 | 7.82s |
| 2,048 | 10 | 168.8 | 18.8 | 7.83s |
| 2,048 | 12 | 172.9 | 16.3 | 8.24s |
| 8,192 | 6 | 99.0 | 21.5 | 25.40s |
| 8,192 | 12 | 134.9 | 14.8 | 32.07s |
max_tokens=2048 패치temperature 0.6, top_p 0.95, 서버 기본 DEFAULT_THINKING=max