~/blog/dgx-spark-deepseek-v4-flash-284b-ds4-engine

DeepSeek-V4-Flash on DGX Spark · part 1

[地端 LLM] 我在 128GB 小盒子上跑了 284B 的 DeepSeek-V4-Flash,然後錯怪了 2-bit

cat --toc

TL;DR

DeepSeek-V4-Flash 是一顆 284B 的 frontier 模型。我在一台 128 GB 的 DGX Spark(GB10)上把它跑起來了——用 antirez 手寫的 ds4 引擎 + 非對稱 Q2 的 GGUF(整顆約 80 GB),decode 15.3–15.6 tok/s。一路上最有意思的不是數字,是一個被我錯怪的結論:我本來以為它「假裝呼叫工具、自己捏結果」是 2-bit 量化太爛,後來才發現真兇是 runtime 沒接 DSML parser——DeepSeek-V4 的工具呼叫用 DSML(XML-like)不是 JSON,模型吐對了,只是沒人解析。換上對的引擎,2-bit 照樣會用工具。

本文重點,一圖看懂

白話版:在一台桌上型小主機上跑「不該跑得動」的東西

DeepSeek-V4-Flash 是中國 DeepSeek 出的一顆非常大的 AI 模型——2840 億參數,正常是要一整櫃的伺服器才跑得動。我手邊只有一台桌上型的小主機(DGX Spark,128 GB 記憶體)。

直覺會說:不可能,塞不下。但有個技巧叫「量化」——把模型的數字壓小,像把一本精裝書印成口袋本,內容還在、體積變小。壓到夠小,這顆 284B 就剛好能擠進 128 GB。

這篇是一個晚上的探索紀錄。我把它跑起來、量了速度、接成我每天在用的 AI 助理。中間還鬧了個笑話:我一度以為這顆模型「不會用工具、只會演戲」,花了好一番功夫才發現,不是模型笨,是我用的軟體沒看懂它說工具的方式。把不該跑得動的東西跑起來,即使結論是「慢」,過程本身就很好玩。


前言

每次規格表都會騙你。看到 128 GB unified memory,你會以為自己終於能跑大模型了;真的去跑,才知道「塞得下」跟「跑得好」是兩回事。

這是 DeepSeek-V4-Flash on DGX Spark 系列的第一篇。起點是某個晚上我累了,隨口問自己一句:「DSv4 塞進 GB10 到底值不值得?」——本來只是想關掉電腦睡覺,結果一路做到端到端上線。這篇講「能不能跑」跟那個被我錯怪的工具呼叫;Part 2 講把它當每天的腦要做的記憶體跟 context 工程;Part 3 講為什麼一顆 284B 砍到 2-bit,品質還是頂得住。

Phase 0:我不用自己刻 5 個 CUDA op,社群做完了

第一個反應是「這要自己刻 kernel 吧」。DeepSeek-V4 的架構有壓縮 KV、sparse routing,不是隨便一個 runtime 載得起來的。

結果查一輪發現,我什麼都不用刻:

  • antirez(對,寫 Redis 那個 antirez)寫了一個專門跑 DeepSeek-V4-Flash 的引擎,叫 ds4,有 CUDA 後端、原生看得懂 DSv4 的工具格式。
  • huihui 已經出了 abliterated(拿掉拒絕方向)的 DSv4 GGUF,還是 ds4 吃得下的非對稱 Q2 layout。

換句話說,最難的部分——把 frontier 架構搬到單卡、又壓到塞得下——別人已經走過。我的工作只剩編譯、跑、踩坑。這就是 phase0 的價值:動手之前先查,常常發現輪子早就有人造好了。

網路上第一個 GB10 實測,卡在一個 stale nvcc 的坑

ds4 的 build 指令乾淨:

git clone https://github.com/antirez/ds4 && cd ds4
make cuda-spark            # 別自己加 -arch,ds4 說這樣最快

模型不用自己量化,ds4 自帶 download_model.sh 幫你下。我這台 128 GB 用 q2-imatrix:

./download_model.sh q2-imatrix    # 96/128 GB 機器;下到 ./gguf/ 並把 ./ds4flash.gguf 指過去
./download_model.sh mtp           # 順便下 MTP draft(要玩投機解碼才需要)

這顆是 antirez 自己的 stock q2-imatrix(約 80 GB,從 antirez/deepseek-v4-gguf 下)。我 daily 用的是 huihui 的 abliterated 版——同樣的非對稱 Q2 layout、但拿掉了拒絕方向——那顆要另外去 huihui-ai/Huihui-DeepSeek-V4-Flash-abliterated-ds4-GGUFHuihui-DeepSeek-V4-Flash-BF16-abliterated-ds4-Q2.gguf(86.7 GB),serve 時 -m 指那個檔就好。下面的速度數字 stock 跟 abliterated 兩顆都量了。

但第一次 build 撞牆。系統裡有個 stale 的 /usr/bin/nvcc(12.0)把我裝的 13.2 shadow 掉了,編到一半噴 math-vector.h __SVFloat32_t undefined。先確認 which nvcc 指到 13.2 那顆(不是被 stale 的 /usr/bin/nvcc 12.0 蓋掉),才編得過。後來起 server 又踩第二個坑:make cuda-spark 用的是 nvcc 13.2,build 出來的 binary 在出廠的 13.0 driver 上直接跑會噴 the provided PTX was compiled with an unsupported toolchain

修法是裝 cuda-compat-13-2(NVIDIA 官方的 forward-compat,只裝一個套件、不碰 kernel driver、零 reflash 風險),起 ds4 時帶上:

sudo apt install cuda-compat-13-2          # 只裝這一包,不動 kernel driver
LD_LIBRARY_PATH=/usr/local/cuda-13.2/compat ./ds4-server --cuda \
  -m ./ds4flash.gguf --ctx 131072 --power 100 --warm-weights \
  --host 0.0.0.0 --port 8000

# ds4 沒有 /health route(會回 404),看 log 出現 listening on 為準,或打 /v1/models:
curl -s http://localhost:8000/v1/models

這招以後任何「13.2+ 編的東西要在出廠 580 driver 上跑」都能用,不用冒險升 driver(DGX Spark 升 driver 是會 boot loop 要 USB 救援的高風險路)。

還有一個容易踩的旋鈕:--power。ds4 預設就是 100(全速),不用特別設;但有些社群配方會照抄成 --power 75,那會直接掉 25%(11.5 對 15.3 tok/s)。確認你跑的是 100。--warm-weights 則是先把權重灌進 GPU 再開始服務,省掉第一個請求的冷啟尖峰。

15.6 tok/s,六個配置全部釘在同一個數字

跑起來之後,我把所有旋鈕掃了一輪。結果有點好笑——不管怎麼轉,速度都一樣:

配置decode tok/s
原版 stock Q215.56
原版 + MTP draft 115.58
原版 + MTP draft 214.48
huihui abliterated Q215.3
huihui + MTP draft 115.63
huihui + MTP draft 214.44

六個配置——換原版、換 abliterated、開投機解碼、關投機解碼——全部釘在 15.6 附近。在 ds4 這顆引擎上,這就是天花板:GB10 的記憶體頻寬 273 GB/s,要餵 80 GB 的權重,每秒就只能吐這麼多 token。(嚴格說是「ds4 的天花板」——換引擎、換 kernel 數字會動,這點我在 Qwen3.5-122B 那篇用 Atlas 的例子講。)

兩個值得記的點。一,投機解碼(MTP)在這裡賺不到錢。draft 1 幾乎零增益,draft 2 反而掉到 14.5——而 draft 2 變慢恰好證明機制真的有在跑(有 engage 才有 overhead),draft 1 的零增益是真的 break-even,不是沒啟動。在一台被頻寬卡死的盒子上,投機解碼多吃的頻寬剛好等於它賺回來的,連本都打平。

二,abliteration 在這裡不收速度稅。原版只比 abliterated 快 ~1.7%(noise 級)。我之前在 Gemma 上量過 abliteration 收 -50% 的 chat 吞吐,差別在哪?瓶頸不同——算力受限才付 abliteration 稅,頻寬受限不付。

tool-use 偵探弧:我錯怪了 2-bit

這是整晚最值得寫的一段。

一開始我送 DSv4 一個 weather tool 的請求,它回了一段看起來像在呼叫工具、但其實沒有真的呼叫的東西——tool_turns=0,參數對不上,像在「演戲捏結果」。我的第一個結論很自然:2-bit 量化把它砍笨了,連工具格式都吐不對

這個結論錯了一半。方向對(輸出確實沒被當成工具呼叫),歸因錯(不是 2-bit)。

真相是:DeepSeek-V4 的工具呼叫不用 JSON/ChatML,用 DSML——一種 XML-like 的標記語言,長這樣:

<|DSML|tool_calls><|DSML|invoke name="get_weather">
<|DSML|parameter name="city" string="true">台北</|DSML|parameter>
</|DSML|invoke></|DSML|tool_calls>

我當時跑的那個 llama.cpp fork 沒有 V4 的 DSML parser(upstream 只認 V3.2 的 function_calls,不認 V4 的 tool_calls,相關 PR closed 沒 merge)。模型其實正確吐出了 DSML,只是 runtime 沒人解析,當成普通文字 → tool_turns=0 → 看起來像演戲。

鐵證在量化表。huihui 用的非對稱 Q2 是這樣分配精度的:

精度
routed ffn gate/up expertsIQ2_XXS
routed ffn down expertsQ2_K
shared experts / attention / output headQ8_0
embedding / router / indexerF16

被砍到 2-bit 的全是「量大但每個只處理一小撮 token」的 routed experts;跟工具格式、routing、attention 相關的層全留 Q8/F16。2-bit 根本碰不到吐 DSML 的那些層。

換上 ds4 引擎(它原生有 DSML render + parse)重跑,工具呼叫立刻正常:get_weather(台北),結構化 tool_calls,一次就中。金句:我錯怪了 2-bit——真兇是 runtime 沒接 DSML parser。換上對的引擎,2-bit 照樣會用工具。

上線:接成光羽的大腦

最後一步是把它接成我每天用的 agent(我叫它「光羽」)。serve 配方就是上面那段,client 端用 model=deepseek-chat 走 non-thinking 模式——一來繞過一個 thinking mode 會吞掉 DSML 的已知 bug,二來日常對話更直接。

ds4 還有個我意外喜歡的東西:KV disk cache。它會把算過的 prefix KV 寫進 NVMe,下次同樣開頭的對話直接從碟撈,不用重算。這部分我留到 Part 2 細講,因為它是「把 frontier 模型當每天的腦」的關鍵工程。

收穫

最花時間的地方

不是編譯,是那個 tool-use 的錯誤歸因。我盯著「假裝呼叫工具」的輸出,腦袋自動補了「2-bit 太爛」的故事,差點就這樣寫進結論。真正解開是去翻 DeepSeek-V4 的 encoding 文件、發現 DSML 的存在,才意識到問題在 runtime 不在模型。從一個輸出現象跳到精度結論,中間漏掉了「runtime 有沒有解析這個格式」這一層。

帶得走的排查方法

看到模型「會講話但不會用工具」,先別怪精度。先確認:這個模型用什麼格式做 tool call?你的 runtime 認得這個格式嗎?DSML / function_calls / OpenAI tool_calls 是不同的 wire format,parser 對不上就會表現成「模型不會用工具」,但模型其實吐對了。

通用原則

錯誤歸因比錯誤本身貴。 一個「2-bit 太爛」的結論會讓你去換模型、加大精度、浪費一堆下載頻寬;一個「runtime 缺 parser」的結論只要換引擎。在下「模型笨」的判決前,先排除掉工具鏈每一層有沒有把模型的輸出看懂。

TL;DR

  • DeepSeek-V4-Flash(284B)在單台 GB10 上跑得動:ds4 引擎 + 非對稱 Q2(~80 GB),15.3–15.6 tok/s。
  • 速度是物理天花板:六個配置全收斂 15.6,MTP 在頻寬受限下賺不到錢,abliteration 不收速度稅。
  • tool-use「演戲」是錯怪 2-bit:真兇是 runtime 沒接 DSML parser,換 ds4 後工具呼叫正常。
  • 上線細節(KV disk cache、context 工程)看 Part 2;為什麼 Q2 品質還頂得住看 Part 3

Also in this series: Part 2 — 把 15 tok/s 的 284B 當每天的 agent 大腦:怎麼設才舒服 · Part 3 — 權重就是正義:284B 砍到 2-bit 還是強

常見問題

DeepSeek-V4-Flash 能在單台 DGX Spark(GB10)上跑嗎?
能,但要用 antirez 寫的專用引擎 ds4,加上非對稱 Q2 量化(routed experts 砍到 IQ2_XXS/Q2_K、attention 跟 output 留 Q8/F16)的 GGUF,整顆約 80 GB。在 GB10 上 decode 大約 15.3–15.6 tok/s。upstream llama.cpp 跑不起來,因為它沒有 V4 的模型載入器;vLLM 只吃 FP4/FP8 safetensors,單台塞不下。
為什麼 DeepSeek-V4-Flash 一開始看起來不會用工具?
因為它的 tool call 用的是 DSML(一種 XML-like 標記)而不是 JSON,而早期我跑的 llama.cpp fork 沒有 V4 的 DSML parser。模型有正確吐出 DSML,但 runtime 當成普通文字,所以看起來像在『假裝呼叫工具、自己捏結果』。換上有 DSML parser 的引擎後,工具呼叫立刻正常。不是 2-bit 量化的問題。
Q2 量化會不會讓模型變笨?
看怎麼量。DeepSeek-V4-Flash 用的是非對稱 Q2——只把『量大但每個只處理一小撮 token』的 routed experts 砍到 2-bit,跟 tool 格式、推理品質相關的 attention/shared/output 層全留 Q8/F16。我接成 daily agent 跑了約 30 小時、280 輪,繁中流暢、工具呼叫結構完整、零退化。
100B+ 的大模型在 GB10 上能跑多快?
看引擎。DeepSeek-V4-Flash 在 ds4 上約 15.6 tok/s,瓶頸是 GB10 的 273 GB/s 頻寬餵不動幾十 GB 權重。但「13–17 tok/s」這個區間有一半是 kernel 不成熟造成的,不是純頻寬物理——換成為該架構寫了原生 sm_121 kernel 的引擎,同一顆大模型可以快上一倍以上。要互動速度就挑 active 小的 ~30B MoE(NVFP4 約 67 tok/s)。