Skip to content
Facebook-f Youtube Line

เปิดทำการ: จันทร์ - ศุกร์: 8:30น. - 17:30น.

  • เกี่ยวกับเรา
    • ข้อมูลบริษัท
    • ลูกค้าของเรา
    • ตัวแทนจำหน่าย
    • ใบรับรอง
  • ติดต่อเรา
  • บริการ
  • สินค้า
  • คอร์สอบรม
  • โซลูชัน
  • บทความข่าวสาร
  • ผลงาน

สั่งสินค้าออนไลน์

เมนู
  • บริการ
  • สินค้า
  • คอร์สอบรม
  • โซลูชัน
  • บทความข่าวสาร
  • ผลงาน
  • บริการ
  • สินค้า
  • คอร์สอบรม
  • โซลูชัน
  • บทความข่าวสาร
  • ผลงาน
แอดไลน์

สั่งสินค้าออนไลน์

สั่งสินค้าออนไลน์

แอดไลน์

คู่มือ Deploy vLLM บน NVIDIA DGX Spark Cluster — 12 Bugs ที่เราเจอจริง พร้อม Architecture Flow และ Troubleshooting Playbook

  • หน้าแรก
  • บทความข่าวสาร
  • คู่มือ Deploy vLLM บน NVIDIA DGX Spark Cluster — 12 Bugs ที่เราเจอจริง พร้อม Architecture Flow และ Troubleshooting Playbook
  • administrator
  • 11 April 2026
  • 19:41 น.
Facebook
LINE
Twitter
Pinterest

คู่มือฉบับสมบูรณ์: การออกแบบและ 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

📋 สารบัญ

  1. Architecture Design Flow — ทำไมต้องเลือกแบบนี้
  2. Network Topology + Data Flow
  3. NCCL Communication Pattern บน DGX Spark
  4. Hardware Spec Breakdown — GB10 Superchip
  5. Pre-Flight Checklist — ก่อน Deploy ต้องเช็คอะไร
  6. Step-by-Step Deployment Guide
  7. Bug #1–12 — Real-World Issues + Fixes
  8. Verification & Benchmark
  9. Production Troubleshooting Decision Tree
  10. Final Configuration (Reference)
  11. บทเรียนสำคัญ 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 ข้อ

  1. DGX Spark unified memory ไม่มี VRAM แยก — nvidia-smi รายงาน Memory-Usage: Not Supported เป็น documented known issue ใช้ free -g แทน
  2. ConnectX-7 มี 4 RoCE interfaces (2 ports × 2 partitions) แต่ละโหนดอาจมี interface Up ต่างกัน — เช็ค ibdev2netdev ก่อนทุกครั้งที่ set NCCL_IB_HCA
  3. GID table layout ไม่ deterministic — โหนด twin spec เดียวกันอาจมี IPv4 RoCE v2 GID ที่ index ต่างกัน ใช้ NCCL_IB_ADDR_FAMILY=4 ให้ NCCL auto-select ปลอดภัยที่สุด
  4. Ray bugs อาจเป็น hardware-specific — โหนดนึง crash อีกโหนดไม่ crash แม้ใช้ image/kernel เดียวกัน swap roles เป็น workaround ที่เร็วที่สุด
  5. Comment ใน shell script หลอกตา grep — verify ด้วย docker exec env จริง อย่า trust source file content
  6. py-spy dump คือ debugging tool ที่มีค่ามากสุดสำหรับ vLLM "hang" — ดู Python stack trace ก่อนโทษ NCCL เพราะ 80% ของ "hang" จริงๆ คือ HuggingFace download, CUDA compile, หรือ libnccl init ที่ยังไม่จบ
  7. ติดตั้ง py-spy บน host ดีกว่า container — container มี ptrace restrict, host SSH ได้ง่ายกว่า หา host PID ด้วย pgrep -f 'ray::RayWorker'
  8. อย่า force NCCL_ALGO=Tree สำหรับ vLLM FP8 MoE — ใช้ default (Ring)
  9. HuggingFace Hub re-validates blobs แม้ cache ครบ ต้อง HF_HUB_OFFLINE=1 + TRANSFORMERS_OFFLINE=1 inject เข้า docker run args ของทั้ง head และ worker
  10. 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, Ray 2.54.1, CUDA 13.0, NGC container <version>-py3, NVIDIA DGX Spark GB10 Superchip (SM121), Ubuntu 24.04 DGX OS kernel 6.x-nvidia (DGX OS kernel)

  • ป้ายกำกับ: AI, Cluster, DGX Spark, LLM, NCCL, NVIDIA, Qwen, RoCE, Tensor Parallel, vLLM
Prevย้อนกลับCase Study: HIKVISION DS-2CD1T23G2-LIU(4MM) in a Co-Working Space

CYN

CYN COMMUNICATION CO.,LTD. จัดจำหน่าย ให้เช่า และบริการออกแบบติดตั้ง ระบบและอุปกรณ์เน็ตเวิร์ค, บอร์ดแคส สตรีมมิ่ง, เซิร์ฟเวอร์ พร้อมให้บริการ Solution ต่างๆที่เกี่ยวข้อง

Facebook-f Youtube Line

บริการ

  • เซิร์ฟเวอร์
  • ถ่ายทอดสด
  • อินเตอร์เน็ต
  • เน็ตเวิร์ค
  • ประชุม & สัมนาออนไลน์
  • กล้องวงจรปิด

สินค้า

  • Peplink
  • Ruijie
  • Reyee
  • Engenius
  • Blackmagic
  • Synology

เกี่ยวกับเรา

  • เกี่ยวกับเรา
  • ติดต่อเรา
  • ร่วมงานกับเรา

ติดตามข่าวสาร

รับข่าวสารล่าสุดของเราส่งตรงไปยังกล่องจดหมายของคุณ

© 2022 cyn.co.th. All Rights Reserved.

  • ข้อกำหนดการใช้งาน
  • นโยบายความเป็นส่วนตัว