DeepSeek-V4-Flash-0731 (UD-IQ4_NL) — SSD→RAM MoE 專家分頁實驗

這個 repo 不含權重。權重在 unsloth/DeepSeek-V4-Flash-0731-GGUF 的 UD-IQ4_NL/ 目錄(4 個 shard,136,662,446,656 bytes ≈ 127.27 GiB)。

這個 repo 放的東西:

  1. sdq-dsv4/ — 改寫後的推論平台(SDQ expert pager 適配 deepseek4)
  2. results/ — 多組 RAM 預算實驗的完整結果與驗證證據
  3. HARDWARE.md — 硬體與執行環境(每個數字附量測指令;哪些是硬限制、哪些只是預算)
  4. results/upstream_hardware_audit.md — 對上游 HelloSun/sddqwen35a3b_v01 硬體/環境/容量敘述的審查與勘誤(含 9 條硬性錯誤、6 條內部矛盾、6 條不清晰敘述)

上游是 HelloSun/Qwen3.8-Flash-Next-GGUF 的 sdq-qwen38/(同一套 SDQ 架構,目標模型是 qwen4exp)。本 repo 是把同一套架構 搬到 deepseek4 之上,並且真的把它改到能跑、能出正確答案。


1. 為什麼需要這個

DeepSeek-V4-Flash-0731 UD-IQ4_NL 有 127.27 GiB,其中:

類別 大小 佔比 說明
MoE routed expert 權重 120.00 GiB 94.3% 43 層 × 256 expert,每 token 只用 6 個
非 expert 權重 7.27 GiB 5.7% attention 5.0 GiB、shared expert 1.07 GiB、token_embd 0.52 GiB、router 0.09 GiB、其餘 norm/hyper-connection
合計 127.27 GiB

「每層 256 個 expert、每 token 只用 6 個」代表 97.7% 的 expert 權重在任何給定 時刻都不會被碰到。把 120 GiB 的 expert 權重留在 SSD、RAM 只放有限大小的 expert arena,是把這個模型塞進小機器的正確做法。這就是 SDQ(SSD→RAM→CPU expert pager)的原始設計。

本機實測:RAM 預算 4 GiB(arena 2.90 GiB = 266 個槽位)時,整個 127.27 GiB 的 模型跑在 peak RSS 10.3 GiB 裡面,並且正確回答 The capital of France is → capital of France is **Paris**.


2. 模型規格(從 GGUF metadata 實讀)

項目 值
架構 deepseek4
層數 43(另有 3 層 hash-routing)
expert 數 / 每 token 用 / shared 256 / 6 / 1
hidden size 4096
expert FFN size 2048
單一 expert 11.125 MiB(42 層)/ 12.75 MiB(layer 26)
原生 context 1,048,576
attention head 64 / kv head 1 / key·value len 512,另有 indexer(top-k 512)、compress_ratios {0, 4, 128}、hyper-connection 4 路
MoE 門控 expert_gating_func = 4(sqrt-softplus)、expert_weights_scale = 1.5、expert_weights_norm = 1
gate/up 量化 IQ3_S(42 層)/ MXFP4(layer 26)
down 量化 MXFP4(全部 43 層)
swiglu clamp routed 與 shared 皆是 10.0(每層一個值)

⚠️ 三個和 qwen4exp 版本不一樣、而且每一個都會讓推論直接算錯的地方: IQ3_S / MXFP4 這兩個量化型別、ggml_swiglu_clamp 的夾法、以及 shared expert 的存在。詳見第 5 節。


3. 實驗結果

完整規格、每個數字的量測指令、以及「哪些是硬限制、哪些只是預算」的說明, 全部在 **HARDWARE.md**。摘要:

CPU Intel Xeon Platinum 8559C,2 socket × 48 core × 2 thread = 192 logical CPU 可見
實際算力配額 cgroup v2 cpu.max = 1600000 100000 → 16.0 CPU(實驗用 -t 16)
實體 RAM 2,000.0 GiB = 1.953 TiB(MemTotal 2,097,117,272 kB)
cgroup v2 memory.max 104,000,000,000 B = 96.86 GiB
實際綁住本專案的上限 /root/memkill.py 80 2 64 → 單一子行程 64 GiB、全部子行程合計 80 GiB、每 2 秒取樣、SIGKILL
儲存 / = overlayfs,底層 nvme0n1 = Amazon Elastic Block Store(網路區塊裝置,不是本地 SSD)7.8 TB
磁碟單流 dd oflag=direct 寫 1.1 GB/s、讀 2.0 GB/s
cgroup 檔案系統 唯讀 → 無法為每一級建立獨立限額

設定:ctx=2048、ubatch=128、-t 16、temp=0、每級 2 輪。 編譯:GGML_NATIVE=OFF + 明確列 AVX512/VNNI/BF16,AMX 關閉(見第 5 節 (e))。

三個容易搞混的地方,這裡先講清楚:

  1. nproc 在這台機器上回 16,是因為環境變數 OMP_NUM_THREADS=16, 不是因為配額或 affinity(sched_getaffinity 回 192)。
  2. 指標名稱沿用 SDQ 原版的 SSD MiB/tok,但這裡的「SSD」是 overlayfs 底下的 EBS 網路裝置,量到的是這個容器讀檔案的速率(單流上限 2.0 GB/s)。
  3. --ram-budget-mb 是 pager budget,不是 OS 限額;真正會殺行程的是 memkill.py 的 64 GiB 單行程上限。這也是本 repo 沒做「原生路徑 vs 分頁路徑」 端到端 A/B 的原因(原生路徑要 127.27 GiB 進 RSS,一定被殺)。
RAM 預算 arena (GiB) slots decode tok/s prefill tok/s hot % cold % SSD MiB/tok 全程 SSD 讀取 peak RSS (MB) swap 每層命中率 min..max
4 GiB 1.79 164 0.69 1.65 0.0 100.0 2944.4 161.8 GB 2233 0 MB 0.0..0.0
8 GiB 5.79 531 1.40 1.65 42.3 57.7 1451.6 93.3 GB 6325 0 MB 3.5..58.0
16 GiB 13.79 1264 1.63 1.75 54.5 45.5 1021.8 73.6 GB 14508 0 MB 15.1..65.9
32 GiB 29.79 2732 1.71 1.80 63.9 36.1 690.7 58.4 GB 30894 0 MB 32.4..73.2
  • hot % = expert 已在 RAM arena、不需要碰 SSD;cold % = 必須回 SSD pread。
  • 重點:127.27 GiB 的模型在 2.2 GiB peak RSS 裡跑得動(0.69 tok/s), 給到 30.9 GiB 得到 1.71 tok/s(2.48×)、SSD 讀取量降到 23.5%。
  • 4 GiB 那一級 hot = 0% 不是 bug:一個 token 在 43 層各用 6 個 expert, 工作集下限就是 43 × 6 = 258 個 expert ≈ 2.8 GiB,而這一級的 arena 只有 1.79 GiB → 任何一次請求都必然是 miss。
  • RAM 預算是 pager budget,不是 OS 硬性限額:本機 /sys/fs/cgroup 唯讀 (echo 0 > memory.high 直接 Read-only),cgroup.subtree_control 為空, 沒辦法為每一級建立獨立的 cgroup 限額,所以 4/8/16/32 GiB 是「把預算交給 pager 去算 arena 大小」,不是四個各自受限的容器。各級的實際 peak RSS 都如實列出, 全部遠低於 memory.max 的 96.86 GiB 與 memkill 的 64 GiB 單行程上限。
  • swap 全程 0 MB:SDQ 不用 swap,權重資料只存在 arena(實體 RAM)與檔案(SSD)兩處。
  • tok/s 與 RAM/SSD/CPUs 綁定,不可跨機器移植。

原始逐輪資料:results/memory_levels_dsv4-4-8-16-32g.json、 說明:results/memory_levels_dsv4-4-8-16-32g.md。


4. 怎麼跑

4.1 取得權重(127.27 GiB)

hf download unsloth/DeepSeek-V4-Flash-0731-GGUF --include "UD-IQ4_NL/*" \
   --local-dir ./models

4.2 建引擎

git clone https://github.com/ggml-org/llama.cpp && cd llama.cpp
git checkout 836d57176dc699a726c55418e4f96b8ca628e1bf
git apply ../sdq-dsv4/0001-sdq-llama-glue.patch          # llama.cpp 側的掛鉤
cp ../sdq-dsv4/sdq_pager.h ../sdq-dsv4/sdq_pager.cpp \
   ../sdq-dsv4/sdq_moe.cpp ../sdq-dsv4/sdq_buft.cpp \
   ../sdq-dsv4/sdq_cli.cpp src/                          # SDQ 主體(直接覆蓋)
git submodule update --init --recursive

# x86:明確指定 ISA,不要用 -march=native(原因見第 5 節 (e))
cmake -B build -DCMAKE_BUILD_TYPE=Release -DLLAMA_CURL=OFF \
      -DGGML_NATIVE=OFF \
      -DGGML_AVX=ON -DGGML_AVX2=ON -DGGML_F16C=ON -DGGML_FMA=ON -DGGML_BMI2=ON \
      -DGGML_AVX512=ON -DGGML_AVX512_VNNI=ON -DGGML_AVX512_BF16=ON
cmake --build build -j --target sdq-chat

**為什麼不要 -march=native**:Sapphire Rapids 以後的 Xeon,-march=native 會直接 定義 __AMX_INT8__/__AVX512VNNI__,於是 llama.cpp 註冊 AMX extra buffer type, 然後在 deepseek4 上撞 ggml_backend_sched_split_graph 的 assert(第 5 節 (e))。 明確列 ISA 就不會踩到 —— AMX 的三個 CMake 選項本來就預設 OFF,而 SDQ 本來就 不走 repack,關掉 AMX 沒有任何損失。

三個 x86 基線都實測過(-t 16,16 GiB 預算,32 個生成 token,各 3 輪):

ISA 設定 decode tok/s(3 輪) 結果
-DGGML_AVX2=ON(Haswell+) 1.16 正確輸出,無 assert
AVX512 + VNNI + BF16(Skylake-X / Ice Lake+) 1.48 / 1.10 / 1.18 正確輸出,無 assert
-march=native -mno-amx-*(同一台機器) 1.71 / 1.09 / 1.03 正確輸出,但需要那個 -mno- hack

三者的差距落在同一組 run 的抖动裡(build 那一列自己就在 1.03–1.71 之間跳), 所以用明確的 AVX512 設定不會比 -march=native 慢。ARM / 其他架構請用 llama.cpp 自己的預設值(不要帶任何 GGML_NATIVE 或 AVX 選項)。

4.3 跑(RAM 預算單位 MiB)

./build/bin/sdq-chat \
  -m ./models/UD-IQ4_NL/DeepSeek-V4-Flash-0731-UD-IQ4_NL-00001-of-00004.gguf \
  -p "The capital of France is" -n 48 -c 2048 -t 16 -b 128 --temp 0 \
  --ram-budget-mb 8192 --no-interactive --stats-file /tmp/stats.jsonl

--ram-budget-mb 是總 RAM 預算(不是「還有多少可用」):pager 會扣掉當下的 RSS 與一份預留量(SDQ_RESERVE_MB,預設 900 MB)之後,剩下的才是 arena。

重跑記憶體實驗:

python3 sdq-dsv4/memory_levels_dsv4.py \
  --model ./models/UD-IQ4_NL/DeepSeek-V4-Flash-0731-UD-IQ4_NL-00001-of-00004.gguf \
  --bin   ./llama.cpp/build/bin/sdq-chat \
  --levels-gib 4 8 16 32 --repeat 2 --n-predict 48

5. 改寫了什麼(以及踩到什麼坑)

改寫基於 HelloSun/Qwen3.8-Flash-Next-GGUF 的 sdq-qwen38/,共 5 處必要修正。 每一處都是「不改就跑不起來或跑錯」,不是風格調整。

(a) IQ3_S / MXFP4 的量化點積

SDQ 的 dot_for() 只認 Q2_K…Q8_0 / Q4_0 / Q5_0 / Q5_1 / IQ4_NL / IQ4_XS。 這個模型的 routed expert 是 IQ3_S(gate/up)與 MXFP4(down)→ 每一層都會印 不支援的專家量化型別 然後什麼都不算。

修正:加上

權重型別 vec_dot activation 型別
GGML_TYPE_IQ3_S ggml_vec_dot_iq3_s_q8_K GGML_TYPE_Q8_K
GGML_TYPE_MXFP4 ggml_vec_dot_mxfp4_q8_0 GGML_TYPE_Q8_0

配對不能自己猜,必須照 ggml 的 type_traits (ggml/src/ggml-cpu/ggml-cpu.c):IQ3_S.vec_dot_type = Q8_K、 MXFP4.vec_dot_type = Q8_0。特別注意 IQ3_S 落到 default: 會被配成 Q8_0, 而它的 6-bit scale 組出來的值範圍大於 q8_0 的 ±127 —— 不改就是整層數值錯掉。

(b) ggml_swiglu_clamp 的夾法

deepseek4(以及 glm5_next / maple / dflash / hy_v4)走的是 ggml 的融合算子 ggml_swiglu_clamp(ggml/src/ggml-cpu/ops.cpp):

const float gate = std::min(src0_p[k], limit);              // 只夾上界
const float up   = std::clamp(src1_p[k], -limit, limit);    // 對稱
dst_p[k]         = gate / (1.f + expf(-gate)) * up;         // 夾在 silu 之前

SDQ 原版夾的是 silu 之後 的 gate、而且是對稱的:

gc = clamp(silu(gate), -limit, limit);                     // 夾在 silu 之後、對稱
out = gc * clamp(up, -limit, limit);

兩者不是同一個函式(夾的位置不同、gate 的邊界條件不同)。

修正:把夾法做成參數 sdq_build_moe(..., swiglu_limit, swiglu_mode), 由 build_moe_ffn() 依 arch 決定(0001-sdq-llama-glue.patch 裡那段 switch 就是照著 LLM_FFN_SILU 的分支寫的)。deepseek4 走 SDQ_SWIGLU_GGML_FUSED_CLAMP。

誠實補充:這個模型的 clamp 值是 10.0,而 silu(x) ≤ x, 所以在 g ≥ limit 的區域兩種寫法只差 silu(10) = 9.9999 對 10 (約 0.001%),g ≤ -limit 的區域兩者都趨近 0。也就是說這一項在這個模型上 是 1e-4 等級的差異,不是災難。仍然照上游寫是對的(正確的公式不該是 「差不多對的公式」),但不要把它講成修了一個大 bug。

(c) shared expert 不進 arena(而且確認沒有被吃掉)

deepseek4 每層有 1 個 shared expert(expert_shared_count = 1, ffn_{gate,up,down}_shexp)。SDQ 的 build_moe_ffn() 掛鉤是提早 return的 ——對 qwen4exp(沒有 shared expert)沒問題,對 deepseek4 就會把 shared expert 整段跳過。

檢查結果(llama.cpp src/models/deepseek4.cpp):shared expert 是在 build_moe_ffn() 之外算的獨立在線

ggml_tensor * moe_out = build_moe_ffn(...);           // ← SDQ 在這裡提早 return
ggml_tensor * ffn_shexp = build_ffn(cur, layer.ffn_up_shexp, ..., il);
cur = ggml_add(ctx0, moe_out, ffn_shexp);             // ← 兩邊都保留

build_ffn() 只用一般的 ggml_mul_mat(不是 mul_mat_id),所以提早 return 不會影響它。SDQ 的 buffer override 正則 blk\.[0-9]+\.ffn_(gate|up|down|gate_up)_exps\.weight 也不會誤配到 _shexp(那裡要求後面接 _exps.weight)。

這一項是「確認不用改」,但確認的過程比改動本身更花時間,所以把它寫下來。

(d) hash-routing 的前 3 層

hash_layer_count = 3:前 3 層的 expert 選擇不是 argsort_top_k 算出來的, 而是 get_rows(ffn_gate_tid2eid, tokens) 從一張 [6, 129280] 的查表得到 (src/models/deepseek4.cpp)。這兩條路徑產生的 ids 形狀與型別完全相同 (I32 [n_expert_used, n_tokens]),SDQ 的 op 是照 src[1] 的 nb[0]/nb[1] 讀的,所以不需要改。差異只在「有沒有 routing bias」,而 bias 是在 ids 之前 由 ggml 圖算完的。

(e) AMX 必須關掉(否則 deepseek4 在這版 llama.cpp 上起不來)

這不是 SDQ 的問題,是 llama.cpp 836d5717 加上 AMX repack 時的問題:

ggml_backend_sched_split_graph: GGML_ASSERT(*cur_backend_id != -1) failed
  unassigned node: op=MUL_MAT ne=[1024,128,8,1] name='attn_wo_a-0'
    src[0] q8_0 buft=AMX
    src[1] f32 name='attn_derope-0 (reshaped) (permuted)'   ← 非 contiguous

ggml_backend_cpu_device_supports_op() 在看到 src 落在 extra buffer type (AMX / REPACK)時會直接把決定權交給該 buffer type 的 supports_op(), 而 AMX 的實作要求 ggml_is_contiguous(src1)。deepseek4 的 attn_output_a(低秩輸出投影)拿到的是 de-rope 之後的 permute view, 不 contiguous → AMX 回 false → 整個 op 沒有任何 backend 能接 → assert。

本機 CPU 有 amx_int8/amx_bf16/amx_tile,而 **-march=native 會直接定義 __AMX_INT8__/__AVX512VNNI__**,於是 AMX extra buffer type 被註冊 (ggml/src/ggml-cpu/amx/amx.cpp 用 #if defined(__AMX_INT8__) && defined(__AVX512VNNI__) 註冊)。

修正有兩種,擇一:

  • 推薦(4.2 採用):不要用 -march=native,改成明確列 ISA (-DGGML_NATIVE=OFF -DGGML_AVX512=ON ...)。AMX 的三個 CMake 選項本來就 預設 OFF,libggml-cpu.so 裡連 "AMX" 這個字串都不會出現,自然不會註冊。
  • 如果你一定要 -march=native:加 -DCMAKE_C_FLAGS="-mno-amx-int8 -mno-amx-tile -mno-amx-bf16"(CXX 同樣)。

兩種都實測過,輸出與 tok/s 都一樣(第 4.2 節的表)。SDQ 本來就不走 repack (expert 權重根本不進 RAM),關掉 AMX 沒有任何損失。

想自己重現這個 assert、又不想改編譯參數的話, sdq-dsv4/optional-sched-debug.patch 可以在 assert 前印出真正沒被接走的節點。

(f) 從 qwen4exp 版本繼承、但這個模型也會踩到的兩個坑

sdq-qwen38/ 已經修好的兩件事,deepseek4 的 GGUF 一樣會踩到,所以直接繼承:

  1. GGUF kv 解析器的型別寬度。DeepSeek 的 metadata 有 3 個 U16 (split.no / split.tensors.count / split.count),舊版 sdq_pager.cpp 把 U8/I8/U16/I16 都用 u32() 讀、且陣列元素遇到 F64 完全不消費位元組, 於是 header 整個錯位,症狀是 [sdq] pager 初始化失敗:basic_string::_M_create。
  2. sharded GGUF。這個模型是 4 個 shard,而且 shard 1 是 metadata-only (n_tensors = 0,整個檔案就是 5.0 MiB 的 tokenizer),權重分散在 shard 2/3/4,每個 shard 有自己的 data_start。只讀 shard 1 → page map 全空; 把 4 個 shard 串起來卻共用第一個 shard 的 data_start → 每個 expert 讀到 錯開約 18 KB 的位置,輸出是看似合理的亂碼。

6. 正確性驗證

6.1 dequant 逐位元組對照(獨立實作)

verify_moe_dsv4.py 裡的 IQ3_S / MXFP4 還原是照 ggml 原始碼手寫的 NumPy。 如果那份手寫有錯,後面所有「C++ kernel 對得上 NumPy」的結論都只是在 一份錯誤的 reference 上自我一致。所以用 ggml_deq(直接呼叫 ggml 的 C 函式) 當第二份獨立實作逐位元組比對:

c++ -O2 -o /tmp/ggml_deq sdq-dsv4/ggml_deq.cpp \
    -I<llama.cpp>/ggml/include -I<llama.cpp>/ggml/src \
    -L<llama.cpp>/build/bin -lggml-base -Wl,-rpath,<llama.cpp>/build/bin
python3 sdq-dsv4/verify_dequant_dsv4.py
張量 型別 max|NumPy − ggml|
blk.0.ffn_gate_exps IQ3_S 0.0
blk.0.ffn_up_exps IQ3_S 0.0
blk.0.ffn_down_exps MXFP4 0.0
blk.26.ffn_gate_exps MXFP4 0.0
blk.26.ffn_down_exps MXFP4 0.0

21/21 列 bit-exact(差異精確為 0,不是「很小」)。

6.2 整個 expert 前向對照

sdq-chat --selftest IL:IE:SEED:OUT.bin 產出 C++ kernel 的結果,再用純 NumPy 從 GGUF 的同一段位元組重算 SiLU-gated MoE 前向:

for spec in 0:7 1:7 2:123 3:255 12:7 25:7 26:7 27:7 41:7 42:7; do
  il=${spec%%:*}; ie=${spec##*:}
  ./build/bin/sdq-chat -m $MODEL --selftest "$il:$ie:1234:/tmp/st_l${il}e${ie}.bin:1" \
      --selftest-limit 10 --selftest-mode 1 --ram-budget-mb 4096 --no-interactive
done
DSV4_LIMIT=10 DSV4_MODE=1 python3 sdq-dsv4/verify_moe_dsv4.py
層 expert gate/up down max 相對誤差 相關係數 結果
0 7 iq3_s mxfp4 0.0099 0.999945 PASS
1 7 iq3_s mxfp4 0.0111 0.999949 PASS
2 123 iq3_s mxfp4 0.0100 0.999950 PASS
3 255 iq3_s mxfp4 0.0077 0.999948 PASS
12 7 iq3_s mxfp4 0.0082 0.999950 PASS
25 7 iq3_s mxfp4 0.0106 0.999941 PASS
26 7 mxfp4 mxfp4 0.0101 0.999947 PASS
27 7 iq3_s mxfp4 0.0082 0.999949 PASS
41 7 iq3_s mxfp4 0.0103 0.999942 PASS
42 7 iq3_s mxfp4 0.0115 0.999948 PASS

殘差是 activation 量化誤差(sdq 走 q8_K / q8_0 量化點積,參考實作走 fp32), 量級 0.8–1.2%,不是結構性錯誤。layer 26 是唯一 gate/up 換成 MXFP4 的層, 單獨列出來是因為它是 (a) 那個修正唯一的執行路徑。

6.3 分頁結果與 arena 大小無關(bit-exact)

SDQ_MOE_DUMP 會在 MoE op 真的算完的那一刻,把該次呼叫的輸入與輸出 (cur / ids / wts / out)寫成一個二進位檔。同一個 prompt、同一組設定, 只改 --ram-budget-mb:

範圍 檔案數 bit-identical
4 GiB vs 32 GiB,全部 43 層 × 9 次 op call(1 次 prefill n_tok=25 + 8 次 decode n_tok=1),含輸入與輸出 387 387 / 387

hot/cold 比例、SSD 讀取量、槽位數(164 vs 2732)、淘汰次數全部不同, 但每一層每一個 token 的 MoE 輸入與輸出位元組完全一樣。這是對「分頁有沒有偷偷 改變數學」最直接的證據。逐檔 md5 見 results/moe_dump_bitexact.md。

另外同一設定重跑 6 次、兩個 RAM 預算各 6 次,greedy(--temp 0)輸出 12/12 逐字相同(results/determinism.txt)。

6.4 誠實聲明:沒做的事,以及一個還沒解掉的問題

沒有做端到端 A/B(SDQ 分頁路徑 vs 原生 mul_mat_id 逐 token 比對文字)。 原因是這個模型在這台機器上沒辦法跑原生路徑 —— 原生路徑要把 127.27 GiB 權重 全部載入 RSS,遠超單一子行程 64 GiB 的上限。6.1–6.3 是目前能取得的最強證據。

還沒解掉的問題:decode 期的 greedy 輸出對行程狀態敏感。 固定設定下重跑 6 次輸出 100% 逐字相同;但只要改動與計算無關的東西 (--stats-file、SDQ_STATS_FILE、SDQ_MOE_DUMP、SDQ_FADV_DONTNEED=0、 SDQ_IO_SPLIT=2、-c、-b),第一個 token 就會換。-t 1(完全單執行緒) 也一樣會換,所以不是執行緒競爭。

而且分歧點已經定位到:把兩種設定的 MoE dump 逐一比對(43 層 × 17 次 op call = 731 個檔案),43 個 prefill 呼叫全部 bit-identical、688 個 decode 呼叫全部不同 (results/moe_dump_bitexact.md §B)。分歧已經出現在 MoE op 的輸入 cur 裡, 也就是說:

  • 不是 SDQ 的分頁或 kernel(§6.3 已經證明分頁對數學是 bit-exact 的);
  • 不是每個 expert 的請求數對不上 —— expert_requests == expert_hits + expert_misses 永遠成立,沒有任何 expert 被跳過(acquire() 拿不到槽位時是 abort(), 不是靜靜跳過);
  • 是 llama.cpp deepseek4 的 decode 期 dsv4 CSA/HCA 壓縮 KV state 路徑, 在這兩種設定下算出了不同的 cur。SDQ 的 op 只是忠實地把它算下去。

而且所有觀察到的變體在語意上都是對的(capital of France is **Paris**. / Paris. / 'm happy to help! The capital of France is **Paris**.), 這個 prompt 的 top-1/top-2 本來就非常接近。

要真的定位到 llama.cpp 那一側,需要用 sanitizer build 重跑(prefill 一致、 decode 不一致這個現象很適合用 MSan/valgrind 抓),本 repo 沒做到。 這件事會影響任何想用這個 port 做 token 級 A/B 的人,所以寫在這裡。


7. 檔案

sdq-dsv4/
  sdq_pager.h / sdq_pager.cpp   expert pager(sharded GGUF、修正後的 GGUF parser、大小兩種槽位)
  sdq_moe.cpp                   自訂 MoE kernel(新增 IQ3_S/MXFP4、ggml_swiglu_clamp 語意)
  sdq_buft.cpp                  讓 expert 權重不進 RAM 的假 buffer type
  sdq_cli.cpp                   sdq-chat 前端(新增 --selftest-limit/-mode/-xscale)
  ggml_deq.cpp                  抽 ggml dequant 結果的小工具(驗證用)
  ggml_tables.py                從 ggml 原始碼抽出的 IQ3_S/MXFP4 常數表(由 gen_tables.py 產生)
  gen_tables.py                 產生上面那個表的小工具
  verify_dequant_dsv4.py        NumPy dequant vs ggml dequant,逐位元組
  verify_moe_dsv4.py            expert 前向 vs 純 NumPy
  memory_levels_dsv4.py         RAM 預算矩陣 runner
  determinism_probe.sh          決定性探針(同一設定重跑 N 次比對輸出)
  0001-sdq-llama-glue.patch     llama.cpp 側的掛鉤(build_moe_ffn / init_mappings / CMake)
  optional-sched-debug.patch    選用:sched assert 前印出沒被接走的節點
HARDWARE.md                            硬體與執行環境(每個數字附量測指令)
results/
  upstream_hardware_audit.md           對上游 sddqwen35a3b_v01 的硬體/環境敘述審查與勘誤
  memory_levels_dsv4-4-8-16-32g.json / .md   記憶體實驗(原始逐輪資料 + 表格)
  verify_dequant.txt             NumPy dequant vs ggml dequant 的逐筆輸出(21/21 bit-exact)
  verify_moe_forward.txt         expert 前向 vs 純 NumPy 的逐筆輸出(10/10 PASS)
  moe_dump_bitexact.md           MoE op 的 bit-exact A/B(387/387 與分歧點定位)
  determinism.txt                決定性探針(同一設定重跑 6 次 × 2 個預算)

重跑驗證

export DSV4_MODEL_DIR=./models/UD-IQ4_NL      # verify_moe_dsv4.py 的權重位置

# (1) dequant bit-exact(需要先 build ggml_deq,見 6.1)
python3 sdq-dsv4/verify_dequant_dsv4.py

# (2) expert 前向(先跑 sdq-chat --selftest 產生 /tmp/st_l*e*.bin,見 6.2)
DSV4_LIMIT=10 DSV4_MODE=1 python3 sdq-dsv4/verify_moe_dsv4.py

# (3) MoE op bit-exact A/B(見 6.3 的 SDQ_MOE_DUMP 用法)
bash sdq-dsv4/determinism_probe.sh 8192 32768

8. 授權

權重沿用 unsloth/DeepSeek-V4-Flash-0731-GGUF 的授權(license: mit,見上游)。 本 repo 的程式碼沿用 SDQ 原專案 (HelloSun/sddqwen35a3b_v01) 與 HelloSun/Qwen3.8-Flash-Next-GGUF 的授權。

Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Model tree for HelloSun/deepseekv4flashgguf

Finetuned
(1)
this model