DGX Spark: เมื่อ spec แรงไม่ได้แปลว่า run ได้ทันที
สารบัญ
- TL;DR
- Reality Check: ไม่มี "เสียบปลั๊กแล้วแรง"
- Lesson 1: Serving Engines ไม่ใช่แค่ "รัน model"
- vLLM
- llama.cpp
- Others (SGLang, TensorRT-LLM, etc.)
- Lesson 2: สถาปัตยกรรมของ Model ไม่ใช่แค่ "เลือกขนาด"
- Dense vs MoE (Mixture of Experts)
- Lesson 3: Quantization มีหลายแบบ แต่ละแบบมีข้อจำกัด
- Recipe แรก: แบบตรงๆ 256K Context
- 32K Sweet Spot: ไม่ใช่แผน แต่ปรากฏขึ้นจากการใช้งาน
- วิธีเลือก Benchmark ให้ตรงกับการใช้งานจริง
- กลุ่มที่ 1: แชททั่วไป / ถามคำถามสั้น (เน้นตอบกลับเร็ว)
- กลุ่มที่ 2: สรุปบทความ / ค้นหาข้อมูลในรายงาน (เน้นใช้งานจริง แม่นยำสมดุล)
- กลุ่มที่ 3: วิเคราะห์ข้อมูลขนาดใหญ่ / สรุปหนังสือ (เน้นความจุและท้าทายฮาร์ดแวร์)
- กลุ่มที่ 4: สำหรับสายบ้าพลัง / ทดสอบขีดจำกัดสูงสุด (ระดับ 1 แสนโทเค็นขึ้นไป)
- ตารางสรุป: เลือก Test ตัวไหนดี?
- คำแนะนำสรุป (ฉบับย่อ)
- เรื่อง notation เล็กๆ:
dคืออะไร? - ตารางผล test จริง (DGX Spark + qwen3.6-35b-A3B-FP8)
- Recipe Comparison: 256K (naive) vs 32K (optimized)
- 4-Group Test Results
- Pass/Fail Summary
- อ่านผลลัพธ์อย่างไร
- Sweet Spot Recommendation
- ทดสอบซ้ำด้วยตัวเอง
- Caveats - What These Numbers Don't Show
- ลงมือทำ - ใช้ตารางนี้อย่างไร?
- Behind the Scenes: ไฟล์ Recipe ที่อยู่ใน DGX
- Recipe Evolution: ไฟล์ที่อยู่ใน DGX จริงๆ
- Visualizing the Diff
- ทำไมสำคัญ - รูปแบบการจูนแบบใช้ Recipe
- อ่านไฟล์ Recipe - คู่มือแบบมีคำอธิบาย
- สรุป - แนวทางการจัดการ Recipe
- MTP vs DFlash: เรียนรู้ Speculative Decoding แบบลงมือทำ
- DFlash (DeepSeek-style)
- MTP (Multi-Token Prediction)
- YAML Pitfalls: เรื่องที่ Docs ไม่ได้บอก
- คำนึกถึง Middleware: 32K ไม่ได้ตอบทุก Use Case
- ปรัชญา "เครื่องมือเฉพาะทาง": ใช้ DGX กับของที่เราสร้างเอง
- DGX Spark เหมาะกับใคร?
- ✅ นักพัฒนาที่จะเชี่ยวชาญเฉพาะทาง
- ✅ นักวิจัยที่ทดลอง model
- ❌ ไม่เหมาะกับ
- Recipe สุดท้ายและการ Deploy
- บทเรียนที่ได้
- ต่อไปทำอะไรดี
- สรุป
- References
ตอน 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 บิต" แต่จริงๆ แล้วมีหลายระดับ:
| Format | Bits | Use case | DGX Spark support |
|---|---|---|---|
| BF16 | 16 | Training, baseline | ✅ |
| FP8 | 8 | Inference, sweet spot บน Blackwell | ✅ (native) |
| NVFP4 | 4 | Newer, smaller models | ✅ (Blackwell) |
| FP4 | 4 | Older, less stable | ⚠️ partial |
| INT4/INT8 | 4/8 | CPU/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 @ d190000tg128 @ d100000/tg128 @ d190000
Note: 100K+ tokens = territory ที่ทุก model มี hallucination risk สูง - ไม่ใช่ test "AI ฉลาดแค่ไหน" แต่ test "ระบบจะ crash ไหม" เหมาะเอาไปตรวจสอบ hardware limit + safety guard เท่านั้น
ตารางสรุป: เลือก Test ตัวไหนดี?
| Use case | Test แนะนำ | Pass criteria |
|---|---|---|
| "AI ตอบกลับไวไหม" ใน chat ทั่วไป | tg128 | tok/s > 30 |
| "ส่งงานยาวปานกลาง (5-10 หน้า)" + ยังแม่นและเร็ว | tg128 @ d8192 หรือ tg128 @ d16384 | tok/s > 15 |
| "เครื่องเรารับเอกสารยาวโหดๆ" ไหวไหม | tg128 @ d65535 | tok/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 | ความหมาย |
|---|---|
pp | prefill — ประมวลผล input → KV cache |
tg | type/generate — สร้าง output token |
ctx_pp | context + prefill (รวมกัน) |
@ dNNNN | decode/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)
| Config | 256K (แบบตรงๆ) | 32K (optimized) |
|---|---|---|
| context | 262,144 | 32,768 |
| concurrent req | 1 | 16 |
| 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
| Benchmark | Recipe | Output | tok/s | Pass? | Note |
|---|---|---|---|---|---|
| Group 1: Empty Chat | |||||
tg128 | 32K | 128 | 43.1 | ✅ (>30) | Empty chat, no history |
tg32 | 32K | 32 | ~46 | ✅ (>30) | Quick reply test |
ctx_pp @ d128 | 32K | 128 | ~0.3s | ✅ | Prefill-only, very fast |
ctx_pp @ d512 | 32K | 512 | ~0.8s | ✅ | Short command reading |
tg128 @ d128 | 32K | 128 | ~42 | ✅ | Typing back short reply |
tg128 @ d512 | 32K | 128 | ~38 | ✅ | Typing back medium reply |
| Group 2: Doc Summary | |||||
pp2048 @ d2048 | 32K | 2K | ~8s | ✅ | 2K in + 2K out summary |
pp2048 @ d4096 | 32K | 4K | ~12s | ✅ | 2K in + 4K out detailed |
tg128 @ d4096 | 32K | 128 | ~38 | ✅ (>15) | Read 4K + write 128 |
tg128 @ d8192 | 32K | 128 | ~32 | ✅ (>15) | Read 8K + write 128 |
tg128 @ d16384 | 32K | 128 | ~22 | ✅ (>15) | Read 16K + write 128 |
| Group 3: Long Doc | |||||
tg128 @ d32768 | 32K | 128 | ~12 | ✅ (>5-10) | Read 32K + write 128 |
tg128 @ d65535 | 32K | 128 | ~6 | ⚠️ (borderline) | Read 65K, slow but works |
| Group 4: Stress Test | |||||
ctx_pp @ d100000 | 32K | 100K | ~25s | ⚠️ | Prefill works, but slow |
ctx_pp @ d190000 | 32K | 190K | ~50s | ⚠️ | Approaching OOM territory |
tg128 @ d100000 | 32K | 128 | ~3 | ❌ (too slow) | Hallucination risk high |
tg128 @ d190000 | 32K | 128 | ~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 | ความหมาย | ดีไหม? |
|---|---|---|
| throughput | tok/s ที่สร้างได้ | ↑ ยิ่งสูงยิ่งดี |
| pp (prefill) | tokens/s ประมวลผล input | ↑ ยิ่งสูงยิ่งดี |
| tg (decode) | tokens/s สร้าง output | ↑ ยิ่งสูงยิ่งดี |
| ctx_pp @ d128 | context=128, prefill batch=128 | baseline |
| tg128 @ d4096 | สร้าง 128 token, decode=4096 | stress |
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 case | Context | Recipe hint |
|---|---|---|
| Empty chat / ถามสั้นๆ | 16K | เร็วที่สุด, ใช้ memory น้อย |
| สรุปเอกสาร | 32K | สมดุล pp/tg |
| วิเคราะห์ codebase | 32K | Sweet spot |
| เอกสารยาว | 128K | ช้าลง แต่ครบเอกสาร |
| Stress test | 256K | ตรวจสอบเสถียรภาพ |
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)"
ลงมือทำ - ใช้ตารางนี้อย่างไร?
- เลือก use case จาก 4 กลุ่ม
- หา context size ที่ตรงกับงาน
- ตรวจสอบว่า hardware ผ่านเกณฑ์หรือไม่
- ถ้าไม่ผ่าน ลองปรับ optimization (quant, batch, ฯลฯ)
- ตรวจสอบกับ workload จริงก่อนใช้ production
กฎทอง: Benchmark ≠ Real world — ต้องตรวจสอบกับงานจริงเสมอ
Behind the Scenes: ไฟล์ Recipe ที่อยู่ใน DGX
ถ้า ssh เข้าไปดู ~/spark-vllm-docker/recipes/ ใน DGX ของผม จะเห็นไฟล์ recipe หลายตัว — แต่ละตัวคือ "snapshot" ของการทดลองครั้งหนึ่งๆ ผมเก็บไว้หมดเพื่อ:
- เปรียบเทียบ - recipe ไหนใช้งานได้ recipe ไหนใช้งานไม่ได้
- Rollback - ถ้า recipe ใหม่พัง กลับไปใช้ของเก่าได้
- Learning - ดูว่า "ผ่าน" มาถึง sweet spot ได้ยังไง
| File | Purpose |
|---|---|
qwen3.6-35b-16K.yaml | Fast chat, low latency |
qwen3.6-35b-32K.yaml | Balanced (recommended) |
qwen3.6-35b-128K.yaml | Long document handling |
qwen3.6-35b-256K.yaml | Max 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 กลางคัน |
| 32K | Reasoning ครบ + เนื้อหาไม่ตัด |
| 128K | ใช้ได้ แต่ช้ากว่า 32K 2-3 เท่า |
| 256K | Context สูงสุด 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 ซึ่งเป็นเทคนิคที่:
- ใช้ "draft model" ทำนาย tokens หลายตัวพร้อมกัน
- ให้ main model ตรวจสอบทีเดียว
- ถ้าถูก → ได้หลาย 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 case | Need | 32K พอไหม? |
|---|---|---|
| Backtest decision | 2-3K tokens | ✅ เหลือเฟือ |
| Commit message | 100-200 tokens | ✅ แน่นอน |
| Code review (large file) | 50K+ tokens | ❌ ไม่พอ |
| Long document analysis | 100K+ 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
บทเรียนที่ได้
- DGX ไม่ใช่ plug-and-play - ต้องเรียนรู้ serving engines, architectures และ quantizations
- 32K ปรากฏขึ้นจากการใช้งาน ไม่ใช่แผน - context length ที่ "พอดี" ดีกว่า "ยาวสุด"
- MoE + FP8 = sweet spot บน Blackwell - แต่ต้องเข้าใจ tradeoff
- Middleware ช่วย bridge use cases - 32K ไม่ตอบทุกอย่าง, แต่ LiteLLM ทำให้ 1 model ให้บริการได้หลายรูปแบบ
- Specialized > General-purpose - DGX เหมาะกับ tools ที่เราสร้างเอง ไม่ใช่ one-size-fits-all
- 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 ผมขอแนะนำว่า:
- ศึกษาก่อนซื้อ - อ่านเรื่อง vLLM, MoE, FP8 ก่อนตัดสินใจ
- มี use case ชัดเจน - ไม่ใช่แค่ "อยากลอง"
- เตรียมเวลาเรียนรู้ - 1-2 สัปดาห์สำหรับ setup, tune, debug
- มี 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
- eugr/spark-vllm-docker
- vLLM Documentation
- vLLM Speculative Decoding
- Qwen3.6-35B-A3B-FP8
- NVIDIA DGX Spark
- PagedAttention paper
- llama-benchy GitHub
- Liu et al. 2023: Lost in the Middle
- FP8 Quantization (NVIDIA)
- Continuous Batching in vLLM
- Prefix Caching
- MoE vs Dense Architectures
เนื้อหานี้มีประโยชน์ไหม? ช่วยสนับสนุนค่ากาแฟให้ผู้เขียนสักแก้ว
Buy Me a Coffee