~/blog/glm53-flash-one-dgx-spark

DGX Spark · part 44

[Benchmark] GLM-5.3-Flash 塞進一台 DGX Spark:108.72 GB、15.5 tok/s、視覺能用

cat --toc

TL;DR

單台 DGX Spark(121 GB)跑得動 GLM-5.3-Flash:UD-Q2_K_XL 108.72 GB,ready 85 秒,英文散文 15.40 tok/s、中文 16.23。中文比英文快 5.39%,因為 MTP 的 draft 接受率高了 5 個百分點。原生視覺可用,OCR 逐字全中 —— 但要拿掉 --spec-type,兩者現在不能並用。唯一會讓你白忙的是 CUDA toolkit:12.x 炸在 warmup、13.2 不報錯但輸出亂碼,只有 13.0 對。

DGX Spark 系列 #44 封面:一台小型銀色桌上超級電腦,上方一個遠大於它的半透明神經晶格被壓縮、匯聚注入機身,晶格中隱約有一枚眼睛虹膜的形狀。

前言

新模型發布那天最掃興的一句話,是「你的機器不夠大」。

GLM-5.3 發布後我去查能不能塞進那台 DGX Spark,查到的第一篇說不行、要兩台。那篇的作者在文章裡自己寫了 "we have not run GLM 5.3 Flash on Spark hardware ourselves" —— 那是估算,不是實測。

我把它跑起來了,一台,108.72 GB。這篇是完整的配方加上跑出來的數字,包含一個到現在還沒人公開量過的東西:它的原生視覺在單台上能不能用。

前面幾集在同一台機器上跑過 DeepSeek-V4-FlashQwen3.8-Flash-Next,文末有同機對照表。

旗艦塞不下,Flash 塞得下:216.72 GB vs 108.72 GB

下面的數字是拿 HuggingFace API 逐檔加總出來的,不是「參數量 × 位元數」的估算:

最小的 GGUF121 GB 塞得下?
旗艦 GLM-5.3(744B-A40B)UD-IQ1_S 216.72 GB❌ 差將近兩倍
GLM-5.3-Flash(320B-A18B)UD-IQ1_S 93.09 GB

Flash 不是旗艦的縮小版,是另一套基底模型。config.json 裡寫的是 glm5_next / Glm5NextForConditionalGeneration:288 個 routed 專家加 1 個 shared,每個 token 取 8 個;45 層,層型是 linear_attention 三層配 deepseek_sparse_attention 一層循環;帶一層 MTP(num_nextn_predict_layers: 1);max_position_embeddings 是 1,048,576。

還有 vision_config,所以它原生就是多模態的。授權是 MIT,旗艦那邊是 GLM-5.3 License。

UD-Q2_K_XL,不選官方建議的 IQ3_XXS

121 GB 的記憶體預算:Q2_K_XL 108.72 GB 留下 12.3 GB 給 KV,IQ3_XXS 120.37 GB 只剩 0.6 GB 連 32K KV 都不夠

unsloth/GLM-5.3-Flash-GGUF 的階梯:

量化大小121 GB
UD-IQ1_S93.09 GB
UD-IQ1_M97.58 GB
UD-Q2_K_XL108.72 GB✅ 本文用這個
UD-IQ3_XXS120.37 GB⚠️ 權重就吃光了
UD-Q3_K_XL 以上147.54 GB+

Unsloth 自己的文件建議 128 GB 機器用 UD-IQ3_XXS。那個建議拿權重去對總記憶體,漏了 KV cache 這一項。120.37 GB 的權重放進 121 GB 的機器,剩下的空間連 32K 的 KV 都不夠,更別說 262K。

Q2_K_XL 載入之後 free -g 是 used 107 / available 13-14,沒有逼近上限。這 12 GB 的差距就是能不能開長 context 的差別。

另外還要抓一個 mmproj-F16.gguf(1.13 GB),視覺那節會用到。

唯一會讓你白忙的地方:CUDA toolkit 必須是 13.0

三個 CUDA toolkit 版本對應三種結局:12.x 炸在 warmup、13.2 不報錯但輸出亂碼、13.0 正確

動手前先對這張表:

項目要求怎麼驗
GPUGB10 / sm_121anvidia-smi
CUDA toolkit13.0<toolkit 路徑>/bin/nvcc --version 要回 13.0.x
統一記憶體121 GBfree -g
磁碟權重 109 GB + 餘裕df -h
作業系統aarch64 Linuxuname -m

toolkit 版本錯了會有兩種完全不同的症狀,而且第二種難抓得多:

toolkit症狀
12.x 標頭server 在 warmup 炸 CUDA error: invalid argument,連 ready 都到不了
13.2不報錯、正常啟動、正常回應 —— 但輸出是亂碼
13.0

第二種你會先懷疑模型、再懷疑量化、然後懷疑提示詞,而問題其實在編譯器。

給了 -DCMAKE_CUDA_COMPILER 還不夠

CMake 找 CUDA toolkit 的路徑,跟你指定的 compiler 是兩套獨立的搜尋。只給 compiler 的話,它會用你指定的 nvcc 去編,但標頭仍照自己的順序找,通常先撈到系統路徑下的舊版。

結果是新編譯器配舊標頭:cudaLaunchKernelExcudaLaunchConfig_t 佈局對不上,kernel launch 回 invalid argument,炸在 warmup 的第一個 soft_max。

兩個變數都要給:

git clone https://github.com/eauchs/llama.cpp.git
cd llama.cpp
# 這個 commit 已經不在該分支 head 的祖先鏈上,要明確 fetch 才拿得到
git fetch origin 8a8d0bcc4d5fdf024c457526245bec4bc3a12adc
git checkout 8a8d0bcc4d5fdf024c457526245bec4bc3a12adc

cmake -B build -DGGML_CUDA=ON -DCMAKE_BUILD_TYPE=Release \
  -DCUDAToolkit_ROOT=<你的 13.0 路徑> \
  -DCMAKE_CUDA_COMPILER=<你的 13.0 路徑>/bin/nvcc
cmake --build build --target llama-server -j 20

建完看這一行,它是唯一的驗收條件:

-- Found CUDAToolkit: <13.0 路徑>/targets/sbsa-linux/include (found version "13.0.88")

不是 13.0.x 就重來。cmake 會把它印出來,而它就在 -- The CUDA compiler identification is NVIDIA 13.0.88 的旁邊一行 —— 兩行不一致就是這個病。建置在 GB10 上花 166.57 秒。

ready 85 秒,262K 和 32K 一樣快

./build/bin/llama-server \
  -m <UD-Q2_K_XL 第一個 shard> \
  --host 127.0.0.1 --port 8010 \
  -ngl 99 -c 262144 -ctk q8_0 -ctv q8_0 \
  -fa on --jinja --spec-type draft-mtp

262144 和 32768 兩種 context 都是 85 秒 ready,載入時間不隨 context 變。

⚠️ 不要試 1M context。 統一記憶體機器沒有乾淨的 OOM 牆,超過之後是 swap thrash,整台機器會失去排程 —— ssh 過得了 banner 但命令送不進去。我的機器不在旁邊,那次等了很久。

環境逐字:

主機   NVIDIA GB10 · 121 GiB 統一記憶體 · aarch64
驅動   580.159.03 · CUDA toolkit 13.0.88
build  llama.cpp 0.3.0-dev (build 10677, commit 8a8d0bcc4)
       CMAKE_CUDA_ARCHITECTURES=121a-real
模型   unsloth/GLM-5.3-Flash-GGUF UD-Q2_K_XL,4 shards,108,720,071,427 bytes

中文比英文快 5.39%,而且看得出機制

同一顆 server、同一組參數,只換題目。max_tokens=1024temperature=0reasoning_effort="low",各跑三發:

語言decode tok/s(中位)rangeMTP 接受率completion tokens
EN15.4015.08-15.5136.01%333
ZH16.2315.83-16.6941.03%362

快的原因不用猜:MTP 的 draft 接受率高了 5.01 個百分點。接受得多,驗證回合就少,出字就快。三發合併之後數據一致(EN 364/1008 = 36.11%,ZH 431/1053 = 40.93%)。

temperature=0 的確定性通過了:EN 三份 content 的 SHA-256 相同,ZH 三份也相同。六發 finish_reason 全是 stop,沒有排除樣本。

這跟「CJK 一定比較慢」的直覺相反,所以要講清楚哪個數字才算數。

每秒字元數不能拿來當速度用

chars/s(中位)字元/token
EN64.884.4
ZH15.471.01

差四倍,但那不是速度差,是 token 密度加上模型自己選的篇幅(英文寫了 1,456 字元、中文 367 字元)。核心速度看 tok/s,chars/s 只能拿來估「你要等多久才看到成品」。

輸出是繁體,一整段只有一個錯字

對繁中場景這是決定性的一格。字形證據:燈塔、綢緞、皺褶、裡、鐘錶、闔上。

那篇散文的一段:

黎明前的海是墨色的,像一匹未經裁剪的綢緞,皺褶裡藏著整夜的風。

他站在燈塔頂端,看著燈火轉過最後一圈。那束光掃過海面時,浪頭便短暫地
亮了起來,像有誰在水底睏開了眼睛,又闔上。

「像有誰在水底開了眼睛」——應該是「睜」。整段就這一個字錯,而這是 2.4-bit 專家的代價,照實記。

寫 code 比寫散文快 52%

丟一題:用 Three.js 從 CDN 載入,只用方塊堆出一座天守閣 —— 石垣基座、逐層收分、每層出簷、頂上裝飾,配地面與光照,相機自動繞行。要一個開了就能看的單一 HTML 檔。

completion tokens2,136
wall92.06 s
decode23.64 tok/s
content5,249 字元,結尾有 </html>
一發就成是(finish_reason=stop)

對了的:石垣基座、三層逐層收分、每層出簷、白牆深瓦、地面光照、自動繞行,一眼認得出是天守閣。錯了的:頂上的裝飾生成了十字架,不是鯱也不是擬宝珠。日式天守閣頂著西洋十字 —— 提示詞只寫了 "decorative finial",這一半算題目沒講清楚。

工作decode tok/s
體素天守閣(Three.js)23.64
中文散文16.23
英文散文15.40

同一顆 server、同一組旗標,差別還是在接受率 —— code 的下一個 token 比散文好猜。

視覺可以用,但要先拿掉一個旗標

這是我最想知道的一格,因為社群沒有人公開量過。

視覺要換一支 build。unsloth 自己那支 PR 同時有模型架構和視覺投影器:

git clone -b glm5next/upstream https://github.com/unslothai/llama.cpp.git
cd llama.cpp && git checkout d07e71ede7

cmake -B build -DGGML_CUDA=ON -DCMAKE_BUILD_TYPE=Release \
  -DCUDAToolkit_ROOT=<你的 13.0 路徑> \
  -DCMAKE_CUDA_COMPILER=<你的 13.0 路徑>/bin/nvcc
cmake --build build --target llama-server llama-mtmd-cli -j 20

configure 4.87 秒、build 136.19 秒。上膛:

./build/bin/llama-server \
  -m <UD-Q2_K_XL 第一個 shard> \
  -ngl 99 -c 32768 -ctk q8_0 -ctv q8_0 -fa on --jinja \
  --mmproj <mmproj-F16.gguf 路徑>

注意沒有 --spec-type 那是必要條件,理由在文末的進階那節。ready 97 秒。

三層驗收:

題目結果
機制/v1/modelscapabilities: ["completion","multimodal"]
低標64×64 純紅 PNG,問它什麼顏色Red
高標1200×400 白底黑字 OCR讀出 THE SILVER ROBOT READS SEVEN BLUE MAPS.,編輯距離 0
文字沒退化The capital of France is**Paris**,finish_reason: stop

三個請求 HTTP 全部 200。高標那張圖的 ground truth 跨了兩次 run 的目錄,我用 sha256 確認是同一張才認那個 0 —— 跨 run 比對一定要驗 hash,不能靠檔名。

圖片被編碼進 context 的速度是 678 tokens @ 193.94 tok/s

同機四顆模型:它是最慢的那個

全部同一台 GX10、同樣英文散文:

模型量化decode tok/s
Qwen3.8-Flash-NextIQ1_S32.27
DeepSeek-V4-Flash-0731vLLM sparkinfer24.95
Qwen3.8-Flash-NextQ4_K_XL23.38
GLM-5.3-FlashQ2_K_XL15.50

散文上它是最慢的。它值錢的地方是原生多模態、1M 架構和 MIT 授權,不是速度。跨模型跨量化只能當數量級參考,別當精確對打。

拿來對外面比:NVIDIA 論壇上有一組 2 台 Spark 跑 vLLM NVFP4 的散文數字是 19.58 tok/s。我們一台拿到 15.40 / 16.23,一半的硬體換八成的速度。

⚠️ 社群那些 46.9 / 62.9 tok/s 的高分我不拿來對打,因為沒有一家公開提示詞。有個 repo 的作者自己寫了「不附提示詞的單流 tok/s 沒有意義」,那句話我同意。


進階:為什麼掛上圖片就 500?

不讀這節不影響使用,前面的配方照抄就能跑。這節是給想知道那個旗標為什麼要拿掉的人。

帶著 --spec-type draft-mtp 送圖片,兩個請求都回 HTTP 500:

E decode: failed to initialize batch
E spec  process: llama_decode(ctx_dft) head=0 failed rc=-1 (pos=36)
E srv   decode: failed to process speculative batch
   the last position stored in the memory module of the context (i.e. the KV cache)
   for sequence 3 is X = 19
   it is required that the sequence positions remain consecutive: Y = X + 1

兩次跑的差別只有這一個旗標,其餘逐字相同。

第一眼看到 500 很容易讀成「模型看不懂圖」,但錯誤裡點名的是 ctx_dft —— 那是 draft context,不是視覺路徑。目標那邊已經走到 36,draft 停在 19,差 17 格,正好是圖片佔掉的位置。

圖片以 embedding 進入主 context,佔掉 19-35 共 17 個位置;draft context 那邊因為 embd != nullptr 被 return true 跳過,KV 停在 19。下一個文字批次帶著位置 36 進來時,連續性檢查就失敗

差別在引擎,不是模型

llama.cpp 的投機解碼把 draft 放在第二個獨立的 llama_context(tools/server/server-context.cpp 裡的 llama_context * ctx_dft),它有自己的 KV cache。而圖片是以 embedding 進入主 context 的,process_mtmd_chunkmtmd_helper_decode_image_chunk(),位置推進量取自 mtmd_input_chunk_get_n_tokens()

同步 draft 的線其實接好了 —— 那個函式收一個 callback,裡面呼叫 common_speculative_process。所以這不是「沒做」,是那條路上有東西沒生效。

common/speculative.cpp 會看到四個用到獨立 draft KV 的實作,它們對圖片批次的處置分兩種:

實作對圖片(embedding)批次
draft_eagle3embd != nullptrreturn true 跳過 → draft KV 留洞
draft_mtp同上,而且前面有一行 // TODO: how to make it work with vision tokens?
draft_dflash / draft_dspark不跳過,但注入時把 M-RoPE 的多維位置壓成同一個純量
draft_simple沒有守衛。它的 draft() prompt 會經 get_text_tokens() 壓成純文字,但 process() 直接把收到的 batch 丟進 llama_decode —— 不能據此認為圖片批次被擋住了

五個 ngram 家族的 process()// TODO: implement; return true,構造上碰不到這題。

判準因此不是「你用哪種投機」,是這個模式有沒有自己的 draft KV。原始碼裡是明文的:add_config_if_enabled(..., params.draft.ctx_dft != nullptr) 只套在那四個上。Gemma-4 那類共用 target KV 的(is_mem_shared)也不會撞到。

vLLM 和 SGLang 的 MTP 讓 assistant layers 共用 target 的 KV,所以位置對不齊這一整類問題在那邊不存在 —— 但它們在多模態餵給 draft 的接縫上各自也有 open issue。分水嶺是引擎的 KV 設計,不是模型。

上游有修法,但被擋著

PR #25144 是一個 +16/-6、單一檔案的修正:給 draft 自己的位置空間,不再沿用 target 的。作者比了兩種做法,結果很清楚 —— 單純放寬位置檢查會不當機但接受率 0(洞還在,draft 沒用);重新編號才真的能用。

ggerganov 沒有合併它,原話是要先把 llama_batch 重構到能同時帶 vision 和 target embedding,「supporting this properly for all cases」。那張 PR 確實只修 draft_mtp,不碰 eagle3 也不碰 dflash,所以他的理由站得住。

我把那個 patch 套進我們的 build 量了一次。git apply --check 回 0,乾淨套用:

組別發數draft_naccepted接受率tok/s(中位)
散文3158558136.66%17.61
含圖續寫3110672465.46%21.52

圖片不再 500,散文的 36.66% 跟沒 patch 的 36.01% 幾乎一樣,文字端沒被傷到。

但六發長輸出裡 llama_decode[1] returned -1 出現 2,661 次,而 head=0 的失敗數是 0 —— 多頭 MTP 的第一顆頭以外全在報錯。同一個錯誤字串在沒 patch 的 log 裡命中 0 次,所以不能推給既有行為。那個 48.50% 的加權接受率是 server 真的回的 timings,可是實際上只有部分 draft 路徑在工作,還背著大量重試成本。

所以這個 patch 目前只能當實驗證據,還不能上線。要視覺就拿掉 --spec-type,那句話到今天為止還是對的。

進階:兩個會浪費你一小時的操作坑

reasoning_effort 一定要顯式給

不給的話,複雜的生成題會把整個 completion 預算燒在 thinking:

max_tokensreasoning 字元content 字元wall
8,19228,8510500 s
16,38454,84201,018 s

想了十七分鐘,一個字都沒開始寫。加上 reasoning_effort:"low" 之後同一題:reasoning 剩 1,155 字元、content 5,249 字元、92 秒完成。

headless Chrome 渲 Three.js 不能加 --disable-gpu

天守閣那題的產物要開來看才知道對不對,而 headless Chrome 加了 --disable-gpu 之後 Three.js 拿不到 WebGL,畫面全黑,而且畫面上沒有任何錯誤。要開 console 才看得到 THREE.WebGLRenderer: Error creating WebGL context.

正解:

--use-gl=angle --use-angle=swiftshader --enable-unsafe-swiftshader

一片空白很容易讓人以為「模型生的 code 壞了」,而那是渲染器的問題,不是模型的。

收工

一台 121 GB 的機器跑 320B 模型,結論是可以,而且不用退到 1-bit。要挑的話:量化選 UD-Q2_K_XL 別選官方建議的 IQ3_XXS(那個建議沒算 KV)、toolkit 只能是 13.0、-c 從 32768 起跳、要視覺就把 --spec-type 拿掉。

速度它是同機最慢的一顆,15.5 tok/s。但它是唯一一顆在這台機器上又能看圖、又是 MIT、又有 1M 架構的,而且中文比英文快。


同系列其他篇:Qwen3.8-Flash-Next:1770 億參數 + 262K 一起開 · DeepSeek-V4-Flash 284B Q2 的品質實測

常見問題

一台 DGX Spark 跑得動 GLM-5.3 嗎?
旗艦版跑不動,Flash 版跑得動。旗艦 GLM-5.3(744B-A40B)最小的 GGUF 是 UD-IQ1_S 216.72 GB,單台 121 GB 差了將近兩倍。GLM-5.3-Flash(320B-A18B)的 UD-Q2_K_XL 是 108.72 GB,載入後 used 107 GB,還留得下 262K 的 KV cache。
GLM-5.3-Flash 在 DGX Spark 上多快?
用 llama.cpp + UD-Q2_K_XL + MTP 投機解碼,英文散文 decode 15.40 tok/s、中文散文 16.23 tok/s、生成 Three.js 程式碼 23.64 tok/s。同一台機器上 DeepSeek-V4-Flash 是 24.95、Qwen3.8-Flash-Next IQ1_S 是 32.27,所以它強的地方不是速度。
為什麼掛上 mmproj 送圖片會回 HTTP 500?
因為同時開了投機解碼。llama.cpp 的投機把 draft 放在第二個 llama_context,而圖片是以 embedding 進入主 context 的,draft 那邊的 KV 位置追不上,批次建構時的連續性檢查就會失敗。拿掉 --spec-type 之後圖片請求全部回 200。

接著讀

不想錯過新文章?

訂閱我確保不漏接!

隨時一鍵退訂。