Skip to main content

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

· 13 min read

ผมใช้ 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

พอไปอ่าน 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 สูงกว่า

กลับมา 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 ดีที่สุด

เทียบ workflow: ก่อน 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

Setup ที่ใช้ตอนนี้

# 1. Install sparkrun (บน DGX)
export PATH="$HOME/.local/bin:$PATH"
which sparkrun # v0.2.38

# 2. Save recipe ที่ ~/spark-vllm-docker/recipes/
ls ~/spark-vllm-docker/recipes/Qwen3.6-35B-A3B-NVFP4-MTP.yaml

# 3. Deploy
ssh [email protected] 'sparkrun run \
~/spark-vllm-docker/recipes/Qwen3.6-35B-A3B-NVFP4-MTP.yaml'

# 4. Verify (port 8001)
curl -s http://10.0.0.246:8001/v1/models | jq
# → id: "RedHatAI/Qwen3.6-35B-A3B-NVFP4"
# → max_model_len: 262144

สรุป: ทำไม 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 ไว้

อ้างอิง

แชร์บทความ

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

Buy Me a Coffee
Loading...