Skip to main content

Qwen3.6-35B บน DGX Spark — จาก FP8 สู่ NVFP4 + บทเรียนจาก Atlas

· 16 min read

สารบัญ

เอาจริง ๆ จุดเด่นอย่างนึงของ DGX Spark ที่อาจจะเหนือกว่าเครื่อง x86 และ GPU ค่ายอื่นก็คือ NVFP4 — รูปแบบข้อมูลเลขทศนิยมลอยตัวขนาด 4 บิต (Floating Point 4-bit) ที่พัฒนาโดย NVIDIA เพื่อใช้บนสถาปัตยกรรม GPU รุ่นใหม่อย่าง Blackwell มันคือเหตุผลที่ DGX Spark — ซูเปอร์คอมพิวเตอร์ AI ขนาดเดสก์ท็อป — สามารถรันโมเดลภาษาขนาดใหญ่ (LLM) ระดับร้อยพันล้านพารามิเตอร์ได้ภายในเครื่องเดียวอย่างมีประสิทธิภาพ

NVFP4 คืออะไร และทำไมถึงต่างจาก INT4 ทั่วไป?

NVFP4 ใช้โครงสร้าง E2M1 — 1 sign bit, 2 exponent bits, 1 mantissa bit — ครอบคลุมค่าในช่วงประมาณ -6 ถึง +6 แต่สิ่งที่ทำให้มันแตกต่างจาก INT4 ทั่วไปคือ two-level micro-block scaling: ใช้ scaling factor แบบ E4M3 FP8 บนทุก ๆ 16-value micro-block พร้อม per-tensor FP32 scalar ระดับบนสุด ช่วยลด quantization error ได้อย่างมีนัยสำคัญ เมื่อเทียบกับ MXFP4 ที่ใช้ block ขนาด 32 ค่า NVFP4 ลดขนาด block ลงครึ่งหนึ่ง ทำให้ scale ได้ละเอียดขึ้น และ preserve ค่าเล็ก ๆ ที่สำคัญใน model weights ได้ดีกว่า

พูดง่าย ๆ คือ — แทนที่จะบีบทุกค่าใน tensor ให้ใช้ scale factor ตัวเดียวกัน NVFP4 แบ่งเป็น block เล็ก ๆ และหา scale factor ที่เหมาะสมให้แต่ละ block ทำให้ค่าที่ได้ใกล้เคียงของจริงมากที่สุด

สถาปัตยกรรมนี้ทำงานบน fifth-generation Tensor Cores ของ GB10 Grace Blackwell Superchip ใน DGX Spark ซึ่งสามารถจัดการ microscaled FP4 data ได้โดยตรง ทั้ง grouping, dynamic scaling และ 4-bit matrix operations — สิ่งที่ GPU รุ่นก่อนอย่าง Hopper หรือ Ampere ทำไม่ได้เพราะขาด dedicated datapath สำหรับ FP4

แต่เรื่องก็ไม่ได้สวยงามขนาดนั้น

อ่านเพิ่ม: DGX Spark: 5 Red Flags — Red Flag #1: NVFP4 ยังไม่ทำงานเต็มประสิทธิภาพ

ในความเป็นจริง chip GB10 ใน DGX Spark ใช้ compute capability SM121 ซึ่ง vLLM ตั้งต้นใช้ Marlin fallback — dequant FP4 กลับเป็น BF16 ก่อนคำนวณ แทนที่จะใช้ native FP4 tensor cores NVIDIA clarify ภายหลัง ว่า instruction มีอยู่จริงบน GB10 แต่ต้อง compile ด้วย sm_121a target ไม่ใช่ sm_121 — ปัญหาคือ toolchain ไม่ใช่ transistor ขาด

พูดง่าย ๆ คือ NVFP4 บน DGX Spark ตอนนี้ทำงานผ่าน fallback path ไม่ใช่ native FP4 compute

แล้วมันใช้ได้ไหม?

แม้จะมีดราม่าเรื่องนี้ แต่ก็ไม่ได้แปลว่า NVFP4 ใช้ไม่ได้ — ผมใช้ Qwen3.6-35B-A3B-FP8 บน DGX Spark มาเดือนกว่า ผ่าน vLLM v0.23.0 recipe ของตัวเอง throughput อยู่ที่ ~290 tok/s ที่ 12 parallel ก็ถือว่าใช้ได้ ไม่ได้แย่ แต่พอไปเจอ claim ใน Discord ว่า "NVFP4 + vLLM nightly ได้ 236.97 tok/s ที่ 10 concurrency" ก็เริ่มสนใจ

คำถามคือ — ทำไม NVFP4 ถึงเร็วกว่า? ทำไม nightly ถึงดีกว่า stable? และที่สำคัญที่สุด — NVFP4 แลกกับ quality เท่าไหร่?

บทความนี้เป็นบันทึกการทดสอบจริง ตั้งแต่ FP8 เดิม → ลอง Atlas (Rust engine) → กลับมาใช้ vLLM + NVFP4 — พร้อมบทเรียนเรื่อง architecture mismatch ที่เจอตอนลอง Atlas

บทความก่อนหน้า: Qwen3.6-35B Sampling Guide — พื้นฐานการเลือก temperature/top_p สำหรับงานต่างๆ

สรุปความเร็ว — เทียบทุก config ที่วัดจริง

ConcurrencyvLLM FP8 (เดิม)vLLM NVFP4 (ใหม่)Atlas NVFP4ที่ดีที่สุด
1 (single)55.4 tok/s58.4 tok/s78.4 tok/sAtlas +41.5%
3106.0106.763.5vLLM NVFP4
6177.2169.065.6vLLM FP8
12292.8335.8 tok/s81.4vLLM NVFP4 +14.7%

นอกจากความเร็ว ยังได้ฟรี: memory ลด 75% (30-50 GB → 7.94 GB), context 2x (128K → 256K), reasoning 7/7 — รายละเอียดทั้งหมดอยู่ด้านล่าง

TL;DR

ทดสอบ Qwen3.6-35B-A3B บน DGX Spark เปรียบเทียบ FP8 vs NVFP4 และลอง Atlas (Rust engine) — สรุปคือ vLLM + NVFP4 ให้ throughput สูงสุด 336 tok/s @ 12 parallel พร้อม memory ลดลง 75% รองรับ context 256K และ reasoning quality ผ่าน 7/7 (รวม cognitive reflection trap) ส่วน Atlas เหมาะกับงาน single-stream low-latency มากกว่า เพราะ scheduler ออกแบบเพื่อ latency ไม่ใช่ batch throughput

จุดเริ่มต้น: FP8 ทำงานได้ดี แต่ไม่ flexible

recipe qwen3.6-35b-a3b-base-128k-v23.yaml ของผมใช้:

--quantization fp8 # FP8 weights
--kv-cache-dtype fp8 # FP8 KV cache
--max-model-len 131072 # 128K context
--gpu-memory-utilization 0.75 # ใช้ VRAM 75%
--speculative-config mtp K=2 # MTP speculative

ผลคือ 12 parallel = 293 tok/s aggregate, single stream = 55 tok/s. ใช้งานได้ แต่มีข้อจำกัด:

  1. 256K context ทำไม่ได้ — KV cache หมด
  2. Memory headroom น้อย — mem 0.75 = ใช้ไป 96 GB / 128 GB
  3. Container 20 GB — image ใหญ่, deploy ช้า
  4. v0.23.0 stable — features ใหม่ต้องรอ release

Atlas: pure Rust engine ที่ทุกคนบอกว่าเร็วกว่า vLLM

ก่อนจะลอง NVFP4 บน vLLM ผมไปเจอทางเลือกอื่นก่อน — พอไปอ่าน NVIDIA developer forum เจอ Atlas:

"Atlas is a pure Rust LLM inference engine with 20+ custom kernels compiled directly for SM121... 2.8x faster than NVIDIA's stock vLLM image."

จุดที่น่าสนใจ:

  • Pure Rust + CUDA — ไม่มี Python, ไม่มี PyTorch, ไม่มี torch.compile
  • Container 2.5 GB (vs vLLM 20 GB)
  • Cold start <2 min (vs vLLM ~10 min)
  • OpenAI + Anthropic API compatible
  • KV high-precision layers auto — เก็บ layers สำคัญใน BF16

ผมเลยลอง sparkrun run @atlas/qwen3.6-35b-a3b-nvfp4 --solo --port 8002 แล้ว benchmark:

ParallelAtlas NVFP4vLLM FP8ผลต่าง
178.4 tok/s55.4Atlas +41.5%
363.5106.0vLLM +67%
665.6177.2vLLM +170%
1281.4292.8vLLM +260%

Atlas ชนะที่ single-stream (+41.5%) แต่ vLLM ชนะ concurrent (3.6x ที่ 12 parallel)

ทำไมถึงเป็นแบบนั้น?

บทเรียน #1: bottleneck ของ Atlas อยู่ที่ scheduler ไม่ใช่ memory

ลองปรับ Atlas recipe เพื่อเพิ่ม concurrent:

  • kv_cache_dtype: nvfp4 (ครึ่ง memory)
  • max_num_seqs: 12
  • max_batch_size: 16
  • gpu_memory_utilization: 0.75
  • ssm_cache_slots: 0

ผลคือ เหมือนเดิมเป๊ะ — 78 tok/s ที่ 1 parallel, 81 tok/s ที่ 12 parallel

ค้นดูใน Atlas GitHub issues + forum thread พบว่า:

"Atlas optimized for low-latency single-stream, not batch throughput" "Slai scheduling + TBT deadline=100ms kills slow batches aggressively"

Atlas ใช้ slai policy ที่ optimize สำหรับ token-by-token latency ต่ำ (100ms deadline). เวลา request ใน batch ช้า จะถูก kill ทันที → effective batch ไม่ scale.

Note: ไม่ใช่ทุก engine optimize เหมือนกัน

vLLM ใช้ continuous batching — ส่ง request เข้า batch ตลอดเวลา ทำให้ aggregate throughput scale เกือบ linear. Atlas เลือก SLAI scheduling — latency ต่ำแต่ throughput จำกัด. ไม่มี engine ที่ "ดีกว่า" ในทุก use case

NVFP4: weights เล็กลงครึ่ง แต่ throughput สูงกว่า

หลังจาก Atlas ไม่ตอบโจทย์ concurrent workload ผมกลับมาที่ vLLM และลอง recipe ที่ claim 236.97 tok/s:

# Qwen3.6-35B-A3B-NVFP4-MTP.yaml
model: RedHatAI/Qwen3.6-35B-A3B-NVFP4
runtime: vllm
builder: eugr
container: ghcr.io/spark-arena/dgx-vllm-eugr-nightly:latest
mods:
- mods/fix-qwen3-coder-next
- mods/fix-qwen3.6-chat-template
defaults:
max_model_len: 262144 # 256K!
gpu_memory_utilization: 0.5 # ครึ่งเดียว!
kv_cache_dtype: fp8
speculative_config: '{"method": "mtp", "num_speculative_tokens": 2}'
load_format: instanttensor
tool_call_parser: qwen3_coder
reasoning_parser: qwen3

ผลลัพธ์:

ParallelvLLM NVFP4vLLM FP8 (เดิม)ผลต่าง
158.455.4+5.4%
3106.7106.0+0.7%
6169.0177.2-4.6%
12335.8292.8+14.7%

335 tok/s ที่ 12 parallel — เกิน claim 236.97 ของ community

บทเรียน #2: memory footprint ต่างกันมาก

ตรวจ container memory ตอน serve:

MetricFP8 (เดิม)NVFP4 (ใหม่)ผลต่าง
Container MEM30-50 GB7.94 GiB-75%
Model weights~35 GB~16.3 GB-53%
System RAM used~70 GB69 GiBเท่าเดิม
Mem %~30%6.52%-77%

NVFP4 weights = 4-bit = ครึ่งหนึ่งของ FP8. เมื่อ weights เล็กลง KV cache + batch processing มี room เหลือเยอะ — เลย scale concurrent ได้ดีกว่า.

# ตรวจ memory ตอน serve
docker stats sparkrun_xxx_solo --no-stream
# ได้ MEM USAGE 7.94GiB / 121.7GiB (6.52%)

บทเรียน #3: NVFP4 ไม่ทำให้ reasoning แย่ลง

หลังจาก migrate เป็น NVFP4 ผม test reasoning quality ด้วย 7 คำถามมาตรฐาน — GSM8K math, formal logic, common sense, และ cognitive reflection trap (คำถามที่คนส่วนใหญ่ตอบผิด):

#ประเภทคำถามคำตอบที่ถูกModel ตอบผล
1mathJanet ขายไข่: 16-3-4=9 ฟอง × $2 = ?$18$18
2logicตะกร้า 5 แอปเปิ้ล หยิบ 3 ออก "คุณ" มีกี่ลูก33 (ไม่ใช่ 2)
3mathซื้อบ้าน $80K + ซ่อม $50K มูลค่าเพิ่ม 150% กำไรเท่าไหร่$70,000$70,000
4logic"ดอกกุหลาบทุกดอกเป็นดอกไม้" จริงไหมYesYes
5logic-trapไม้เบสบอล+ลูก $1.10 ไม้แพงกว่า $1 ลูกราคา?$0.05$0.05 (ไม่ใช่ $0.10!)
6common-sensevacuum ลดเวลาครึ่งหนึ่ง ปกติ 60 นาที → ?3030 นาที
7formal logic"กุหลาบทุกดอกเป็นดอกไม้, ดอกไม้บางชนิดเหี่ยวเร็ว → กุหลาบบางดอกเหี่ยวเร็ว" ได้ไหมNoNo (syllogism invalid)

Score: 7/7 (100%) — รวม cognitive reflection trap (ข้อ 5) ที่คนส่วนใหญ่ตอบผิด

ตัวอย่างเต็มของข้อ 3 (Josh flipping house):

Step 1: Total Cost = $80,000 + $50,000 = $130,000
Step 2: New Value = $80,000 + (150% × $80,000) = $200,000
Step 3: Profit = $200,000 - $130,000 = $70,000

คำตอบตรงกับ expected เป๊ะ พร้อม step-by-step reasoning

วิธีใช้ reasoning model กับ vLLM

Qwen3.6 เป็น reasoning model — output มี 2 fields:

  • content: คำตอบสุดท้าย
  • reasoning: thinking process (อาจยาวหลายร้อย tokens)

ถ้าอยากได้คำตอบตรง ๆ ไม่ต้องดู reasoning → เพิ่ม flag นี้ใน request:

{
"chat_template_kwargs": {"enable_thinking": false}
}

หรือถ้าอยากได้ reasoning process ด้วย → ตั้ง max_tokens: 2048+ (ไม่งั้น content จะ null เพราะถูกตัดตอน reasoning ยังไม่จบ)

เทียบ reasoning latency

Question typeเวลาเฉลี่ย
Simple (Q2, Q6)1.2 - 2.0s
Multi-step math (Q1, Q3)3.8 - 7.5s
Formal logic (Q4, Q7)5.1 - 7.4s

ทุกคำถามตอบใน < 8 วินาที ถือว่า interactive-friendly

FP8 vs NVFP4: ตัวไหนฉลาดกว่า?

จาก model card ของ NVIDIA เทียบ BF16 baseline กับ NVFP4:

BenchmarkBF16NVFP4Delta
MMLU Pro85.685.0−0.6
GPQA Diamond84.984.8−0.1
AIME 202589.288.8−0.4
τ²-Bench Telecom95.594.7−0.8
SciCode40.840.6−0.2
IFBench62.362.8+0.5
MMMU Pro74.174.5+0.4

NVFP4 ต่างจาก BF16 แค่ 0.1–0.8 คะแนน และบาง task ยังได้สูงกว่าด้วย

ส่วน independent benchmark จาก sch0tten/nvfp4-benchmark ที่เทียบ BF16 / FP8 / INT4-AWQ / NVFP4 บน Blackwell 96GB โดยตรง พบว่า:

  • FP8 เสีย quality น้อยที่สุด — MMLU-Pro delta แค่ −0.1 ถึง −0.7 จาก BF16
  • NVFP4 สำหรับ Qwen3.6-35B-A3B (MoE) เสียแค่ −0.5 คะแนน MMLU-Pro จาก BF16 — อยู่ใน noise floor (test-retest drift ~0.35 คะแนน)
  • ความแตกต่างระหว่าง FP8 กับ NVFP4 สำหรับ MoE model แทบไม่ต่างกันเลย เพราะ MoE กระจาย quantization error ไปทั่ว expert pool
  • quality cost ของ 4-bit ไปอยู่ที่ knowledge (MMLU-Pro) เท่านั้น — math (GSM8K), code (HumanEval/MBPP), instruction-following ไม่ต่างจาก BF16

Note: NVFP4 quantization ไม่กระทบ reasoning capability

ผล test ของผม (7/7) สอดคล้องกับ benchmark ทางการ — NVFP4 เสีย quality จาก BF16 แค่ < 1 คะแนน และสำหรับ MoE model อยู่ในระดับ noise. FP8 ฉลาดกว่า NVFP4 เล็กน้อย แต่ไม่มีนัยสำคัญสำหรับ Qwen3.6-35B-A3B. ส่วน claim ของ Atlas team ว่า "FP8 garbled, NVFP4 valid" น่าจะเป็นปัญหาเฉพาะของ Atlas engine ไม่ใช่ปัญหาของ FP8 format เอง

บทเรียน #4: max_num_batched_tokens = sweet spot ที่ 32768

หลังจาก NVFP4 stable ผมลอง tune --max-num-batched-tokens จาก 32768 → 65536 ด้วย sparkrun override:

sparkrun run recipes/Qwen3.6-35B-A3B-NVFP4-MTP.yaml \
-o max_num_batched_tokens=65536 --port 8001

ผล benchmark (apples-to-apples, prompt 500 tokens × 12 reqs):

Parallel32768 (default)65536 (override)Δ
1 (12 reqs back-to-back)258.5263.2+1.8%
3318.7324.6+1.8%
6322.5321.2-0.4%
12318.2321.3+1.0%

±1-2% = noise range — 65536 ไม่ได้ throughput win

ทำไมเพิ่ม batched_tokens ไม่ช่วย?

ทั้ง 32768 และ 65536 plateau ที่ ~320 tok/s aggregate — bottleneck อยู่ที่อื่น:

  1. Scheduler limit concurrent requests ก่อน batched_tokens จะเต็ม
  2. MTP acceptance ~88% — speculative decoding ทำงานเต็มที่แล้ว batch ใหญ่ขึ้นไม่ช่วย
  3. KV cache bandwidth — อาจเป็นคอขวดจริงที่ GPU

การเพิ่ม batched_tokens 32K→65K ใช้ memory มากขึ้นแต่ไม่ได้ throughput → rollback เป็น 32768

Lesson: ไม่ใช่ทุก "น่าลอง" จะคุ้ม

ผม assume ว่า batched_tokens สูงขึ้น = throughput สูงขึ้น — แต่จริง ๆ bottleneck อยู่ที่ scheduler + MTP ไม่ใช่ batch size. ลองแล้วไม่คุ้ม rollback ได้ ไม่ต้อง force commit recipe default ไว้ที่ 32768 ดีที่สุด

สรุปการเปลี่ยนแปลง: ก่อน vs หลัง

Note: nightly + mods = แลกระหว่าง features กับ stability

eugr nightly มี optimizations ใหม่กว่า stable — แต่อาจ break. mods (fix-qwen3-coder-next, fix-qwen3.6-chat-template) fix known issues. ผมเก็บ recipe qwen3.6-35b-a3b-base-128k-v23.yaml ไว้เป็น fallback ถ้า NVFP4 พัง

เทียบ NVFP4 vs FP8 ทุกมิติ (สรุป)

MetricvLLM FP8 (v23, เดิม)vLLM NVFP4 (eugr, ใหม่)Δ
Throughput @ 12 parallel292.8 tok/s335.8 tok/s+14.7%
Throughput @ 6 parallel177.2 tok/s169.0 tok/s-4.6%
Memory (container RSS)30-50 GB7.94 GiB-75%
Memory (model weights)~35 GB~16.3 GB-53%
Context length128K256K+100%
Reasoning quality~BF16 (NVIDIA eval)7/7 (GSM8K + logic)✅ verified
Cold start~10 min~3 min (InstantTensor)-70%
VRAM headroom @ mem=0.5N/A (mem 0.75 used)~68 GB free

NVFP4 ชนะ FP8 ครบทุกมิติ ที่สำคัญที่สุดคือ context length เพิ่มเป็น 256K (จาก 128K) — เปิดทางให้ process เอกสารยาว ๆ หรือ reasoning หลาย round โดยไม่ต้อง chunk

วิธีรัน: แนะนำให้ศึกษาจาก sparkrun

สำหรับวิธีการ deploy recipe ทั้งหมดบน DGX Spark — ตั้งแต่ install, configure ไปจนถึง serve — แนะนำให้ศึกษาจาก sparkrun (spark-arena/sparkrun) ซึ่งเป็น CLI launcher ที่ผมใช้จริง มี documentation ครบทั้ง installation, recipe format, และ deployment options

recipe ที่ใช้ในบทความนี้คือ Qwen3.6-35B-A3B-NVFP4-MTP.yaml (config แสดงไว้ด้านบน) — ปรับแต่งตามสภาพแวดล้อมของคุณเองได้

สรุป: ทำไม NVFP4 ถึงดีกว่า (สำหรับ use case ของผม)

Use caseWinnerWhy
Single chat / agentAtlas (NVFP4, 78 tok/s)low latency, fast first token
Batch processing (backtest)vLLM NVFP4 (336 tok/s @ 12p)continuous batching
256K contextvLLM NVFP4256K works, FP8 ไม่ได้
Memory-constrainedvLLM NVFP4 (7.94 GB container)เหลือ memory เยอะ
Reasoning tasksvLLM NVFP4 (7/7 verified)reasoning quality intact

ผมเลือก vLLM NVFP4 เพราะ workload หลักคือ bot4k backtest — concurrent throughput สำคัญกว่า single-stream latency.

ข้อควรระวัง

  1. eugr nightly unstablenightly tag เปลี่ยนได้ทุกวัน. ถ้า deploy ใหม่อาจ break. ควร pin commit hash
  2. mem 0.5 ต่ำมาก — ทำงานได้ดีกับ NVFP4 (16 GB weights) แต่ถ้าใช้ model ใหญ่กว่า (122B+) อาจต้องเพิ่ม
  3. Atlas ยังไม่ fit งาน concurrent — แต่ถ้าใช้ interactive เดี่ยว Atlas คือตัวเลือกที่ดีที่สุดตอนนี้ (78 tok/s, 1/8 image size)
  4. Reasoning model latency — reasoning tasks ใช้เวลา 5-8s ต่อ request (vs < 2s สำหรับ simple chat). ถ้าใช้ใน hot path ที่ต้องการ response เร็ว ควร disable thinking ด้วย chat_template_kwargs: {enable_thinking: false}
  5. batched_tokens = 32768 sweet spot — อย่า override เป็น 65536 หรือสูงกว่าโดยไม่จำเป็น. bottleneck อยู่ที่ scheduler/MTP ไม่ใช่ batch size. ใช้ recipe default ไว้

อ้างอิง

Appendix: Model Properties (live from API)

ดึงจาก GET /v1/models ของ endpoint ที่ใช้จริง (http://10.0.0.246:8001/v1):

PropertyValue
Model IDRedHatAI/Qwen3.6-35B-A3B-NVFP4
Objectmodel
Owned byvllm
RootRedHatAI/Qwen3.6-35B-A3B-NVFP4
Parentnull
Max model length262144 (256K tokens)
CreatedUnix timestamp 1782025982 (~2026-06-19)

Permissions

PermissionValue
allow_create_enginefalse
allow_samplingtrue
allow_logprobstrue
allow_search_indicesfalse
allow_viewtrue
allow_fine_tuningfalse
is_blockingfalse
organization* (public)
Raw JSON response
{
"id": "RedHatAI/Qwen3.6-35B-A3B-NVFP4",
"object": "model",
"created": 1782025982,
"owned_by": "vllm",
"root": "RedHatAI/Qwen3.6-35B-A3B-NVFP4",
"parent": null,
"max_model_len": 262144,
"permission": [
{
"id": "modelperm-ac67af075b13dfcf",
"object": "model_permission",
"created": 1782025982,
"allow_create_engine": false,
"allow_sampling": true,
"allow_logprobs": true,
"allow_search_indices": false,
"allow_view": true,
"allow_fine_tuning": false,
"organization": "*",
"group": null,
"is_blocking": false
}
]
}
แชร์บทความ

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

Buy Me a Coffee
Loading...