คู่มือฉบับสมบูรณ์: การออกแบบและ Deploy vLLM บน NVIDIA DGX Spark Cluster แบบ Cross-Node Tensor Parallel (TP=2) — ทีม CYN Communication บันทึกประสบการณ์การสร้าง 2 clusters คู่ขนานสำหรับรัน Qwen3.5-122B-A10B-FP8 (MoE 256 experts, 122 พันล้านพารามิเตอร์) พร้อม architecture design flow, step-by-step deployment guide, diagnostic playbook, และการแก้ 12 bugs จริง ที่เจอบนฮาร์ดแวร์ production
บทความนี้ครอบคลุมตั้งแต่ การออกแบบเครือข่าย RoCE direct-attach การ วาง data flow ของ tensor parallelism การ pre-flight verification ไปจนถึง production troubleshooting decision tree เหมาะสำหรับ AI Infrastructure Engineer, DevOps Engineer, และผู้ที่กำลังประเมินการสร้าง AI cluster ระดับ organization
📋 สารบัญ
- Architecture Design Flow — ทำไมต้องเลือกแบบนี้
- Network Topology + Data Flow
- NCCL Communication Pattern บน DGX Spark
- Hardware Spec Breakdown — GB10 Superchip
- Pre-Flight Checklist — ก่อน Deploy ต้องเช็คอะไร
- Step-by-Step Deployment Guide
- Bug #1–12 — Real-World Issues + Fixes
- Verification & Benchmark
- Production Troubleshooting Decision Tree
- Final Configuration (Reference)
- บทเรียนสำคัญ 10 ข้อ
1. Architecture Design Flow — ทำไมต้องเลือกแบบนี้
1.1 Requirement Analysis
ก่อนเริ่ม deploy คลัสเตอร์ AI ใด ๆ ต้องตอบคำถาม 5 ข้อก่อน:
┌─────────────────────────────────────────────────────────┐
│ DESIGN DECISION FLOW │
│ │
│ 1. โมเดลขนาดเท่าไร? │
│ └─ Qwen3.5-122B-A10B-FP8 │
│ └─ FP8 block quant → ~122 GB weights │
│ │
│ 2. ฮาร์ดแวร์มีหน่วยความจำเท่าไร? │
│ └─ DGX Spark GB10 = 128 GB unified memory │
│ └─ ใช้ได้จริง ~114 GB (หัก OS + buffer) │
│ │
│ 3. 1 node เก็บโมเดลได้ทั้งก้อนไหม? │
│ └─ ไม่พอ! 122 GB > 114 GB available │
│ └─ ต้อง shard → ต้องการ ≥ 2 nodes │
│ │
│ 4. กี่ nodes? │
│ └─ TP=2: 61 GB weights/node + KV cache + CUDA │
│ graphs + activations ≈ 72 GB/node │
│ └─ ลงตัวพอดี ใช้ memory 56% ของแต่ละ node │
│ │
│ 5. เครือข่ายความเร็วเท่าไรพอ? │
│ └─ TP=2 → all-reduce ทุก layer │
│ └─ Qwen3.5 122B มี 60+ layers, batch large │
│ └─ ต้องการ ≥ 100 Gbps / ยิ่งเยอะยิ่งดี │
│ └─ ConnectX-7 200 Gbps RoCE ✓ │
└─────────────────────────────────────────────────────────┘
1.2 Topology Design — Why Direct-Attach RoCE
ทางเลือกการเชื่อมต่อ 2 nodes สำหรับ TP=2:
| วิธี | Bandwidth | Latency | ต้นทุน | Scale to 4+ nodes? |
|---|---|---|---|---|
| 10 GbE Ethernet | 10 Gbps | ~100 μs | ต่ำ | ได้ แต่ bottleneck |
| InfiniBand HDR switch | 200 Gbps | ~1 μs | สูง (ต้องมี switch) | ดีเยี่ยม |
| RoCE direct-attach ✓ | 200 Gbps | ~2 μs | กลาง | 2–3 nodes |
| NVLink Switch | 900 Gbps | <1 μs | แพงมาก | DGX H100/H200 เท่านั้น |
DGX Spark ไม่มี NVLink Switch และทีมมีแค่ 2 nodes → RoCE direct-attach (QSFP112 DAC cable) คือจุด sweet spot
1.3 Orchestration — Why Ray + Docker
┌──────────────────────────────────────────────────────────┐
│ │
│ Ray Cluster ──────────────────┐ │
│ (orchestration) │ │
│ │ │ │
│ ├─> Ray GCS (control) │ │
│ ├─> Placement Group │ Distributed Executor │
│ └─> Worker actors │ for vLLM │
│ │ │
│ vLLM │ │
│ (inference engine) ────────────┘ │
│ │ │
│ ├─> Engine Core (API + scheduler) │
│ ├─> GPU Model Runner (per rank) │
│ └─> NCCL Communicator (cross-node) │
│ │
└──────────────────────────────────────────────────────────┘
ทำไม Ray?
• vLLM --distributed-executor-backend=ray รองรับ multi-node
• Ray จัดการ placement + fault isolation + actor model
• Ray Dashboard (port 8265) ดู GPU utilization ได้
ทำไม Docker?
• NGC containers มี CUDA + cuDNN + NCCL + vLLM pre-built
• Isolate จาก host OS (ไม่ติดกับ Python version)
• nvidia-container-toolkit forward GPU devices
1.4 Why Swap Roles บน Cluster B
หลังเจอ Ray GCS segfault เฉพาะ AI BOX 3 (จะอธิบายใน Bug #9) เราตัดสินใจ:
┌─────────────────────────────────────────────────────────┐
│ Decision: Swap Roles for Cluster B │
│ │
│ ❌ Original plan: │
│ AI BOX 3 (AI-BOX-3) = HEAD │
│ AI BOX 4 (AI-BOX-4) = WORKER │
│ → Ray GCS crash every start │
│ │
│ ✓ New plan: │
│ AI BOX 4 (AI-BOX-4) = HEAD (Ray master) │
│ AI BOX 3 (AI-BOX-3) = WORKER │
│ → Works perfectly! │
│ │
│ Trade-offs: │
│ + ใช้ได้ทันที ไม่ต้องรอ Ray upstream fix │
│ + Performance เท่าเดิม (GPU ทั้ง 2 โหนดทำงานเท่ากัน) │
│ - ต้อง rsync scripts, setup new systemd service │
│ - Client ต้องเปลี่ยน endpoint จาก :14 → :17 (cluster B) │
└─────────────────────────────────────────────────────────┘
2. Network Topology + Data Flow
2.1 Physical Network Layout
CYN AI CLUSTER NETWORK
═══════════════════════════════════════════
┌────────────────────────────────────────────────────┐
│ Management Network (10 GbE) │
│ mgmt network — SSH, Ray dashboard, vLLM REST API │
└─┬──────────┬──────────┬──────────┬────────────────┘
│AI BOX 1 │AI BOX 2 │AI BOX 3 │AI BOX 4
│ │ │ │
┌─┴───┐ ┌─┴───┐ ┌─┴───┐ ┌─┴───┐
│AIB1 │ │AIB2 │ │AIB3 │ │AIB4 │
│HEAD │ │WKR │ │WKR │ │HEAD │
│GB10 │ │GB10 │ │GB10 │ │GB10 │
└─┬───┘ └─┬───┘ └─┬───┘ └─┬───┘
│ │ │ │
│ 200 Gbps │ │ 200 Gbps │
│ RoCE │ │ RoCE │
│ DAC │ │ DAC │
│ cable │ │ cable │
│internal │internal │internal │internal
│RoCE-1 │RoCE-2 │RoCE-3 │RoCE-4
└──────────┘ └──────────┘
Cluster A Cluster B
(NCCL data plane) (NCCL data plane)
Management: 10 GbE (slow, shared, for SSH + API + control)
Data: 200 Gbps RoCE direct-attach (fast, isolated, for NCCL)
2.2 Data Flow เมื่อ Inference Request เข้ามา
Client Head (AI BOX 1) Worker (AI BOX 2)
│ │ │
│ POST /v1/chat/completions │
│────────────────────────>│ │
│ │ │
│ [API Server] │
│ │ │
│ [Tokenizer] │
│ prompt_tokens = 22 │
│ │ │
│ [Scheduler] │
│ batch request │
│ │ │
│ [Engine Core] │
│ │ │
│ [Ray Executor] │
│ │────Ray RPC────> │
│ │ [Worker actor]
│ │ │
│ │ [GPU Model Runner]
│ │ │
│ GPU 0 (rank 0) GPU 0 (rank 1)
│ Layer weights Layer weights
│ [0..30, shard 0] [0..30, shard 1]
│ │ │
│ │ ╭─── all-reduce ────╮ │
│ │ │ │ │
│ │<┤ NCCL over RoCE ├> │
│ │ │ 200 Gbps direct │ │
│ │ │ │ │
│ │ ╰───── all-gather ──╯ │
│ │ │
│ [Combined output] │
│ │ │
│ [Sampler] │
│ next_token = "4" │
│ │ │
│ ┌─── streaming ────────┤ │
│ │ SSE: "4" │ │
│ │ SSE: "\n" │ │
│ │ ... │ │
│ │ SSE: [DONE] │ │
│ └──────────────────────│ │
│ │ │
│<────────────────────────│ │
│ HTTP 200 OK │ │
Critical observations:
- Request เข้า head node (AI BOX 1) เสมอ — worker (AI BOX 2) ไม่มี API server
- ทุก transformer layer ต้องทำ all-reduce ข้าม 2 nodes → RoCE เป็น bottleneck สำคัญ
- KV cache อยู่บน GPU ทั้ง 2 nodes (shard ครึ่งๆ) — ต้อง sync ผ่าน NCCL
- Output tokens generate บน head ก่อน stream กลับ client
2.3 Memory Layout ของ TP=2
Node AI BOX 1 (Head) Node AI BOX 2 (Worker)
┌─────────────────────┐ ┌─────────────────────┐
│ OS + kernel │ ~8 GB │ OS + kernel │ ~8 GB
├─────────────────────┤ ├─────────────────────┤
│ │ │ │
│ Model Weights │ │ Model Weights │
│ Shard 0 │ ~61 GB │ Shard 1 │ ~61 GB
│ (FP8 Qwen3.5) │ │ (FP8 Qwen3.5) │
│ │ │ │
├─────────────────────┤ ├─────────────────────┤
│ KV Cache │ ~8 GB │ KV Cache │ ~8 GB
├─────────────────────┤ ├─────────────────────┤
│ Activations │ ~2 GB │ Activations │ ~2 GB
├─────────────────────┤ ├─────────────────────┤
│ CUDA graphs │ ~1 GB │ CUDA graphs │ ~1 GB
├─────────────────────┤ ├─────────────────────┤
│ NCCL buffers │ ~1 GB │ NCCL buffers │ ~1 GB
├─────────────────────┤ ├─────────────────────┤
│ Free (headroom) │ ~47 GB │ Free (headroom) │ ~47 GB
└─────────────────────┘ └─────────────────────┘
Total 128 GB Total 128 GB
Used ~72 GB (56%) Used ~72 GB (56%)
3. NCCL Communication Pattern บน DGX Spark
3.1 Collective Operations ที่ vLLM ใช้
- all-reduce — ใช้ sync activations หลัง attention/FFN ทุก layer (ปริมาณใหญ่สุด)
- all-gather — ใช้ gather expert outputs ใน MoE
- broadcast — ใช้ sync sampled token จาก head → worker
- reduce-scatter — ใช้ใน TP gradient (ไม่ใช้ในกรณี inference-only แต่มีใน warmup)
3.2 NCCL Transport Selection Flow
┌─────────────────────────────────────────────────────────┐
│ NCCL INITIALIZATION (happens in ncclCommInitRank) │
│ │
│ 1. Detect local NICs │
│ └─ ibdev2netdev → (Up) │
│ │
│ 2. Auto-select GID (if NCCL_IB_GID_INDEX not set) │
│ └─ NCCL_IB_ADDR_FAMILY=4 → prefer IPv4 │
│ └─ Scan /sys/class/infiniband//ports/1/gids/* │
│ └─ Find entry with YYYY:XX → IPv4 <internal-net> │
│ │
│ 3. Build channels │
│ └─ 8 coll channels (ring) │
│ └─ 8 p2p channels (point-to-point) │
│ └─ 1 p2p channel per peer │
│ │
│ 4. Create QPs (Queue Pairs) │
│ └─ Sender QP (local → remote) │
│ └─ Receiver QP (remote → local) │
│ └─ Set MTU 5 (4096 bytes) │
│ │
│ 5. Transition QP state │
│ └─ RESET → INIT → RTR (Ready to Receive) → RTS │
│ └─ ibv_modify_qp ที่นี่ → fail ถ้า GID ไม่ match │
│ │
│ 6. Exchange ECE (Enhanced Connection Establishment) │
│ └─ Vendor-specific: Mellanox 0x15b3 │
│ │
│ 7. Init COMPLETE │
│ └─ Init timings printed │
└─────────────────────────────────────────────────────────┘
3.3 ทำไม GID Selection ถึงสำคัญ
GID = Global Identifier ของ RoCE port ใช้สำหรับ address routing ระดับ RDMA — แต่ละ port มี GID หลายตัวตาม index:
GID Types บน RoCE port:
Type 1: IB/RoCE v1 — ใช้ MAC address (link-layer)
Type 2: RoCE v2 — ใช้ IPv4/IPv6 (network-layer, routable)
✓ Preferred สำหรับ cross-node
GID Sub-types:
fe80::... = link-local (MAC-based, ใช้ได้เฉพาะ L2)
::ffff:a.b.c.d = IPv4-mapped IPv6 (routable through L3)
ถ้า set NCCL_IB_GID_INDEX=3 แต่ index 3 บน node A เป็น IPv4 แต่บน node B เป็น link-local → ibv_modify_qp() จะ fail เพราะ address type ไม่ match
4. Hardware Spec Breakdown — GB10 Superchip
4.1 NVIDIA GB10 Details
| Component | Specification | ความหมาย |
|---|---|---|
| CPU | 20-core ARM Neoverse | 10× Cortex-X925 (performance) + 10× Cortex-A725 (efficient) |
| GPU Architecture | Blackwell SM 12.1 (sm_121a) | Compute capability ใหม่ vLLM รองรับจาก 25.11 |
| CUDA Cores | 6,144 | ประมาณ RTX 5070 ระดับ consumer |
| Tensor Cores | 5th-gen | รองรับ FP4/FP6/FP8/INT8/BF16/FP16/TF32 |
| RT Cores | 4th-gen | สำหรับ ray tracing (ไม่ได้ใช้ใน AI inference) |
| Memory | 128 GB LPDDR5x unified | CPU และ GPU แชร์หน่วยความจำเดียวกัน |
| Memory Bandwidth | 273 GB/s | 256-bit bus @ 4266 MHz, 16 channels |
| NVLink-C2C | Not applicable | GB10 เป็น single chip (ไม่ใช่ Grace Hopper) |
| Storage | 1 TB NVMe M.2 SED | Self-encrypting drive |
| Network | ConnectX-7 2× QSFP 200 Gbps + 10 GbE + WiFi 7 | 4 RoCE ports รวม |
| Power | 240W external PSU | SoC TDP 140W + 100W headroom |
4.2 Unified Memory — Gotcha สำคัญ
DGX Spark แตกต่างจาก GPU ทั่วไปที่มี VRAM แยก — CPU และ GPU แชร์ DRAM เดียวกัน ผลกระทบ:
nvidia-smiรายงานMemory-Usage: Not Supported(documented NVIDIA known issue)- ใช้
free -gดู GPU memory (เพราะเป็น memory เดียวกันกับระบบ) - ไม่มี PCIe bottleneck เหมือน GPU discrete — CPU-GPU transfer เร็วมาก
- แต่ ไม่มี GPU Direct RDMA (GDR) เพราะไม่มี GPU framebuffer แยก → NCCL ต้องใช้ CPU-mediated path สำหรับ cross-node
$ nvidia-smi
+---------------------------------------------------------+
| GPU Name Memory-Usage GPU-Util |
|=========================================================|
| 0 NVIDIA GB10 Not Supported N/A |
+---------------------------------------------------------+
$ free -g
total used free shared buff/cache available
Mem: 121 72 15 0 33 49
# Used 72 GB = nì̀ = GPU memory ของ Qwen3.5-122B shard 0
4.3 ConnectX-7 Interface Layout
แต่ละ DGX Spark มี ConnectX-7 1 ตัว ที่ผ่านเป็น 4 RoCE interfaces:
$ ibdev2netdev
port 1 ==> (Up/Down) ← QSFP Port 1 (left)
port 1 ==> (Up/Down) ← QSFP Port 1 (right)
port 1 ==> (Up/Down) ← QSFP Port 2 (left)
port 1 ==> (Up/Down) ← QSFP Port 2 (right)
ทำไมมี 4 ตัว?
ConnectX-7 รองรับ 2 physical QSFP ports × 2 virtual partitions
แต่ DAC cable เชื่อม 1 สาย → activated แค่ 1-2 จาก 4 ตัว
อีกตัวเป็น Down เพราะไม่มี cable
5. Pre-Flight Checklist — ก่อน Deploy ต้องเช็คอะไร
5.1 Hardware & OS Verification
╔══════════════════════════════════════════════════════════╗
║ PRE-FLIGHT CHECKLIST (run บน**ทุก node**) ║
╚══════════════════════════════════════════════════════════╝
[ 1 ] OS เป็น DGX OS (Ubuntu 24.04 + kernel -nvidia)
$ hostnamectl | grep -E 'Operating|Kernel'
Expected: Ubuntu 24.04 LTS, Linux 6.x-nvidia (DGX OS kernel)
$ ls /etc/nvidia/cx7-hotplug-enabled
Expected: file exists
[ 2 ] GPU detected by nvidia-smi
$ nvidia-smi --query-gpu=name,driver_version --format=csv
Expected: NVIDIA GB10, 580.x (recent NVIDIA driver) (or newer)
[ 3 ] RoCE interfaces Up and linked
$ ibdev2netdev
Expected: >=1 interface (Up)
$ rdma link show
Expected: state ACTIVE, physical_state LINK_UP
[ 4 ] RoCE IP addresses assigned
$ ip -o addr show | grep '<your-roce-subnet>'
Expected: inet <roce-ip>/24 on <interface>
[ 5 ] Ping to peer ผ่าน RoCE interface
$ ping -c 3 -I
Expected: 0% packet loss, <1 ms RTT
[ 6 ] Docker running with NVIDIA runtime
$ docker info | grep Runtimes
Expected: "nvidia" in the list
ถ้าไม่มี:
$ sudo nvidia-ctk runtime configure --runtime=docker
$ sudo systemctl restart docker
[ 7 ] Test GPU inside container
$ docker run --rm --gpus all vllm-node:latest \
python3 -c 'import torch; print(torch.cuda.is_available())'
Expected: True
[ 8 ] Passwordless SSH from HEAD to WORKER (via RoCE IP)
$ ssh -o BatchMode=yes @ 'hostname'
Expected: worker hostname (no password prompt)
[ 9 ] HuggingFace cache มี model แล้ว
$ ls ~/.cache/huggingface/hub/models--Qwen--Qwen3.5-122B-A10B-FP8/
Expected: snapshots/, blobs/
[ 10 ] Disk space sufficient
$ df -h <home-dir>/.cache/
Expected: >200 GB free (model ~240 GB + overhead)
5.2 Software Dependencies Check (ภายใน container)
# Inspect image version ก่อน run
$ docker run --rm --gpus all vllm-node:latest bash -c '
python3 -c "
import torch, vllm, ray
print(\"torch :\", torch.__version__)
print(\"cuda :\", torch.version.cuda)
print(\"vllm :\", vllm.__version__)
print(\"ray :\", ray.__version__)
print(\"cuda available:\", torch.cuda.is_available())
print(\"gpu count:\", torch.cuda.device_count())
"'
Expected output:
torch : 2.12.x (recent dev build, CUDA 13)
cuda : 13.0
vllm : 0.19.x (recent dev build)
ray : 2.54.1
cuda available: True
gpu count: 1
6. Step-by-Step Deployment Guide
6.1 Overview
┌─────────────────────────────────────────────────────────┐
│ DEPLOYMENT WORKFLOW (per cluster) │
│ │
│ Phase 1: Environment Prep (per node) │
│ ├─ Configure NVIDIA runtime │
│ ├─ Pull Docker image │
│ └─ Verify RoCE + GPU │
│ │
│ Phase 2: Code Setup (head node) │
│ ├─ Clone vllm-dgx-spark repo │
│ ├─ Apply 12 patches (scripts) │
│ ├─ Create config.local.env │
│ └─ Transfer image to worker (if needed) │
│ │
│ Phase 3: Systemd Service (head node) │
│ ├─ Create vllm-cluster.service unit │
│ ├─ Enable + start service │
│ └─ Monitor /var/log/vllm-cluster.log │
│ │
│ Phase 4: Verify & Benchmark │
│ ├─ Check health endpoint │
│ ├─ Test /v1/models │
│ ├─ Test /v1/chat/completions │
│ └─ Measure latency/throughput │
└─────────────────────────────────────────────────────────┘
6.2 Phase 1 — Environment Prep (run on EVERY node)
# 1. Configure nvidia runtime
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker
# Verify
docker info 2>/dev/null | grep -i 'Runtimes'
# Expected: Runtimes: nvidia runc io.containerd.runc.v2
# 2. Pull vLLM image
docker pull nvcr.io/nvidia/vllm:<version>-py3
# 3. Tag locally (convention ของ mark-ramsey-ri repo)
docker tag nvcr.io/nvidia/vllm:<version>-py3 vllm-node:latest
# 4. Smoke-test GPU access
docker run --rm --gpus all vllm-node:latest \
python3 -c 'import torch; assert torch.cuda.is_available(); print("✓ GPU OK")'
6.3 Phase 2 — Code Setup (run on HEAD only)
# 1. Clone upstream repo
cd ~
git clone https://github.com/mark-ramsey-ri/vllm-dgx-spark.git
cd vllm-dgx-spark
# 2. Create config.local.env
cat > config.local.env <<'EOF'
# CYN Cluster A: AI BOX 1 head + AI BOX 2 worker — TP=2
WORKER_HOST=AI BOX 2 (RoCE)
WORKER_IB_IP=AI BOX 2 (RoCE)
WORKER_USER=aiboss
HEAD_IP=AI BOX 1 (RoCE)
MODEL=Qwen/Qwen3.5-122B-A10B-FP8
TENSOR_PARALLEL=2
GPU_MEMORY_UTIL=0.90
MAX_MODEL_LEN=32768
VLLM_IMAGE=vllm-node:latest
HEAD_CONTAINER_NAME=ray-head
WORKER_CONTAINER_NAME=ray-worker
SHM_SIZE=16g
HF_CACHE=<home-dir>/.cache/huggingface
WORKER_HF_CACHE=<home-dir>/.cache/huggingface
VLLM_PORT=8000
RAY_DASHBOARD_PORT=8265
RAY_PORT=6380
ENABLE_EXPERT_PARALLEL=false
TRUST_REMOTE_CODE=true
LOAD_FORMAT=safetensors
EXTRA_ARGS="--max-num-batched-tokens 4096 --enforce-eager --enable-prefix-caching --enable-auto-tool-choice --tool-call-parser hermes"
# NCCL - critical for cross-node TP=2
NCCL_DEBUG=INFO
NCCL_IB_HCA=
NCCL_IB_GID_INDEX=3
NCCL_CUMEM_ENABLE=0
NCCL_NET_GDR_LEVEL=0
NCCL_P2P_DISABLE=1
VLLM_WORKER_MULTIPROC_METHOD=spawn
VLLM_RPC_TIMEOUT=600
EOF
# 3. Apply 12 patches (ดูรายละเอียดใน section 7)
# - Bug #2: docker pull false-fail
# - Bug #3: WORKER_IP into WORKER_ENV
# - Bug #4: remove --swap-space
# - Bug #6: HF_HUB_OFFLINE in docker env
# - Bug #10: IMAGE in WORKER_ENV
# - Bug #11+12: same as #6 for cluster B
# (รายละเอียด sed commands ด้านล่างแต่ละ bug)
# 4. Ensure scripts executable
chmod +x start_cluster.sh start_worker_vllm.sh stop_cluster.sh
6.4 Phase 3 — Systemd Service
sudo tee /etc/systemd/system/vllm-cluster.service <<'EOF'
[Unit]
Description=CYN vLLM Cluster (Head + Worker, TP=2)
After=network-online.target docker.service
Wants=network-online.target docker.service
[Service]
Type=simple
User=aiboss
Group=aiboss
Environment=HOME=<home-dir>
WorkingDirectory=<home-dir>/vllm-dgx-spark
# รอ network + Docker daemon พร้อม (30 วินาที buffer)
ExecStartPre=/bin/sleep 30
# Start cluster — script จะ SSH ไป worker และ start ทุก step
ExecStart=/bin/bash <home-dir>/vllm-dgx-spark/start_cluster.sh
# อย่า restart อัตโนมัติ — ถ้า crash ต้อง debug manually
Restart=no
# Graceful stop
ExecStop=/bin/bash <home-dir>/vllm-dgx-spark/stop_cluster.sh
# Allow long startup (model load + warmup)
TimeoutStartSec=1800
TimeoutStopSec=300
# Redirect logs
StandardOutput=append:/var/log/vllm-cluster.log
StandardError=append:/var/log/vllm-cluster.log
[Install]
WantedBy=multi-user.target
EOF
sudo touch /var/log/vllm-cluster.log
sudo chown aiboss:aiboss /var/log/vllm-cluster.log
sudo systemctl daemon-reload
sudo systemctl enable vllm-cluster.service
sudo systemctl start vllm-cluster.service
# Monitor
tail -f /var/log/vllm-cluster.log
# หรือ
sudo journalctl -u vllm-cluster -f
6.5 Phase 4 — Verify & Benchmark
# 1. Health check
curl -s http://AI BOX 1:8000/health
# Expected: (empty body, HTTP 200)
# 2. Models endpoint
curl -s http://AI BOX 1:8000/v1/models | python3 -m json.tool
# Expected:
# {
# "object": "list",
# "data": [{
# "id": "Qwen/Qwen3.5-122B-A10B-FP8",
# "max_model_len": 32768,
# ...
# }]
# }
# 3. Generate test
curl -s -X POST http://AI BOX 1:8000/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{
"model": "Qwen/Qwen3.5-122B-A10B-FP8",
"messages": [{"role":"user","content":"/no_think ทักทายเป็นภาษาไทย"}],
"max_tokens": 50,
"temperature": 0.3
}' | python3 -m json.tool
# 4. Check Ray cluster status
docker exec ray-head ray status --address=127.0.0.1:6380
# Expected:
# Active:
# 1 node_... (head)
# 1 node_... (worker)
# Resources:
# 2.0/2.0 GPU
7. Bug #1–12 — Real-World Issues + Fixes
Bug #1: Docker NVIDIA Runtime ไม่ได้ Configure
อาการ: Container รันปกติ แต่ PyTorch cuda = False, Ray เห็น 0.0/1.0 GPU
Root cause: DGX OS ติดตั้ง nvidia-container-toolkit มาให้ แต่ /etc/docker/daemon.json ไม่มี ทำให้ Docker daemon ไม่รู้จัก nvidia runtime
Diagnostic:
$ docker info | grep Runtimes
Runtimes: io.containerd.runc.v2 runc # ← no "nvidia"
$ cat /etc/docker/daemon.json 2>&1
cat: /etc/docker/daemon.json: No such file
Fix:
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker
# Verify
docker info | grep -i nvidia
⚠️ Gotcha: Container เก่าต้อง docker rm -f แล้วสร้างใหม่ — restart docker ไม่ migrate container runtime
Bug #2: Shell Script Pull-Image False-Fail
อาการ: Worker log แสดง Status: Image is up to date แล้วตามด้วย ERROR: Failed to pull
Root cause: Logic bug ใน start_worker_vllm.sh:
# Buggy code:
if ! docker pull "${IMAGE}" 2>/dev/null || echo "Using local image"; then
error "Failed to pull image ${IMAGE}"
fi
# Truth table:
# docker pull success → ! → false → || echo (always true) → if true → ERROR!
# docker pull fail → ! → true → || (skip) → if true → ERROR (correct)
Fix: แยก statement และใช้ docker image inspect verify:
python3 <<'PYEOF'
path = '<home-dir>/vllm-dgx-spark/start_worker_vllm.sh'
with open(path) as f: c = f.read()
old = '''if ! docker pull "${IMAGE}" 2>/dev/null || echo "Using local image"; then
error "Failed to pull image ${IMAGE}"
fi'''
new = '''docker pull "${IMAGE}" 2>/dev/null || echo " Using local image: ${IMAGE}"
if ! docker image inspect "${IMAGE}" >/dev/null 2>&1; then
error "Image ${IMAGE} not available locally and pull failed"
fi'''
c = c.replace(old, new)
with open(path, 'w') as f: f.write(c)
PYEOF
Bug #3: Worker Auto-Detect RoCE Interface ผิด Subnet
อาการ: Worker register ด้วย IP ผิด subnet (AI BOX 2 (RoCE secondary) แทน AI BOX 2 (RoCE)) → Ray cluster ไม่ form, worker alive: False
Root cause: Function discover_ib_interface() ใน start_worker_vllm.sh เลือก (Up) interface ตัวแรกจาก ibdev2netdev — แต่บางโหนดมี 2+ interfaces Up และ order ไม่ deterministic
Fix: ใน start_cluster.sh prepend WORKER_IP=${WORKER_IB_IP} เข้า WORKER_ENV เพื่อ override auto-detect:
sed -i 's|WORKER_ENV="HEAD_IP=${HEAD_IP}|WORKER_ENV="WORKER_IP=${WORKER_IB_IP} HEAD_IP=${HEAD_IP}|' \
~/vllm-dgx-spark/start_cluster.sh
Bug #4: --swap-space ถูกลบออกจาก vLLM 0.19
อาการ: vllm: error: unrecognized arguments: --swap-space 16
Root cause: vLLM 0.19.1rc1 ลบ --swap-space argument ออก (vLLM จัดการ swap อัตโนมัติ) แต่ start_cluster.sh ยัง append อาร์กิวเมนต์นี้อยู่
Fix:
sed -i 's|VLLM_ARGS="${VLLM_ARGS} --swap-space ${SWAP_SPACE}"|# [FIX] removed --swap-space (not supported in vLLM 0.19+)|' \
~/vllm-dgx-spark/start_cluster.sh
Bug #5: NCCL_IB_GID_INDEX Mismatch ระหว่างโหนด
อาการ:
NCCL WARN Call to ibv_modify_qp failed with 22 Invalid argument,
on dev :1, curr state INIT, next state RTR,
local GID index 5, local GID ::ffff:AI BOX 1 (RoCE),
remote GID :: ← ว่าง!
Root cause: GID table ระหว่าง 2 nodes ไม่เหมือนกัน แม้ hardware เดียวกัน — IPv4 RoCE v2 อาจอยู่ที่ index ต่างกัน
Diagnostic:
# Run บนทั้ง 2 nodes และเปรียบเทียบ
for i in 0 1 2 3 4 5 6; do
gid=$(cat /sys/class/infiniband//ports/1/gids/$i 2>/dev/null)
typ=$(cat /sys/class/infiniband//ports/1/gid_attrs/types/$i 2>/dev/null)
[ -n "$gid" ] && echo " [$i] $gid ($typ)"
done
# Cluster A result:
# AI BOX 1 index 5 = 0000:ffff:YYYY:0001 (RoCE v2, IPv4 AI BOX 1 (RoCE))
# AI BOX 2 index 5 = (empty!) ← ไม่ตรง
# AI BOX 2 index 6 = 0000:ffff:YYYY:0002 (RoCE v2, IPv4 AI BOX 2 (RoCE))
Fix (Cluster A): ใช้ index 3 (link-local RoCE v2 ที่มีทั้ง 2 nodes):
# config.local.env
NCCL_IB_GID_INDEX=3
# ใน start_cluster.sh และ start_worker_vllm.sh ต้อง export เข้า container
# (ดู section ENV_ARGS patch)
Fix (Cluster B): ใช้ NCCL_IB_ADDR_FAMILY=4 แทน (ดู Bug #12)
Bug #6: HF_HUB_OFFLINE ไม่ Inject เข้า Container Env
อาการ: vLLM log หยุดที่ "Using FlashAttention version 2" แล้วเงียบ 15+ นาที Memory ค้างที่ 72 GB
Diagnostic ด้วย py-spy:
# ติดตั้ง py-spy บน host (ถ้ายังไม่มี)
pip install --user py-spy
# หา PID ของ RayWorker ในกระบวนการ namespace ของ host
pgrep -f 'ray::RayWorker'
# Dump Python stack trace
sudo ~/.local/bin/py-spy dump --pid <pid>
# Expected (ถ้า normal):
# compile_cuda_kernels
# initialize_distributed_engine
#
# แต่ถ้าเจอ:
# xet_get (huggingface_hub/file_download.py)
# hf_hub_download
# _download_to_tmp_and_move
# snapshot_download
# download_weights_from_hf (vllm/model_executor/model_loader/weight_utils.py)
# → vLLM กำลัง re-download weights!
Root cause: huggingface_hub.snapshot_download() re-validate blobs แม้ cache ครบ ต้อง force offline mode
⚠️ Gotcha: Script เดิมมี comment:
$ grep HF_HUB_OFFLINE ~/vllm-dgx-spark/start_cluster.sh
904: # Note: We do NOT set HF_HUB_OFFLINE=1 here because workers need to resolve the model name
grep -c ได้ 1 — แต่เป็น comment ไม่ใช่ env var!
Fix: Inject -e HF_HUB_OFFLINE=1 -e TRANSFORMERS_OFFLINE=1 ลงใน docker run args:
# Head script
sed -i '/-e HF_HOME=\/root\/.cache\/huggingface$/a\
# [FIX] Force offline — prevent vllm re-download cached weights\
-e HF_HUB_OFFLINE=1\
-e TRANSFORMERS_OFFLINE=1' ~/vllm-dgx-spark/start_cluster.sh
# Worker script (ระวัง trailing backslash)
sed -i '/-e HF_HOME=\/root\/.cache\/huggingface \\$/a\
-e HF_HUB_OFFLINE=1 \\\
-e TRANSFORMERS_OFFLINE=1 \\' ~/vllm-dgx-spark/start_worker_vllm.sh
# Verify ด้วย docker exec env
docker exec ray-head env | grep -E 'HF_|TRANSFORMERS'
# Expected:
# HF_HOME=/root/.cache/huggingface
# HF_HUB_OFFLINE=1
# TRANSFORMERS_OFFLINE=1
Bug #7: NCCL_ALGO=Tree ไม่รองรับ int8 AllGather
อาการ:
Error : no algorithm/protocol available for function AllGather
with datatype ncclInt8. NCCL_ALGO was set to Tree.
Root cause: คำแนะนำทั่วไปคือ force NCCL_ALGO=Tree เพื่อหลีกเลี่ยง Ring latency แต่ Tree algorithm ไม่รองรับ AllGather กับ int8 datatype ที่ vLLM ใช้สำหรับ FP8 MoE
Fix: ลบ NCCL_ALGO ออกหมด ปล่อยใช้ default (Ring):
sed -i '/NCCL_ALGO=/d' ~/vllm-dgx-spark/config.local.env
sed -i '/NCCL_ALGO/d' ~/vllm-dgx-spark/start_cluster.sh
sed -i '/NCCL_ALGO/d' ~/vllm-dgx-spark/start_worker_vllm.sh
Bug #8: Qwen3.5 Mamba Cache Block Size Assertion
อาการ:
AssertionError: In Mamba cache align mode,
block_size (2096) must be <= max_num_batched_tokens (2048).
Root cause: Qwen3.5 เป็น hybrid Mamba-Transformer architecture Mamba cache block = 2096 tokens แต่ default max_num_batched_tokens = 2048 (น้อยกว่า 1 block)
Fix: เพิ่ม argument:
# config.local.env
EXTRA_ARGS="--max-num-batched-tokens 4096 --enforce-eager --enable-prefix-caching --enable-auto-tool-choice --tool-call-parser hermes"
Bug #9: Ray GCS Segfault — Hardware-Specific
อาการ (Cluster B เฉพาะเมื่อ AI BOX 3 เป็น head):
# GCS server เป็น zombie (defunct)
$ docker exec ray-head ps aux | grep gcs_server
root 179 0.1 0.0 0 0 ? Z [gcs_server] <defunct>
# Log error
Check failed: !msg_batch->messages().empty()
ray_syncer_bidi_reactor_base.h:250
Root cause: Ray bug hardware-specific — โหนด AI-BOX-3 (AI BOX 3) trigger bug เมื่อเป็น head และ worker register Ray assert fail ตอน OnReadDone() เพราะได้ empty message batch
สิ่งที่ไม่ช่วย (ลองแล้วทั้งหมด): reboot, fresh containers, switch RoCE→Ethernet, Ray version check, NCCL env tuning
Fix — Swap Roles: โหนด AI BOX 4 (AI-BOX-4) ทำงานเป็น head ได้ปกติ ใช้ hardware ชุดเดียวกัน ให้สลับบทบาท:
# Step 1: Copy scripts AI BOX 3 → AI BOX 4
ssh <user>@<peer> \
"rsync -a ~/vllm-dgx-spark/ <user>@<peer> (RoCE):~/vllm-dgx-spark/"
# Step 2: Edit config on AI BOX 4
ssh <user>@<peer> "
sed -i 's|HEAD_IP=AI BOX 3 (RoCE)|HEAD_IP=AI BOX 4 (RoCE)|' ~/vllm-dgx-spark/config.local.env
sed -i 's|WORKER_HOST=AI BOX 4 (RoCE)|WORKER_HOST=AI BOX 3 (RoCE)|' ~/vllm-dgx-spark/config.local.env
sed -i 's|WORKER_IB_IP=AI BOX 4 (RoCE)|WORKER_IB_IP=AI BOX 3 (RoCE)|' ~/vllm-dgx-spark/config.local.env
"
# Step 3: Disable systemd on AI BOX 3, install + enable on AI BOX 4
ssh <user>@<peer> "sudo systemctl disable --now vllm-cluster"
ssh <user>@<peer> "<install vllm-cluster.service as per Phase 3>"
ssh <user>@<peer> "sudo systemctl enable --now vllm-cluster"
Bug #10: Worker Image Fallback เป็น Pristine NGC (vLLM 0.11.0)
อาการ:
# Worker log
✅ vLLM 0.11.0+582e4e37.nv25.11 available ← เก่า
✅ Ray 2.51.1 available ← เก่า
# Head log
✅ vLLM 0.19.x (recent dev build) ← ใหม่
✅ Ray 2.54.1 ← ใหม่
# Ray cluster ไม่ form:
Failed to connect to GCS at address AI BOX 4 (RoCE):6380 within 5 seconds.
Root cause: start_worker_vllm.sh default IMAGE="${IMAGE:-nvcr.io/nvidia/vllm:<version>-py3}" — ซึ่งคือ pristine NGC image (vLLM 0.11.0) แต่ head ใช้ vllm-node:latest (upgraded, 0.19.1) โดย start_cluster.sh ไม่ propagate IMAGE เข้า WORKER_ENV
Fix 1: Patch WORKER_ENV:
sed -i 's|WORKER_ENV="NCCL_IB_HCA|WORKER_ENV="IMAGE=${VLLM_IMAGE:-vllm-node:latest} NCCL_IB_HCA|' \
~/vllm-dgx-spark/start_cluster.sh
Fix 2: Transfer image ไป worker ผ่าน RoCE 200 Gbps (ถ้าไม่มี):
ssh <user>@<head> \
"docker save vllm-node:latest | ssh aiboss@<worker-roce-ip> 'docker load'"
# 19 GB ใช้เวลา ~1m47s
Bug #11: HF_HUB_OFFLINE Re-Apply บน Cluster B
อาการ: Symptoms เดียวกับ Bug #6 หลัง rsync scripts ข้าม node
Root cause: Scripts ที่ rsync มาเป็น version เดิมที่ยังมี comment หลอกตา ต้อง re-apply patch
บทเรียน: ทุกครั้งที่ copy scripts ให้ run verification:
# ถูก — verify container env จริง
docker exec ray-head env | grep HF_HUB_OFFLINE
# ผิด — grep file source อาจเจอแค่ comment
grep HF_HUB_OFFLINE ~/vllm-dgx-spark/start_cluster.sh
Bug #12: Link-Local IPv6 GID ไม่ Routable
อาการ:
ibv_modify_qp failed with 101 Network is unreachable
local GID fe80::xxxx:xxxx:xxxx:DDDD
remote GID fe80::xxxx:xxxx:xxxx:CCCC
Root cause: Link-local IPv6 (fe80::) ใช้ได้เฉพาะใน L2 broadcast domain kernel routing table ไม่รู้จัก route ระหว่าง link-local addresses แม้บน direct-attach cable
Fix: บังคับให้ NCCL ใช้ IPv4 auto-select:
# ลบ NCCL_IB_GID_INDEX
sed -i '/NCCL_IB_GID_INDEX/d' ~/vllm-dgx-spark/config.local.env
# เพิ่ม NCCL_IB_ADDR_FAMILY=4
echo 'NCCL_IB_ADDR_FAMILY=4' >> ~/vllm-dgx-spark/config.local.env
# Update scripts
sed -i 's|NCCL_IB_GID_INDEX[^"]*|NCCL_IB_ADDR_FAMILY="${NCCL_IB_ADDR_FAMILY:-4}"|' \
~/vllm-dgx-spark/start_cluster.sh \
~/vllm-dgx-spark/start_worker_vllm.sh
8. Verification & Benchmark
8.1 Post-Deploy Health Check
# 1. Cluster health
curl -sf http://AI BOX 1:8000/health && echo "✓ Cluster A up"
curl -sf http://AI BOX 4:8000/health && echo "✓ Cluster B up"
# 2. Ray node count
for host in AI BOX 1 AI BOX 4; do
ssh aiboss@$host "docker exec ray-head ray status --address=127.0.0.1:6380 2>&1 | grep 'GPU'"
done
# Expected: 2.0/2.0 GPU (ทั้งสอง clusters)
# 3. Model availability
curl -s http://AI BOX 1:8000/v1/models | python3 -c '
import sys,json
d=json.load(sys.stdin)
print(f"Model: {d[\"data\"][0][\"id\"]}")
print(f"Context: {d[\"data\"][0][\"max_model_len\"]}")
'
8.2 Latency Benchmark
# Single request, 30 tokens
time curl -s -X POST http://AI BOX 1:8000/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{
"model": "Qwen/Qwen3.5-122B-A10B-FP8",
"messages": [{"role":"user","content":"/no_think Hello, 2+2="}],
"max_tokens": 30,
"temperature": 0
}' > /dev/null
# Expected: ~2.1 seconds (including TTFT + 30 token generation)
8.3 Throughput Benchmark
# ใช้ vllm bench (อยู่ใน container)
docker exec ray-head bash -c '
vllm bench serve \
--backend openai \
--model Qwen/Qwen3.5-122B-A10B-FP8 \
--base-url http://localhost:8000 \
--dataset-name random \
--num-prompts 50 \
--request-rate 5
'
9. Production Troubleshooting Decision Tree
┌─────────────────────────────────────────────────────────┐
│ PROBLEM: vLLM cluster ไม่ขึ้น / hang / error │
└─────────────────────┬───────────────────────────────────┘
│
┌────────────┴────────────┐
│ curl /health ผ่านไหม? │
└────────────┬────────────┘
│
┌───────────┴───────────┐
Yes No
│ │
│ ├─> ระบบ up แต่ unstable?
│ │ └─> ดู journalctl, docker logs ray-head
│ │
▼ ▼
Cluster OK [ตรวจ Ray cluster status]
│
┌──────────────┴──────────────┐
│ ray status แสดง X/Y GPU ? │
└──────────────┬──────────────┘
│
┌────────────────┼────────────────┐
2/2 1/2 0/?
│ │ │
│ │ ▼
▼ ▼ [ray-head crash/Docker fail]
[vLLM load] [Worker ไม่ join] │
│ │ ├─ Bug #1: nvidia runtime
│ │ ├─ Bug #2: docker pull bug
│ │ └─ Bug #9: Ray GCS segfault
│ │ └─ Swap roles
│ │
│ ▼
│ [ดู worker_setup.log]
│ │
│ ┌───────┴───────┐
│ Image mismatch? Wrong subnet?
│ │ │
│ │ │
│ Bug #10: IMAGE Bug #3: WORKER_IP
│ propagation override
│
▼
[vLLM hang ที่ log line ใด?]
│
┌─────────────┼─────────────┬──────────────┐
│ │ │ │
"FlashAttention "swap-space "NCCL_ALGO "block_size
version 2" unrecognized" Tree" (2096)"
│ │ │ │
│ │ │ │
py-spy dump Bug #4: Bug #7: Bug #8:
RayWorker remove remove max-num-
│ --swap- NCCL_ALGO batched-
│ space tokens
▼
stack มี
"download_weights_from_hf"?
│
├─ Yes → Bug #6/#11: HF_HUB_OFFLINE not in env
└─ No → ดู NCCL log สำหรับ
"ibv_modify_qp failed"
│
┌────────┴────────┐
"Invalid arg" "Network unreachable"
remote GID :: local fe80::...
│ │
│ │
Bug #5: Bug #12:
GID_INDEX ADDR_FAMILY=4
mismatch
10. Final Configuration (Reference)
10.1 Cluster A config.local.env
# CYN Cluster A: AI BOX 1 (head) + AI BOX 2 (worker) — TP=2
WORKER_HOST=AI BOX 2 (RoCE)
WORKER_IB_IP=AI BOX 2 (RoCE)
WORKER_USER=aiboss
HEAD_IP=AI BOX 1 (RoCE)
MODEL=Qwen/Qwen3.5-122B-A10B-FP8
TENSOR_PARALLEL=2
GPU_MEMORY_UTIL=0.90
MAX_MODEL_LEN=32768
VLLM_IMAGE=vllm-node:latest
HEAD_CONTAINER_NAME=ray-head
WORKER_CONTAINER_NAME=ray-worker
SHM_SIZE=16g
HF_CACHE=<home-dir>/.cache/huggingface
WORKER_HF_CACHE=<home-dir>/.cache/huggingface
VLLM_PORT=8000
RAY_DASHBOARD_PORT=8265
RAY_PORT=6380
ENABLE_EXPERT_PARALLEL=false
TRUST_REMOTE_CODE=true
LOAD_FORMAT=safetensors
EXTRA_ARGS="--max-num-batched-tokens 4096 --enforce-eager --enable-prefix-caching --enable-auto-tool-choice --tool-call-parser hermes"
# NCCL — critical for cross-node TP=2
NCCL_DEBUG=INFO
NCCL_IB_HCA=
NCCL_IB_GID_INDEX=3
NCCL_CUMEM_ENABLE=0
NCCL_NET_GDR_LEVEL=0
NCCL_P2P_DISABLE=1
VLLM_WORKER_MULTIPROC_METHOD=spawn
VLLM_RPC_TIMEOUT=600
10.2 Cluster B config.local.env (ต่างจาก A 4 บรรทัด)
# ต่างจาก Cluster A:
WORKER_HOST=AI BOX 3 (RoCE) # swapped (AI BOX 4 is head)
WORKER_IB_IP=AI BOX 3 (RoCE)
HEAD_IP=AI BOX 4 (RoCE)
NCCL_IB_HCA= # AI BOX 4 has f0 Up (not f1 like AI BOX 1)
# ลบออก:
# NCCL_IB_GID_INDEX=3 ← ใช้ ADDR_FAMILY แทน
# เพิ่ม:
NCCL_IB_ADDR_FAMILY=4 # auto-select IPv4 GID
10.3 Performance Metrics (Production)
| Metric | Cluster A | Cluster B |
|---|---|---|
| Cold start (boot → ready) | ~12 min | ~12 min |
| Model weight load | 109.6 s (58.2 GiB) | 109.8 s |
| NCCL init | 0.36 s | 0.36 s |
| Engine warmup | 62.2 s | 62.5 s |
| KV cache size | 1,041,712 tokens | 1,041,000 tokens |
| Max concurrency | 90.41× @ 32k ctx | 90× @ 32k ctx |
| Memory per node | 72 / 128 GB (56%) | 72 / 128 GB |
| Single request latency (30 tok) | ~2.1 s | ~2.1 s |
| RoCE link speed | 200 Gbps | 200 Gbps |
11. บทเรียนสำคัญ 10 ข้อ
- DGX Spark unified memory ไม่มี VRAM แยก —
nvidia-smiรายงานMemory-Usage: Not Supportedเป็น documented known issue ใช้free -gแทน - ConnectX-7 มี 4 RoCE interfaces (2 ports × 2 partitions) แต่ละโหนดอาจมี interface Up ต่างกัน — เช็ค
ibdev2netdevก่อนทุกครั้งที่ setNCCL_IB_HCA - GID table layout ไม่ deterministic — โหนด twin spec เดียวกันอาจมี IPv4 RoCE v2 GID ที่ index ต่างกัน ใช้
NCCL_IB_ADDR_FAMILY=4ให้ NCCL auto-select ปลอดภัยที่สุด - Ray bugs อาจเป็น hardware-specific — โหนดนึง crash อีกโหนดไม่ crash แม้ใช้ image/kernel เดียวกัน swap roles เป็น workaround ที่เร็วที่สุด
- Comment ใน shell script หลอกตา grep — verify ด้วย
docker exec envจริง อย่า trust source file content - py-spy dump คือ debugging tool ที่มีค่ามากสุดสำหรับ vLLM "hang" — ดู Python stack trace ก่อนโทษ NCCL เพราะ 80% ของ "hang" จริงๆ คือ HuggingFace download, CUDA compile, หรือ libnccl init ที่ยังไม่จบ
- ติดตั้ง py-spy บน host ดีกว่า container — container มี ptrace restrict, host SSH ได้ง่ายกว่า หา host PID ด้วย
pgrep -f 'ray::RayWorker' - อย่า force
NCCL_ALGO=Treeสำหรับ vLLM FP8 MoE — ใช้ default (Ring) - HuggingFace Hub re-validates blobs แม้ cache ครบ ต้อง
HF_HUB_OFFLINE=1+TRANSFORMERS_OFFLINE=1inject เข้า docker run args ของทั้ง head และ worker - Qwen3.5 hybrid Mamba-Transformer ต้องการ
--max-num-batched-tokens ≥ 4096— Mamba cache block = 2096 เกิน default 2048
บทสรุป
การ deploy vLLM cross-node tensor parallel บน NVIDIA DGX Spark ไม่ใช่งานที่ทำตาม README แล้วจบ — มี edge case เฉพาะ hardware, firmware, kernel, Docker runtime, NCCL GID selection, Ray GCS bugs, shell script bugs ใน open-source repo ฯลฯ แต่ทุกปัญหามี root cause ที่หา diagnostic ได้ และโซลูชันที่ portable
CYN Communication ใช้เวลาหลายชั่วโมงแก้ 12 bugs เพื่อให้ทั้ง 2 clusters ใช้งานได้พร้อมกัน และเราได้สิ่งที่มีค่ามากกว่า vLLM cluster — เราได้ playbook ที่นำไปใช้กับ deployment ต่อ ๆ ไปได้
ทีม CYN Infrastructure ยินดีให้คำปรึกษา
ถ้าทีมคุณกำลังประเมินการสร้าง AI cluster ระดับ organization หรือกำลังประสบปัญหาคล้ายกัน CYN Communication รับ consulting เฉพาะทางด้าน:
- NVIDIA AI systems — DGX Spark, DGX Station, DGX H100/H200, DGX GB200, HGX
- LLM Serving Production — vLLM, TensorRT-LLM, llama.cpp, Ollama, LiteLLM
- Model tuning — Qwen, Llama, DeepSeek, Mistral, GPT-OSS, Phi
- Multi-node networking — NCCL + InfiniBand/RoCE/NVLink Switch tuning
- Cluster orchestration — Ray, Kubernetes, SLURM, MLflow
- Hardware integration — GPU servers, storage, networking, power & cooling
- 24×7 Support & Monitoring — Grafana, Prometheus, alert routing
ติดต่อทีมได้ที่ cyn.co.th หรือเยี่ยมชมโชว์รูมที่กรุงเทพฯ โทร โทรติดต่อได้ — CYN Communication เป็นตัวแทนจำหน่าย NVIDIA, HPE, Dell EMC, SuperMicro, Lenovo และ integration partner สำหรับโปรเจ็ค AI Infrastructure ระดับ enterprise
บทความนี้เขียนจากประสบการณ์จริงของทีม CYN Infrastructure Software stack: vLLM 0.19.1rc1.dev85, PyTorch 2.12.0.dev