Skip to main content

Qwen3.6-35B บน DGX Spark: ทำไม v5 ถึงเป็น sweet spot

· 6 min read

บันทึก 26 มิถุนายน 2569 — ตี 2 — เรื่องของการ tune vLLM บน GB10

เมื่อวาน (2026-06-25) ผมรัน benchmark v2 ที่ gpu_mem=0.7 แล้ว RAM ขึ้นไป 99% เครื่องเกือบค้าง ส่วน v1 ที่ gpu_mem=0.5 ก็เจออาการเดียวกันตอนมี request stuck วันนี้เลยลอง tune ต่อจนมาถึง v5 ที่ gpu_mem=0.75 ซึ่งน่าจะเป็น sweet spot สำหรับ workload จริงที่ผมใช้

เรื่องมันเริ่มจาก RAM ขึ้น 99%​

ตอนแรกผมใช้ v1 (gpu_mem=0.5) มาหลายวันแล้ว ทุกอย่างดูปกติ จนกระทั่งมีแชทค้างไป 1-2 request GPU ขึ้นไป 90%+ prefix cache สะสมจน KV cache เต็ม RAM เลยขึ้นไป 99% (เหลือ 1.3 Gi) เครื่องเริ่มอืด

ผมเข้าใจว่า gpu_mem ต่ำคือต้นเหตุ ก็เลยเพิ่มเป็น v2 (gpu_mem=0.7) แต่พอ benchmark จริง ผลออกมา:

v1 (gpu_mem=0.5): C=6 = 169 tok/s
v2 (gpu_mem=0.7): C=6 = 202 tok/s (+20%)
v2 (gpu_mem=0.7): RAM = 99% (ยังเจอปัญหาเดิม)

ปรากฏว่า gpu_mem เพิ่มขึ้นช่วย throughput แต่ RAM ก็ยังเต็มเหมือนเดิม ผมเลยลอง v3 (gpu_mem=0.85) — ผล benchmark ออกมา 209 / 377 / 835 tok/s ซึ่งดูดีเกินจริง พอไล่ดูแล้วพบว่ามันเป็น anomaly (measurement bug) ลบออกจาก notes ทิ้ง

ทำไม RAM ถึงเต็มทั้งที่ gpu_mem ต่ำ​

ผมใช้เวลาสักพักกว่าจะเข้าใจ ตอนแรกคิดว่า gpu_mem=0.5 น่าจะเหลือ RAM เยอะ แต่พอ trace ดูดี ๆ พบว่า:

  • v1 (gpu_mem=0.5) เจอ RAM 99% เมื่อมี stuck request
  • v2 (gpu_mem=0.7) เจอ RAM 99% เหมือนกัน
  • v3 (gpu_mem=0.85) เจอ RAM 99% + ค้าง

สรุปคือ RAM 99% ไม่ได้เกี่ยวกับ gpu_mem โดยตรง แต่เป็นเพราะ stuck request ทำให้ prefix cache สะสมจน KV cache เต็ม ซึ่ง vLLM allocate KV cache ใน unified memory (DGX Spark GB10) ไม่ใช่ VRAM เพียงอย่างเดียว

Note: Unified memory quirk — DGX Spark GB10 ใช้ unified memory 121 Gi ระหว่าง CPU กับ GPU ไม่เหมือน discrete GPU ที่ VRAM แยกจาก RAM ดังนั้นถ้า KV cache โต มันกินพื้นที่จาก RAM ทั้งหมด ไม่ใช่แค่ VRAM ส่วนหนึ่ง

v5 Config — สิ่งที่เปลี่ยนจาก v1​

ผมเทียบ v1 กับ v5 ดูแล้วมี 4 จุดที่ปรับ:

Flagv1v5ความเหมือน/ต่าง
gpu_memory_utilization0.50.75+0.25
max_num_seqs(default 256)8match workload 1-6
max_num_batched_tokens3276832768เท่ากัน
max_model_len262144262144เท่ากัน
MTP K22เท่ากัน

แล้วก็มี 3 env var ที่เพิ่มเข้ามาใน v5:

VLLM_MARLIN_USE_ATOMIC_ADD=1 # NVFP4 GEMM kernel
VLLM_ALLOW_LONG_MAX_MODEL_LEN=1 # safety net สำหรับ 256K
VLLM_USE_FLASHINFER_SAMPLER=1 # sampler kernel เร็วกว่า default

Note: ทำไม max_num_seqs=8 — ผมดู workload จริงจาก LiteLLM logs แล้วเจอว่ามี concurrent 1-6 request ส่วนใหญ่ default 256 มัน oversized สำหรับเครื่องเดียวที่มี GPU แค่ 1 ตัว

Benchmark จริง — v1 vs v2 vs v4 vs v5​

ผมใช้ Python threads + curl POST, max_tokens=300, temperature=0.0 รัน 12 concurrent 10/12 successful ทุก version:

Parallelv1 (gpu=0.5)v2 (gpu=0.7)v4 (gpu=0.80)v5 (gpu=0.75)
158.455.657.059.2
3106.7111.8104.0105.0
6169.0202.3208.0207.0
12335.8339.2233.5235.2

Note: วิธีอ่านตาราง — เลขคือ tok/s aggregate ยิ่งสูงยิ่งเร็ว v5 ชนะ v1 ที่ C=6 (+22%) แต่แพ้ v1 ที่ C=12 (-30%) เพราะ max_num_seqs=8 cap ไว้

ผลที่น่าสนใจคือ v5 ≈ v4 ทุก concurrency แปลว่าการลด gpu_mem จาก 0.80 เป็น 0.75 ไม่ได้ทำให้ throughput ตก ผมเลยตัดสินใจใช้ v5 เพราะได้ RAM safety เพิ่ม (97 Gi vs 103 Gi)

ทำไม v5 เป็น sweet spot​

สรุปง่าย ๆ คือ:

  1. RAM safety — v5 = RAM 80% (97 Gi used, 23 Gi free, headroom ~14 GB) vs v1, v2 = RAM 99% ตอนมี stuck request
  2. Throughput ดีที่ C=6 — 207 tok/s ดีกว่า v1 (169) 22%
  3. FlashInfer sampler — env var ใหม่ คาดว่า +5-10% sampling throughput (ยังไม่ได้ A/B test)
  4. Match workload — max_num_seqs=8 ตรงกับ concurrent 1-6 ที่ใช้จริง

ทดลองใช้จริงก่อนตัดสินใจต่อ​

ผมยังไม่ได้ tune v6 เพราะตอนนี้ v5 ดูเป็นจุดสมดุลที่ดีแล้ว จะลองใช้จริงดูว่า:

  • chat 1-3 concurrent ใช้งานลื่นไหม
  • backtest 6 concurrent throughput OK ไหม
  • memory pressure ระหว่างใช้งาน 24/7 เป็นยังไง

ถ้าทุกอย่างโอเคก็ใช้ v5 ต่อ ถ้ามีปัญหาค่อยกลับมา tune v6

สิ่งที่ v5 ไม่ได้​

ผมต้องบอกตรง ๆ ว่า v5 มีข้อจำกัด:

  • C=12 throughput ลดลง 30% (235 vs 335) เพราะ max_num_seqs=8 cap ถ้ามี burst request 12+ พร้อมกัน มันจะคอขวด
  • KV cache เล็กกว่า v4 (5.23M vs 5.74M tokens) เพราะ gpu_mem ต่ำกว่า
  • ยังไม่ได้ A/B test FlashInfer sampler เทียบกับไม่ใส่ env var
  • ยังไม่ได้ทดสอบ chat agent workload จริง benchmark ใช้ synthetic prompt ไม่ใช่ conversation ยาว ๆ

Note: C=12 throughput ลดลง 30% คืออะไร — ถ้ามี 12 user ยิง request พร้อมกัน v5 จะตอบได้ 235 tok/s รวม vs v1 ทำได้ 335 tok/s แปลว่า per-user latency v5 ช้ากว่าตอน burst แต่ workload จริงของผมไม่เคยเกิน 6 concurrent

Setup จริงบน DGX Spark​

สำหรับคนที่สนใจลอง ผมใช้ recipe แบบนี้:

# Qwen3.6-35B-A3B-NVFP4-MTP-v5.yaml
gpu_memory_utilization: 0.75
max_num_batched_tokens: 32768
max_num_seqs: 8
max_model_len: 262144
kv_cache_dtype: fp8
attention_backend: flashinfer
load_format: instanttensor
prefix_caching: true
tool_call_parser: qwen3_coder
reasoning_parser: qwen3
tensor_parallel_size: 1

speculative_config:
method: mtp
num_speculative_tokens: 2

env:
VLLM_MARLIN_USE_ATOMIC_ADD: '1'
VLLM_ALLOW_LONG_MAX_MODEL_LEN: '1'
VLLM_USE_FLASHINFER_SAMPLER: '1'
sparkrun run Qwen3.6-35B-A3B-NVFP4-MTP-v5.yaml
# 3 min startup: load + compile + autotune
# ตอนเสร็จ vLLM จะฟังที่ port 8000

ผมใช้ LiteLLM proxy เป็นตัวกลาง ตั้ง timeout=60s, num_retries=1 เพื่อกัน stuck request ค้างนาน

เรียนรู้จาก journey นี้​

สิ่งที่ผมได้จากการ tune v1 → v5:

  • อย่าเพิ่ม gpu_mem เพราะคิดว่ายิ่งสูงยิ่งดี v0.95 ก็ไม่ได้แปลว่าเร็วกว่า v0.75 เสมอไป ขึ้นอยู่กับ workload จริง
  • RAM 99% ไม่ได้เกิดจาก gpu_mem ต่ำ เกิดจาก stuck request → prefix cache สะสม ต้องแก้ที่ LiteLLM timeout ไม่ใช่ gpu_mem
  • Benchmark anomaly คือเรื่องจริง v3 ที่ผมเชื่อว่าดี 209-835 tok/s พอ trace แล้วเป็น measurement bug ลบทิ้ง ดีกว่าเก็บไว้หลอกตัวเอง
  • max_num_seqs สำคัญ default 256 oversized สำหรับ single GPU ผมตั้ง 8 เพราะ workload จริงไม่เคยเกิน 6

อ้างอิง​

แชร์บทความ
☕

เนื้อหานี้มีประโยชน์ไหม? ช่วยสนับสนุนค่ากาแฟให้ผู้เขียนสักแก้ว

Buy Me a Coffee
Loading...