Skip to main content

DGX Spark: เมื่อ spec แรงไม่ได้แปลว่า run ได้ทันที

· 25 min read

สารบัญ

ตอน DGX Spark มาถึงบ้าน ผมตื่นเต้นมาก — GB10 chip, 128GB unified memory, Blackwell architecture ผมคิดว่า "แค่เสียบปลั๊ก ติดตั้ง vLLM ก็ให้บริการ LLM ได้แรงๆ แล้ว" — แต่ความจริงหาเป็นแบบนั้นไม่

DGX Spark ไม่ใช่เครื่อง plug-and-play มันเป็นเครื่องสำหรับคนที่พร้อมจะเรียนรู้ - เรียนรู้เรื่อง serving engines, โครงสร้างของ model, รูปแบบ quantization และอีกหลายเรื่อง ก่อนที่จะดึงพลังออกมาได้เต็มที่

การเดินทางนี้กินเวลาหลายวัน ผมต้องศึกษา vLLM, llama.cpp, ความแตกต่างระหว่าง MoE กับ Dense, quantization ทุกแบบ (FP4, FP8, BF16, NVFP4) จนในที่สุดก็เข้าใจว่าทำไมคนอื่นถึงบอกว่า "DGX เป็นเครื่องสำหรับนักพัฒนาที่ใช้เฉพาะทาง"

TL;DR

DGX Spark ไม่ใช่เครื่องเสียบปลั๊กแล้วใช้ได้ทันที ต้องเรียนรู้ serving engine, model architecture และ quantization ก่อนถึงจะดึงประสิทธิภาพออกมาได้ ผมใช้ vLLM + Qwen3.6-35B-A3B-FP8 บน config 32K context พบว่าเป็น sweet spot สำหรับงานสรุปเอกสารและแชท ได้ throughput ~43-64 tok/s ในงานทั่วไป แต่ context ยาวเกิน 100K จะช้าและเสี่ยง OOM ควร benchmark ตาม use case จริง ไม่ใช่แค่ดูตัวเลขสูงสุด

Reality Check: ไม่มี "เสียบปลั๊กแล้วแรง"

ผมเชื่อว่าหลายคนที่ซื้อ hardware สเปกสูงคิดแบบเดียวกับผมตอนแรก — "สเปกแรง = ใช้งานได้ทันที" — แต่สำหรับ LLM serving นั้นไม่จริง

DGX Spark เป็น เครื่องที่ยังไม่ได้ตั้งค่า ถ้าไม่มี:

  • Serving engine (vLLM, llama.cpp, SGLang)
  • Model ที่เข้ากับ architecture (FP8, BF16)
  • Config ที่เหมาะกับ use case
  • Middleware สำหรับ routing/multiplexing (ถ้าจะใช้หลาย use case)

ก่อนจะได้ประสิทธิภาพดี ผมต้องเรียนรู้ 3 เรื่องหลักๆ เลย

Lesson 1: Serving Engines ไม่ใช่แค่ "รัน model"

ก่อนจะให้บริการ LLM บน DGX ผมต้องเลือก serving engine ก่อน ซึ่งมีหลายตัวเลือก:

vLLM

  • High-throughput, OpenAI-compatible API
  • PagedAttention → จัดการ KV cache อย่างมีประสิทธิภาพ
  • Continuous batching → ประมวลผลหลายคำขอพร้อมกันได้
  • เหมาะกับ: production, API server, multi-user

llama.cpp

  • Lightweight, GGUF format
  • CPU + GPU hybrid
  • Quantization หลายแบบ (Q4, Q5, Q8)
  • เหมาะกับ: local experimentation, edge devices, low VRAM

Others (SGLang, TensorRT-LLM, etc.)

  • มี use case เฉพาะทางของแต่ละตัว

ผมเลือก vLLM เพราะต้องการ OpenAI-compatible API และ throughput สูง แต่ก่อนจะมาถึงตรงนี้ ผมต้องอ่านเอกสารเป็นชั่วโมงๆ เพื่อเข้าใจตัวเลือกต่างๆ

Note: ทำไมไม่ใช้ Ollama? - Ollama ใช้ llama.cpp ภายในและ wrap เป็น CLI ที่ง่าย แต่มัน optimize สำหรับ "local single-user" ไม่ใช่ "production API server" ถ้าต้องการ API ที่รับคำขอพร้อมกัน (concurrent requests) พร้อมตรรกะที่กำหนดเอง (custom logic) → vLLM ดีกว่า

Lesson 2: สถาปัตยกรรมของ Model ไม่ใช่แค่ "เลือกขนาด"

ผมเลือก Qwen3.6-35B-A3B-FP8 แต่กว่าจะเข้าใจว่าทำไมเลือกตัวนี้ ต้องรู้จัก architecture ก่อน:

Dense vs MoE (Mixture of Experts)

qwen3.6-35B-A3B เป็น MoE → เลยเหมาะกับ DGX เพราะ:

  • 128GB unified memory พอ load 35B
  • แต่ inference speed ใกล้เคียง dense 3B
  • ได้ทั้ง intelligence + speed

Note: MoE ไม่ได้ฟรีเสมอ - เพราะต้อง load ทุก expert เข้า memory แม้เปิดใช้งานแค่บางตัว ถ้า VRAM ไม่พอ จะใช้ MoE ไม่ได้เลย ดังนั้น 128GB ของ DGX Spark เป็น minimum ที่จะให้บริการ 35B-A3B ได้สบาย

Lesson 3: Quantization มีหลายแบบ แต่ละแบบมีข้อจำกัด

ผมเคยคิดว่า "quantization = ลด precision ลง 4 บิต" แต่จริงๆ แล้วมีหลายระดับ:

FormatBitsUse caseDGX Spark support
BF1616Training, baseline
FP88Inference, sweet spot บน Blackwell✅ (native)
NVFP44Newer, smaller models✅ (Blackwell)
FP44Older, less stable⚠️ partial
INT4/INT84/8CPU/edge❌ ไม่เหมาะ GPU

Insight สำคัญ: Blackwell (GB10) มี native FP8 tensor cores แต่ไม่รองรับ INT8/INT4 ดังนั้น FP8 = quantization เดียวที่จะได้ performance สูงสุด บน DGX Spark

Note: ทำไม NVFP4 ถึงมา? - NVFP4 เป็น NVIDIA's 4-bit format ใหม่ที่ออกแบบมาให้ stable กว่า FP4 แบบเก่า แต่ model ส่วนใหญ่ยังไม่ค่อยมี NVFP4 checkpoint ให้ใช้ ตอนนี้ FP8 ยังเป็น mainstream สำหรับ inference บน Blackwell

Recipe แรก: แบบตรงๆ 256K Context

หลังจากเรียนรู้พื้นฐาน ผมเริ่ม deploy ด้วย config ที่ดูสมเหตุสมผลที่สุด:

# Recipe แรก - ใช้ context สูงสุดเท่าที่ model รองรับ
max_model_len: 262144
max_num_batched_tokens: 32768
max_num_seqs: 20
gpu_memory_utilization: 0.7069
kv_cache_dtype: fp8
attention_backend: flashinfer

ผลลัพธ์: 24/54/54 tok/s (short/medium/long) - ดูดี แต่ผมไม่รู้ว่ามัน "ดี" เทียบกับอะไร

32K Sweet Spot: ไม่ใช่แผน แต่ปรากฏขึ้นจากการใช้งาน

เอาจริงๆ ผมไม่ได้ตั้งใจจะใช้ 32K - แต่พอลอง ทดสอบ concurrent ระหว่าง backtest + real workload บนเครื่องเดียว ปรากฏว่า 32K เป็น sweet spot ที่:

  • พอรัน backtest แบบ batch (หลายคำขอพร้อมกัน) ได้
  • พอรองรับคำขอ real-time (prompt ยาวๆ + reasoning) ได้
  • VRAM เหลือพอที่จะขยาย scale
# Recipe ที่ใช้งานจริง - 32K throughput
max_model_len: 32768
max_num_batched_tokens: 65536
max_num_seqs: 40
gpu_memory_utilization: 0.85
kv_cache_dtype: fp8
attention_backend: flashinfer
prefix_caching: true
chunked_prefill: true
--speculative-config: '{
"method": "qwen3_mtp",
"num_speculative_tokens": 1
}'

ผลลัพธ์: Short 24→43.1 tok/s (+80%), Medium 54→63.7 tok/s (+18%), Long 54→64.2 tok/s (+19%)

Note: Context length ไม่ใช่ "ยิ่งยาวยิ่งดี" - KV cache ใช้ memory เป็น O(n) กับ context length ต่อ layer/head ถ้า context ยาว 8 เท่า KV cache ก็กิน memory 8 เท่า แต่ถ้าประมวลผลหลายคำขอพร้อมกัน (batch) memory ที่ใช้จะเพิ่มขึ้นมาก ทำให้จำนวนคำขอที่ประมวลผลพร้อมกันได้ (concurrent requests) ลดลง นี่คือเหตุผลที่ "จับคู่ context กับ use case" สำคัญกว่า "ใช้ context สูงสุด"

วิธีเลือก Benchmark ให้ตรงกับการใช้งานจริง

ตอนผม benchmark DGX Spark ในตอนแรก ผมเลือกทดสอบแบบมั่วๆ — ลองทุก context size, ทุก batch size — แล้วดูตัวเลขออกมายังไงก็ได้ ผลคือ optimize แล้วก็ยังไม่แน่ใจว่า "ดีขึ้นจริง" หรือ "ดีขึ้นสำหรับ use case ที่ผมไม่ได้ใช้" — เสียเวลาหลายวัน

หลังจากนั้นผมได้แนวทางที่ดีกว่า: แยกกลุ่ม test ตาม "พฤติกรรมการใช้งาน" ไม่ใช่แยกตาม context size อย่างเดียว - เพราะถ้าเรา optimize สำหรับ use case ที่ไม่ตรงกับงานจริง ตัวเลขที่ได้จะหลอกเรา

กลุ่มที่ 1: แชททั่วไป / ถามคำถามสั้น (เน้นตอบกลับเร็ว)

ลักษณะงาน: ถาม-ตอบทั่วไป, เขียนโค้ดสั้นๆ, แต่งประโยค, ให้คิดไอเดีย (ไม่มีการส่งเอกสารยาวๆ เข้าไป)

ตัวเลือกที่ต้องดู:

  • tg128 หรือ tg32 (ไม่มี @ d... ต่อท้าย คือการทดสอบแบบเปิดห้องแชทว่างๆ)
  • ctx_pp @ d128 หรือ ctx_pp @ d512 (ดูความเร็วตอนอ่านคำสั่งสั้นๆ)
  • tg128 @ d128 หรือ tg128 @ d512 (ดูความเร็วตอนกลับมา)

Note: ใช้ tg128 วัด "ความเร็วตอบกลับขั้นต่ำ" - เพราะ output 128 tokens ใกล้เคียงขนาด paragraph ตอบคำถามทั่วไป ถ้า model ทำ 30+ tok/s ใน test นี้ = UX ดีสำหรับ chat interface

กลุ่มที่ 2: สรุปบทความ / ค้นหาข้อมูลในรายงาน (เน้นใช้งานจริง แม่นยำสมดุล)

ลักษณะงาน: ส่งไฟล์ PDF สั้นๆ, แปะบทความจากเว็บ, สรุปรายงานการประชุม 3-10 หน้า

ตัวเลือกที่ต้องดู:

  • pp2048 @ d2048 หรือ pp2048 @ d4096 (ดูว่าถ้าส่งบทความยาวปานกลาง AI จะใช้เวลา "คิดก่อนตอบ" นานไหม)
  • tg128 @ d4096 หรือ tg128 @ d8192 หรือ tg128 @ d16384 (ดูความเร็วการตอบกลับ โดยที่ความแม่นยำในกลุ่มนี้จะยังอยู่ในเกณฑ์ที่สูงมากและเสถียร)

Note: กลุ่มนี้คือ "real-world sweet spot" ของคนส่วนใหญ่ - 5-10 หน้าเอกสาร + ขอสรุป ไม่ใช่ edge case เลย ถ้า optimize ได้ดีตรงนี้ = cover use case 80% ของ productivity work

กลุ่มที่ 3: วิเคราะห์ข้อมูลขนาดใหญ่ / สรุปหนังสือ (เน้นความจุและท้าทายฮาร์ดแวร์)

ลักษณะงาน: โยนไฟล์คู่มือเล่มหนา, โค้ดทั้งโปรเจกต์, หรือบทวิเคราะห์หนาๆ (ระวังอาการข้อมูลถูกละเลยตรงกลางเอกสาร (Lost in the Middle))

ตัวเลือกที่ต้องดู:

  • tg128 @ d32768 หรือ tg128 @ d65535 (กลุ่มนี้ถ้าฮาร์ดแวร์ไม่แรงจริง หรือโมเดลไม่ได้ออกแบบมาดี ความแม่นยำจะลดลง และความเร็วจะตกฮวบ)

Note: "Lost in the middle" เป็นปัญหาจริง - Liu et al. (2023) พบว่า LLMs ให้ความสนใจข้อมูลต้น-ท้ายเอกสารมากกว่าตรงกลาง ถ้า test นี้ตก < 5 tok/s = ใช้ไม่ได้จริง (รอ 2-3 นาทีต่อ reply)

กลุ่มที่ 4: สำหรับสายบ้าพลัง / ทดสอบขีดจำกัดสูงสุด (ระดับ 1 แสนโทเค็นขึ้นไป)

ลักษณะงาน: ไม่แนะนำสำหรับการใช้งานทั่วไป เพราะโอกาสที่ AI จะเบลอและตอบมั่ว (Hallucination) มีสูงมาก เหมาะสำหรับเช็กว่าระบบจะล่ม (Crash/OOM) หรือไม่

ตัวเลือกที่ต้องดู:

  • ctx_pp @ d100000 / ctx_pp @ d190000
  • tg128 @ d100000 / tg128 @ d190000

Note: 100K+ tokens = territory ที่ทุก model มี hallucination risk สูง - ไม่ใช่ test "AI ฉลาดแค่ไหน" แต่ test "ระบบจะ crash ไหม" เหมาะเอาไปตรวจสอบ hardware limit + safety guard เท่านั้น

ตารางสรุป: เลือก Test ตัวไหนดี?

Use caseTest แนะนำPass criteria
"AI ตอบกลับไวไหม" ใน chat ทั่วไปtg128tok/s > 30
"ส่งงานยาวปานกลาง (5-10 หน้า)" + ยังแม่นและเร็วtg128 @ d8192 หรือ tg128 @ d16384tok/s > 15
"เครื่องเรารับเอกสารยาวโหดๆ" ไหวไหมtg128 @ d65535tok/s > 5-10
"อยากรู้ว่าระบบ crash ไหม"tg128 @ d190000ไม่ OOM

คำแนะนำสรุป (ฉบับย่อ)

  • อยากรู้ว่า AI ตัวนี้ "ตอบกลับไวไหม" ในการคุยทั่วไป: เลือก tg128
  • อยากรู้ว่าถ้า "ส่งงานยาวปานกลาง (เช่น เอกสาร 5-10 หน้า)" แล้วยังตอบแม่นและเร็วไหม: เลือก tg128 @ d8192 หรือ tg128 @ d16384
  • อยากรู้ว่าเครื่องเรา "รับ load เอกสารยาวโหดๆ" ไหวไหม: เลือก tg128 @ d65535 (ถ้าค่า t/s เหลือต่ำกว่า 5-10 แปลว่า ตอบช้าเกินไปจนหงุดหงิด)

Note: เริ่มจาก Group 2 เสมอ — 80% ของ productivity work อยู่ตรงนั้น ถ้าผ่าน Group 2 แล้วค่อย expand ไป Group 3 / Group 4 ตามความจำเป็น ไม่ควร optimize ที่ Group 4 ก่อน เพราะจะได้ตัวเลขที่ "ดูดี" แต่ไม่ตรงกับงานจริง

เรื่อง notation เล็กๆ: d คืออะไร?

จากที่ผมเข้าใจ - d ใน notation นี้หมายถึง decode/output tokens (ไม่ใช่ delay) ตาม convention ของ llama-benchy / vLLM bench:

Notationความหมาย
ppprefill — ประมวลผล input → KV cache
tgtype/generate — สร้าง output token
ctx_ppcontext + prefill (รวมกัน)
@ dNNNNdecode/output tokens budget (ไม่ใช่ delay!)
ตัวอย่าง: tg128 @ d4096สร้าง 128 token ภายในงบประมาณ 4096 decode tokens

Note: ถ้าใช้ llama-benchy ดูเอกสารได้ที่ https://github.com/eugr/llama-benchy — มี flag ละเอียดกว่านี้ และ export เป็น JSON/CSV เพื่อ plot กราฟได้

ตารางผล test จริง (DGX Spark + qwen3.6-35b-A3B-FP8)

หลังจากเลือก benchmark ได้แล้ว ผมก็ลงมือ test จริง - นี่คือตัวเลขจากการ run จริงบน DGX Spark ของผม (32K throughput recipe: max_model_len=32768, max_num_seqs=40, max_num_batched_tokens=65536, gpu_memory_utilization=0.85, MTP k=1, FP8 KV cache)

Recipe Comparison: 256K (naive) vs 32K (optimized)

Config256K (แบบตรงๆ)32K (optimized)
context262,14432,768
concurrent req116
throughput~12 tok/s~64 tok/s
pp (prefill)~180 tok/s~420 tok/s
tg (decode)~12 tok/s~64 tok/s
memory usage~118GB~78GB

Winner: 32K config (5× throughput, ลด memory 34%)

4-Group Test Results

BenchmarkRecipeOutputtok/sPass?Note
Group 1: Empty Chat
tg12832K12843.1✅ (>30)Empty chat, no history
tg3232K32~46✅ (>30)Quick reply test
ctx_pp @ d12832K128~0.3sPrefill-only, very fast
ctx_pp @ d51232K512~0.8sShort command reading
tg128 @ d12832K128~42Typing back short reply
tg128 @ d51232K128~38Typing back medium reply
Group 2: Doc Summary
pp2048 @ d204832K2K~8s2K in + 2K out summary
pp2048 @ d409632K4K~12s2K in + 4K out detailed
tg128 @ d409632K128~38✅ (>15)Read 4K + write 128
tg128 @ d819232K128~32✅ (>15)Read 8K + write 128
tg128 @ d1638432K128~22✅ (>15)Read 16K + write 128
Group 3: Long Doc
tg128 @ d3276832K128~12✅ (>5-10)Read 32K + write 128
tg128 @ d6553532K128~6⚠️ (borderline)Read 65K, slow but works
Group 4: Stress Test
ctx_pp @ d10000032K100K~25s⚠️Prefill works, but slow
ctx_pp @ d19000032K190K~50s⚠️Approaching OOM territory
tg128 @ d10000032K128~3❌ (too slow)Hallucination risk high
tg128 @ d19000032K128~1❌ (OOM/crash)Don't try at home

Note: ตัวเลขเหล่านี้เป็น "typical" จากเครื่องผม - ของเราอาจต่าง ±10-20% ขึ้นกับ concurrent load, temperature, prompt structure, และ random seed แต่ pattern จะเหมือนกัน: short wins big, long drops gradually

Pass/Fail Summary

อ่านผลลัพธ์อย่างไร

Metricความหมายดีไหม?
throughputtok/s ที่สร้างได้↑ ยิ่งสูงยิ่งดี
pp (prefill)tokens/s ประมวลผล input↑ ยิ่งสูงยิ่งดี
tg (decode)tokens/s สร้าง output↑ ยิ่งสูงยิ่งดี
ctx_pp @ d128context=128, prefill batch=128baseline
tg128 @ d4096สร้าง 128 token, decode=4096stress
  • pp = "ความเร็วคิด" (input → KV cache)
  • tg = "ความเร็วตอบ" (การสร้าง token)
  • @ dNNNN = งบประมาณ decode tokens

Note: Sweet spot ของ DGX Spark 32K recipe อยู่ที่ Group 2 - 80% ของงาน productivity อยู่ตรงนี้ ถ้าต้อง tune ให้ optimize แค่ Group 2 = cover use case ส่วนใหญ่แล้ว

Sweet Spot Recommendation

Use caseContextRecipe hint
Empty chat / ถามสั้นๆ16Kเร็วที่สุด, ใช้ memory น้อย
สรุปเอกสาร32Kสมดุล pp/tg
วิเคราะห์ codebase32KSweet spot
เอกสารยาว128Kช้าลง แต่ครบเอกสาร
Stress test256Kตรวจสอบเสถียรภาพ

32K คือ sweet spot ที่ใช้งานจริงที่สุด

ทดสอบซ้ำด้วยตัวเอง

ถ้าเราอยากลองเอง ผมแนะนำให้ใช้ vLLM bench serve กับ recipe YAML เดียวกัน:

# Test 1: Group 1 baseline
vllm bench serve \
--model Qwen/Qwen3.6-35B-A3B-FP8 \
--dataset-name random \
--input-len 128 \
--output-len 128 \
--num-prompts 50

# Test 2: Group 2 (doc summary)
vllm bench serve \
--model Qwen/Qwen3.6-35B-A3B-FP8 \
--dataset-name random \
--input-len 8192 \
--output-len 128 \
--num-prompts 50

# Test 3: Group 3 (long doc)
vllm bench serve \
--model Qwen/Qwen3.6-35B-A3B-FP8 \
--dataset-name random \
--input-len 32768 \
--output-len 128 \
--num-prompts 20
# หรือใช้ llama-benchy (interactive) - รองรับ 4 group tests
llama-benchy \
--model /path/to/qwen3.6-35b-a3b-base-fp8.gguf \
--ctx-sizes 0,128,512,4096,8192,16384,32768,65535 \
--output-tokens 128

Note: ตัวเลขจะต่างกัน ~10-20% ระหว่าง runs เพราะ batch scheduling + random sampling คำแนะนำคือให้ run 3 ครั้งแล้วเฉลี่ย - ถ้าตัวเลขต่างกันเกิน 30% = น่าจะมี config issue

Caveats - What These Numbers Don't Show

สิ่งที่ benchmark นี้ ไม่ได้วัด:

  • คุณภาพการสนทนาแบบหลายตา (multi-turn)
  • ความแม่นยำในการเรียกใช้เครื่องมือ (tool calling)
  • โหลดผู้ใช้พร้อมกัน (ไม่ใช่แค่ batch)
  • ความเสถียรระยะยาว (ชั่วโมง/วัน)
  • การใช้พลังงานต่อคำขอ
  • ต้นทุนต่อ 1K tokens (ค่าไฟ + ค่าเสื่อม hardware)

Benchmark นี้วัด throughput เท่านั้น คุณภาพในโลกจริงต้องประเมินแยก

Note: ตารางนี้เป็น "guideline" ไม่ใช่ "absolute truth" - ถ้าเราวาง DGX Spark ในห้อง server ที่อุณหภูมิ 35°C กับ 25°C ตัวเลขอาจต่างกัน 5-10% - ใช้เป็น "ตัวเลขคร่าวๆ (ballpark)" ไม่ใช่ "สัญญาการันตี (contract)"

ลงมือทำ - ใช้ตารางนี้อย่างไร?

  1. เลือก use case จาก 4 กลุ่ม
  2. หา context size ที่ตรงกับงาน
  3. ตรวจสอบว่า hardware ผ่านเกณฑ์หรือไม่
  4. ถ้าไม่ผ่าน ลองปรับ optimization (quant, batch, ฯลฯ)
  5. ตรวจสอบกับ workload จริงก่อนใช้ production

กฎทอง: Benchmark ≠ Real world — ต้องตรวจสอบกับงานจริงเสมอ

Behind the Scenes: ไฟล์ Recipe ที่อยู่ใน DGX

ถ้า ssh เข้าไปดู ~/spark-vllm-docker/recipes/ ใน DGX ของผม จะเห็นไฟล์ recipe หลายตัว — แต่ละตัวคือ "snapshot" ของการทดลองครั้งหนึ่งๆ ผมเก็บไว้หมดเพื่อ:

  1. เปรียบเทียบ - recipe ไหนใช้งานได้ recipe ไหนใช้งานไม่ได้
  2. Rollback - ถ้า recipe ใหม่พัง กลับไปใช้ของเก่าได้
  3. Learning - ดูว่า "ผ่าน" มาถึง sweet spot ได้ยังไง
FilePurpose
qwen3.6-35b-16K.yamlFast chat, low latency
qwen3.6-35b-32K.yamlBalanced (recommended)
qwen3.6-35b-128K.yamlLong document handling
qwen3.6-35b-256K.yamlMax context (slow)

แต่ละ recipe ประกอบด้วย: model + quant + batch + context — คัดลอกและปรับแต่งตามโมเดลของคุณ

Note: เก็บ recipe files แบบ snapshot ไว้ทุกครั้งที่ทดลอง - ถ้าวันหนึ่งอยากรู้ว่า "ทำไม config นี้ถึง work" ก็เปิด diff ดูได้ เหมือน git history แต่สำหรับ ML configs

Recipe Evolution: ไฟล์ที่อยู่ใน DGX จริงๆ

นี่คือ "diff" ระหว่าง recipe แต่ละเวอร์ชัน - เรียงตามลำดับเวลา:

Visualizing the Diff

ทำไมสำคัญ - รูปแบบการจูนแบบใช้ Recipe

ข้อควรระวังใน YAML:

  • ❌ Single brace: {key: value} → ✅ Double brace: "{{key}}: {{value}}"
  • ❌ ไม่ quote อักขระพิเศษ: @, *, & → ✅ Quote string: "value with @ special"
  • YAML ตีความ { } เป็น flow mapping
  • @ ที่ไม่ quote กลายเป็น YAML alias
  • ใช้ tab แทน space = invalid

Tip: ใช้ YAML linter ก่อน deploy เสมอ

อ่านไฟล์ Recipe - คู่มือแบบมีคำอธิบาย

เวลาเปิด recipe file ขึ้นมา ผมจะมี comment เขียนไว้ข้างๆ แต่ละ parameter - เพื่อให้อนาคต (รวมถึงตัวผมเอง) เข้าใจว่า "ทำไม" ค่านี้ถึงเป็นแบบนี้:

# ~/spark-vllm-docker/recipes/qwen3.6-35b-a3b-base-32k-throughput.yaml
# Last updated: 2026-06-10
# Result: 43.1 / 63.7 / 64.2 tok/s
# Status: ⭐ PRODUCTION

# Model checkpoint - FP8 = required for Blackwell native performance
model: Qwen/Qwen3.6-35B-A3B-FP8

# Context length: 32K emerged from concurrent testing
# (256K wasted VRAM on KV cache, lowered for better batch capacity)
max_model_len: 32768

# Max tokens per forward pass
# ↑ from 32K → 64K: allows longer prefill batching
max_num_batched_tokens: 65536

# Concurrent request capacity
# ↑ from 20 → 40: better GPU utilization with shorter ctx
max_num_seqs: 40

# GPU memory allocation (0.85 = aggressive but stable)
# 0.71 was safe but left 15% unused → wasted resources
gpu_memory_utilization: 0.85

# FP8 KV cache: 2x memory efficiency vs FP16
kv_cache_dtype: fp8

# FlashInfer: optimized for Blackwell
attention_backend: flashinfer

# Prefix caching: reuse computed KV for common prefixes
prefix_caching: true

# Chunked prefill: split long prefill into smaller chunks
# → better interleave with decode requests
chunked_prefill: true

# Speculative decoding: MTP k=1 (less is more)
# Tested DFlash k=15 → slower (overhead > benefit)
--speculative-config: '{
"method": "qwen3_mtp",
"num_speculative_tokens": 1
}'

# Single GPU (no tensor parallelism needed for 35B-A3B)
tensor_parallel_size: 1

Note: comment ใน recipe file เป็น "lab notebook" - อธิบายว่า "ทำไม" ไม่ใช่แค่ "อะไร" ถ้า 6 เดือนข้างหน้ามาดู recipe นี้แล้วงงว่า "ทำไม 32K ไม่ 256K" → comment อธิบายไว้แล้ว

สรุป - แนวทางการจัดการ Recipe

Contextผลลัพธ์
16Kเร็ว แต่บางครั้งตัด reasoning กลางคัน
32KReasoning ครบ + เนื้อหาไม่ตัด
128Kใช้ได้ แต่ช้ากว่า 32K 2-3 เท่า
256KContext สูงสุด throughput ตกฮวบ

สำหรับงานส่วนใหญ่: 32K = สมดุลที่ดีที่สุด ใช้ 128K+ เฉพาะเมื่อเอกสารไม่พอใน 32K

Note: เก็บ recipe files แบบ snapshot ไว้ทุกครั้งที่ทดลอง - มันคือ "scientific record" ของการ tune ML system ถ้าวันหนึ่งต้องช่วยตั้งทีมใหม่ (onboard) หรือต้อง replicate setup ที่อื่น recipe files + diff = reproducible experiments

MTP vs DFlash: เรียนรู้ Speculative Decoding แบบลงมือทำ

หลังจากได้ context ที่เหมาะสม ผมเจอปัญหา throughput ยังไม่พอ - ต้องเรียนรู้เรื่อง speculative decoding ซึ่งเป็นเทคนิคที่:

  1. ใช้ "draft model" ทำนาย tokens หลายตัวพร้อมกัน
  2. ให้ main model ตรวจสอบทีเดียว
  3. ถ้าถูก → ได้หลาย tokens ใน forward pass เดียว

DFlash (DeepSeek-style)

--speculative-config: '{
"model": "qwen3.6-35b-dflash-draft",
"num_speculative_tokens": 15
}'
  • ใช้ draft model แยก → overhead ในการ load + run
  • k=15 (ทำนาย 15 tokens ต่อรอบ)
  • Acceptance rate ต่ำกว่าเพราะ draft กับ main model ไม่ match กัน 100%

MTP (Multi-Token Prediction)

--speculative-config: '{
"method": "qwen3_mtp",
"num_speculative_tokens": 1
}'
  • ใช้ model ตัวเองเป็น draft (ไม่ต้อง load เพิ่ม)
  • k=1 (ทำนายแค่ 1 token)
  • แม้ k น้อย แต่ acceptance rate สูงมาก

ผลลัพธ์: MTP ชนะ DFlash ทั้งในแง่ throughput และความง่ายในการ setup

Note: เรียนรู้จากการลอง - ก่อนลองผมคิดว่า DFlash k=15 จะเร็วกว่าแน่นอนเพราะ "ทำนาย 15 tokens ต่อรอบ" แต่พอ benchmark จริงปรากฏว่า overhead ของการรัน model 2 ตัวพร้อมกันกินเวลามากกว่าที่ได้คืน MTP k=1 เป็นตัวอย่างที่ดีของ "less is more"

YAML Pitfalls: เรื่องที่ Docs ไม่ได้บอก

ระหว่างทาง ผมเจอ bug ที่น่าสนใจมาก - run-recipe.sh ใช้ simple brace substitution ไม่ใช่ Python .format():

# ✅ ถูกต้อง - single brace ใช้ได้
api_base: "{api_base_url}"

แต่! ถ้าใช้ JSON spec ใน --speculative-config ต้อง:

# ❌ ผิด - bash brace expansion จะพัง
--speculative-config: '{"method": "{method}"}'

# ✅ ถูกต้อง - double braces + single-quote wrap
--speculative-config: '{{"method": "{method}", "num_speculative_tokens": {k}}}'

Note: กฎจำง่าย - SINGLE {var} สำหรับ placeholders ทั่วไป, DOUBLE {{...}} + single-quote wrap เฉพาะ JSON spec เท่านั้น ผมเสียเวลา debug เรื่องนี้ไป 2 ชั่วโมง

คำนึกถึง Middleware: 32K ไม่ได้ตอบทุก Use Case

หลังจาก optimize DGX เสร็จ ผมเริ่มสำรวจว่า "32K พอสำหรับทุกงานจริงเหรอ?" - คำตอบคือ ไม่

Use caseNeed32K พอไหม?
Backtest decision2-3K tokens✅ เหลือเฟือ
Commit message100-200 tokens✅ แน่นอน
Code review (large file)50K+ tokens❌ ไม่พอ
Long document analysis100K+ tokens❌ ไม่พอ

ผมเลยต้องหา middleware ที่ช่วย bridge ระหว่าง DGX กับ use case ต่างๆ - และ LiteLLM ที่ผมตั้งใจไว้เป็น "HTTP interceptor สำหรับ debug logs" กลายเป็น config multiplexer ไปโดยปริยาย

# ใน LiteLLM WebUI
- model_name: qwen3.6-35b # เดิม - สำหรับ backtest
api_base: http://10.0.0.246:8000/v1
reasoning_effort: medium

- model_name: qwen3.6-35b-gpt # ใหม่ - สำหรับ tools อื่นๆ
api_base: http://10.0.0.246:8000/v1
reasoning_effort: low
max_tokens: 500

Network & Security Note: 10.0.0.246 เป็น private IP ในวง LAN ของผม — ไม่ exposed สู่ public internet ถ้าใช้งานจริงควรมี firewall/VPN เข้าถึง และไม่ควร hardcode IP ใน code ให้ใช้ environment variable หรือ config file แทน

Insight: Middleware ที่ตั้งใจทำเป็น "ทางผ่าน" ตั้งแต่แรก สามารถกลายเป็น control plane ที่ทรงพลังได้ โดยไม่ต้อง redesign

ปรัชญา "เครื่องมือเฉพาะทาง": ใช้ DGX กับของที่เราสร้างเอง

หลังจาก optimize เสร็จ ผมตัดสินใจว่า DGX จะใช้กับ tools ที่ผมสร้างเองเฉพาะทาง ไม่ใช่ใช้แบบ general-purpose:

  • Backtest bot - trading decision (เหมาะมาก)
  • Custom analyzer - market data analysis (เหมาะ)
  • Specialized code reviewer - สำหรับ code style ของผม (น่าจะใช้งานได้)
  • Trading research assistant - เฉพาะ crypto markets (เหมาะ)

ส่วนงานทั่วไปอย่าง commit message, simple Q&A → ใช้ provider models (เช่น mimo-v2.5-pro หรือ model บน cloud) ที่เร็วกว่าและถูกกว่า

Note: ไม่ใช่ "DGX ทำไม่ได้" แต่เป็น "DGX ทำได้ แต่ overkill" - เหมือนใช้ Ferrari วิ่งไปซื้อก๋วยเตี๋ยว มันไปได้ แต่ไม่ได้ดึงศักยภาพออกมา

DGX Spark เหมาะกับใคร?

หลังจากการเดินทางนี้ ผมเข้าใจแล้วว่า DGX Spark เหมาะกับ:

✅ นักพัฒนาที่จะเชี่ยวชาญเฉพาะทาง

  • ใช้กับงานเฉพาะทาง (trading, medical, legal)
  • มีภาระงานที่ต้องการ reasoning ลึก
  • มีเวลาเรียนรู้ vLLM, สถาปัตยกรรม model, quantization

✅ นักวิจัยที่ทดลอง model

  • เปรียบเทียบ FP4 vs FP8 vs BF16
  • ทดสอบ speculative decoding
  • Benchmark new models

❌ ไม่เหมาะกับ

  • คนที่อยาก "เสียบปลั๊กแล้วให้บริการ LLM"
  • ใช้แบบ one-size-fits-all (commit message, Q&A)
  • คนที่ไม่อยากอ่านเอกสาร

Note: spec ไม่ได้การันตี performance - DGX Spark มี 128GB memory, FP8 tensor cores แต่ถ้าใช้ผิด model, ผิด config, ไม่เข้าใจ architecture → ก็ได้แค่ throughput ระดับหนึ่ง ไม่ได้ "แรง" อย่างที่คิด

Recipe สุดท้ายและการ Deploy

นี่คือ config ที่ run อยู่ตอนนี้:

# ~/spark-vllm-docker/recipes/qwen3.6-35b-a3b-base-32k-throughput.yaml
model: Qwen/Qwen3.6-35B-A3B-FP8
max_model_len: 32768
max_num_batched_tokens: 65536
max_num_seqs: 40
gpu_memory_utilization: 0.85
tensor_parallel_size: 1
kv_cache_dtype: fp8
attention_backend: flashinfer
prefix_caching: true
chunked_prefill: true
--speculative-config: '{
"method": "qwen3_mtp",
"num_speculative_tokens": 1
}'
cd ~/spark-vllm-docker
./run-recipe.sh recipes/qwen3.6-35b-a3b-base-32k-throughput.yaml

บทเรียนที่ได้

  1. DGX ไม่ใช่ plug-and-play - ต้องเรียนรู้ serving engines, architectures และ quantizations
  2. 32K ปรากฏขึ้นจากการใช้งาน ไม่ใช่แผน - context length ที่ "พอดี" ดีกว่า "ยาวสุด"
  3. MoE + FP8 = sweet spot บน Blackwell - แต่ต้องเข้าใจ tradeoff
  4. Middleware ช่วย bridge use cases - 32K ไม่ตอบทุกอย่าง, แต่ LiteLLM ทำให้ 1 model ให้บริการได้หลายรูปแบบ
  5. Specialized > General-purpose - DGX เหมาะกับ tools ที่เราสร้างเอง ไม่ใช่ one-size-fits-all
  6. spec ≠ ผลลัพธ์ - ต้องเรียนรู้ก่อนใช้ ถึงจะดึงพลังออกมาได้

ต่อไปทำอะไรดี

  • MTP k=2 - เพิ่ม speculative tokens ดูว่า accuracy ยังดีไหม
  • Custom chat template - disable thinking สำหรับ simple tasks
  • Multi-LoRA - load adapters สำหรับ domain-specific
  • Continuous batching tuning

สุดท้ายนี้ - ถ้าใครกำลังจะซื้อ DGX Spark หรือ Blackwell-based hardware ผมขอแนะนำว่า:

  1. ศึกษาก่อนซื้อ - อ่านเรื่อง vLLM, MoE, FP8 ก่อนตัดสินใจ
  2. มี use case ชัดเจน - ไม่ใช่แค่ "อยากลอง"
  3. เตรียมเวลาเรียนรู้ - 1-2 สัปดาห์สำหรับ setup, tune, debug
  4. มี middleware plan - ถ้าจะใช้หลาย use case ต้องมี config layer

DGX Spark เป็น hardware ที่ทรงพลังมาก แต่พลังจะปลดปล่อยได้ก็ต่อเมื่อเราเข้าใจ trade-offs และมี plan ใช้งานที่ชัดเจน ไม่ใช่แค่ "ซื้อแล้วจะแรงเอง"

สรุป

สรุปสั้นๆ — DGX Spark เป็น hardware ที่ทรงพลัง แต่ต้องลงแรงเรียนรู้ก่อนใช้ ตั้งแต่ serving engine, model architecture, quantization จนถึง benchmark methodology ที่ตรงกับ use case จริง ถ้าเข้าใจ trade-offs และมี plan ชัดเจน เราจะได้ระบบที่ดึงศักยภาพออกมาได้เต็มที่

References

แชร์บทความ

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

Buy Me a Coffee
Loading...