RTX 5090 Qwen3.8-27B dsh 全套實測:自寫推理引擎 NInfer × DeepSeek Harness × 開源編碼 agent 任務 —— 跑 13 小時真實編程任務的數據、6 個坑、以及「並發不是越大越快」
-
0. 先給結論
這篇跟論壇上其他 Qwen3.8-27B 帖的角度不同:那幾篇測的是「引擎快幾快」,這篇測的是「一整套本地 agent 環境在真實工作負載下體感如何」。全部數字都來自 2026-08 下旬的實際運行(含 13.7 小時、1,200+ 個真實請求的完整 log),測法都寫在後面,歡迎複現或打臉。
項目 結果 硬體 RTX 5090 32GB(單卡, --device 0)模型 Qwen3.8-27B NVFP4(20.02 GiB artifact) 單請求 decode(實測中位數) 143 tok/s(最低 48,最高 216) 98,919-token prompt 的 TTFT 23.3 秒(C=2 無排隊);同樣 prompt 在 C=4 擁堵時 140–190 秒 13.7 小時真實 agent 工作負荷 1,205 個請求 / 142.8M prompt tokens / 2.05M 生成 tokens,全部跑完 最大發現 真實 agent 負載 98.6% 是 prefill 工作量 → 最佳並發度從「sweep 裡的 4」翻轉成 2 能力 GPQA-Diamond 88.38%(NInfer 官方評估);結構化輸出 MTP 接受率 90.8% 一句話:5090 跑 Qwen3.8-27B NVFP4 做本地編程 agent,decode 速度不是瓶頸,TTFT 和 KV 容量規劃才是;整套環境可以用,但要把並發度和 context 開關想清楚。
1. 三個組件各自是什麼
組件 角色 說明 NInfer 推理引擎(GPU 端) from-scratch C++/CUDA 引擎,編譯目標 sm_120a / CUDA 13.1,只支援明確註冊的 checkpoint( .ninfer格式,closed set 設計,不做通用 runtime)。INT8 group-64 paged KV、CUDA Graphs、MTP 投機解碼、prefix reuse、OpenAI + Anthropic 兩套 HTTP APIDeepSeek Harness (DSH) agent harness(Web GUI) DeepSeek AI 的開源 agent 框架(MIT,developer preview),「everything is a plugin」架構。我拿它當編程 agent 的主運行時:subagent / goal / 任務排程都跑在它上面,LLM provider 指向我本機的 NInfer(OpenAI 兼容 /v1/chat/completions)編碼 agent 插件(VS Code) 編程 agent(同時是被開發對象) Roo Code 停更後由原貢獻者延續的開源專案。我在個人 fork 上做 task-tree 控制與取消系列(見第 6 節),本文的 13.7 小時 DSH 工作負荷就是開發這個插件時產生的 關係:NInfer 出 token,DSH 用 token 跑 agent,編碼 agent 插件是被開發的對象(同時是產品同類,對照它的任務模型來設計本地環境)。
2. 環境
項目 內容 OS Windows 11 + WSL2 Ubuntu(kernel 6.6.87.2-microsoft-standard-WSL2) CPU Ryzen 9 9950X3D 16C/32T GPU RTX 5090 32GB(主角;多卡機,其餘卡未使用, --device 0強制)驅動 610.88 模型 artifact qwen3_8_27b_nvfp4.ninfer,21,492,695,040 bytes(20.02 GiB)服務配置(最終) --max-context 196608 --kv-dtype int8 --vision --media-cache-mib 256 --media-live-mib 512 --max-concurrency 2 --spec mtp --draft-tokens 3 --lm-head-draft網路 WSL NAT → Windows 經 netsh portproxy暴露在家庭 LAN192.168.x.x:18080。auth 是關閉的——只敢放在可信任的家庭網路,見第 8 節3. 效能數據(全部附測法)
3-1. NInfer 官方發布數字(同顆 5090,INT8 KV + CUDA Graphs)
MTP3 飽和 decode(1 秒完整區間內 batch = 設定並發度):
C=1 C=2 C=4 C=8 C8/C1 143.8 tok/s(接受率 48.9%) 267.6 461.1 766.6 5.33× 單請求 corpus:7,680-token prompt → prefill 8,340 tok/s;260,096-token prompt → prefill 2,203 tok/s、decode 52.9(MTP0)。MTP3 結構化輸出 219.8 tok/s、接受率 90.8%、3.72 tokens/round。
注意 Qwen3.8 的 MTP 接受率(45–49%)明顯低於 Qwen3.6(67–71%)——看 3.8 的 aggregate 數字時要連接受率一起看,不是引擎退化,是這代模型草稿頭猜得較差。
3-2. 我自己在 WSL 的 makespan 掃描(2026-08-11)
測法:75 個固定 corpus 請求(seed 20260811),
--max-context 131072,mtp3/draft 3,量完整 makespan。C makespan aggregate decode 相對 C=1 1 4,530 s 166.0 tok/s 1.0× 2 2,538 s 285.7 1.78× 4 1,698 s 426.2 2.67×(最優) 8 2,285 s 326.3 1.98×(反而更慢) 這張表是 decode-dominated 負載下的標準答案:4 最優、8 反效果(batch 利用率掉到 2.29)。
3-3. 13.7 小時真實 agent 負載(本節全文最該看)
測法:2026-08-19 23:16 → 08-20 12:03,C=4、262K context、WSL。負載 = DSH 開發編碼 agent 時的多 subagent 編程請求(含 tool calls、thinking、100–241 則訊息的長 history),完整 server log 存檔。
指標 數值 完成請求數 1,205 prompt tokens / 生成 tokens 142.8M / 2.05M → 98.6% 是 prefill 工作 實際 makespan 13.7 小時 同負載若 C=1 順序跑(估算:solo prefill 3,500–5,000 tok/s + decode 166) 11.4–14.8 小時 per-request decode 實測 min 48 / median 143 / max 216 tok/s,沒有一個低於 48 TTFT(100K+ prompt) 138–193 秒 DSH 界面顯示的「速度」 11–23 tok/s 三個直接結論:
- C=4 和 C=1 總時間差不到 ±10%——因為 98.6% 的工作是 prefill,而 prefill 同一時間只有一個請求在做(log
prefilling=1),並發對它零幫助;decode 佔 1.4%,batching 優勢救不回來。3-2 的「C=4 最優」在這種負載下直接失效。 - 「51 tok/s」是顯示假象。DSH 的 tok/s = 輸出 token ÷ 總 LLM 時間(TTFT + 排隊 + decode),而 TTFT+排隊佔了 84–91% 的 LLM 時間。實際 decode 是 143,差 7 倍。不要拿 agent harness 顯示的速度去評 engine——跟參考帖「只報數字不報測法等於沒有資訊」是同一課。
- decode 從來沒有變慢(min 48 tok/s)。用戶體感的「有時 10–30 t/s」全部來自 prefill/排隊,不是生成階段退化。
4. KV cache / prefix reuse:整篇最實用的規劃方法
NInfer 的 paged KV pool 容量 =
--max-context(explicit 模式)。262K context + int8 KV = 10.45 GiB,weights 之後淨餘 10.47 GiB,零餘裕。而呢個 agent 的 prompt 中位數是 117,841 tokens(window 的 45%)。實測 reuse 分佈(1,157 個請求):
prompt 尺寸 full_reset(重做全部 prefill) 有 cache hit <50K 39% 60% 50–100K 78% 21% 100–160K 97% 2% ≥160K 100% 0% 條件很直白:C 個 frontier 同時駐留需要的容量 = C × 中位 prompt。C=4 × 117K = 470K > 262K pool → 不斷逐出 → 全部 full_reset。小 context(<50K)重用得好好的(97–99% hit),所以引擎機制本身沒問題,是容量規劃問題。
兩個推論:
- 縮 context 救不了:agent 會按比例 compaction,prompt 永遠是 window 的 ~45%,比例不變,C=4 永遠 miss。
- 正確做法是 C=2(2 × 86K = 172K < 192K pool)。改成 C=2 + 192K 之後實測:98,919-token prompt TTFT 23.3s(純 prefill,無排隊),decode 184 tok/s。對比同尺寸 prompt 在 C=4 擁堵時的 140–193s,6–8 倍差距。
DSH 多開 3–4 個 subagent 不用怕——第 3、4 個只在 FIFO 排隊(pending timeout 600s 不會 expire),排到時 frontier 還在 pool 裡,TTFT 很快。
5. 踩過的坑(誠實記錄)
# 坑 現象 正解 1 引擎預設 max_concurrency=1+ pending timeout 30sDSH 多請求並發時全部 expired while waiting for admission--max-concurrency N --pending-timeout-ms 600000;N 按第 4 節規劃2 DSH「緩存命中」永遠 0% 兩層原因疊加:(a) NInfer 的 chat-completions usage 原本不報 cached tokens(Responses API 有報);(b) 即使報了,第 4 節的逐出會令它真的是 0 (a) 小 patch 補 prompt_tokens_details.cached_tokens(~20 行 + schema 測試),端到端驗證 DSH 記錄到非 0 cacheReadTokens;(b) 靠 C=23 用 DSH 顯示速度評估 engine 「51 tok/s 好慢」→ 白排查一圈 decode 路徑 看 server log 的 per-request ttft=/decode=;顯示值含 TTFT+排隊4 KV pool = max_context,不是「越大越好」 262K context 在 32GB 卡上是零餘裕狀態,任何附加分配(vision)都放不進去 按「C × 中位 prompt ≤ pool」反推 context 5 vision 與 262K 不能共存 10.45 GiB KV + 預設 media buffer(1G+2G)> 10.47 GiB 淨餘,啟動直接 reject --max-context 196608 --media-cache-mib 256 --media-live-mib 512→ fit,剩 489 MiB slack6 「C=8 更快」的直覺 3-2 掃描 C=8 已比 C=4 慢 23%;真實負載 8 個並發 = 純排隊 + 逐出加劇 本負載的最優 C 是 2,不是 8 6. 編碼 agent 插件:task-tree 控制系列 + 進行中的 abort signal 工作
動機很具體:本地 27B 跑多 subagent 編程時,任務樹會長得很深很長,上游的任務模型缺「深度控制」和「可靠取消/檢查點」。我在個人 fork 上做了 9 個 PR(5 個核心 + 4 個跟進,全部合併):
類別 內容 深度控制 任務嵌套深度追蹤(cycle-safe backfill)+ schema; maxNestingDepth/autoFlattenOnLimit設定項 round-trip;達深度上限時 subtask 自動 inline flatten取消與恢復 取消時級聯中斷 live children + 歷史樹顯示深度;修跨 interrupt/resume 的 delegation link 遺失 檢查點 手動對話 checkpoint 儲存 + trigger 方法 呈現 i18n 補齊;把深度/parent id 給到 model 看;inline 轉換顯示為獨立 chat banner 目前進行中:abort signal 的端到端 plumbing——由核心任務運行時,經 provider 橋接層(OpenAI provider 整合、pass-through providers),到 prompt 補全(complete-prompt)的取消與 config builder。動機:長本地多 subagent 運行下,取消路徑要真正 reach 住嘅 HTTP 請求同 provider 流,唔係只係將任務狀態標死;呢個系列同上面嘅 task-tree 控制係同一條線(深度限制 + 級聯取消 + 可恢復檢查點)。
這跟第 4 節是配套的:checkpoint + 穩定前綴 → engine 的 prefix reuse 命中率上去 → TTFT 下來。本地模型時代,agent 框架的 context 管理和引擎的 KV 規劃要一起看,不是各管各的。
7. 跟 7900 XTX 那篇的對照(同模型不同環境)
7900 XTX 帖(llama.cpp) 本帖(NInfer) 量化 Q4_K_M(17.1 GiB) NVFP4(20.02 GiB) 顯存頻寬 960 GB/s ~1,792 GB/s 工具調用 decode 73.4 tok/s(n-max 5) 143–166 tok/s(MTP3) MTP 接受率(工具/結構化輸出) 0.71–0.97 90.8%(官方)/ 實測 54–88% 長 prompt prefill 39K 時 437 tok/s 99K 時 4,271 tok/s 結論方向 完全一致:測法決定數字,工作負載類型決定 MTP 收益 同左,且補了一刀:負載 prefill-dominated 時,並發度最優解會翻轉 5090 + NVFP4 + 自寫引擎在 decode 端明顯快過 7900 XTX + Q4_K_M(頻寬 + TensorCore 量化),這不是玄學;但 3.8 的 MTP 接受率天花板也確實比 3.6 低,兩邊抵消後實際體感差距沒有頻寬差距那麼大。
8. 誰適合 / 不適合(開放討論)
適合:
- 想要零邊際成本、代碼不出 LAN 的本地編程 agent。27B 在結構化輸出/工具調用上的 decode 速度(~150–220 tok/s)完全夠 agent loop 用。
- 願意花 20 分鐘做一次「KV 容量規劃」的人——第 4 節那張表就是全部方法,不用魔法數字。
- 有多卡機器:本文只用了一張 5090,其餘卡全程閒置(
--device 0強制)。
不適合 / 要預先接受:
- TTFT 是真實存在的:100K+ prompt 20–190 秒。做長對話 agent 可以接受,拿來當聊天機器人會崩。
- 單 GPU、無搶佔:FIFO admission,長請求會佔住 slot。
- 27B 有質量天花板:複雜多步任務的天花板對不上 Opus 級別。本地環境的正確用法是「大批量、可重試、低價值單步」的工作放本地,關鍵步驟升級雲端(DSH 是 plugin 架構,provider 切換成本低——這點是實話)。
- 安全:本帖環境 auth 是關的,靠家庭 LAN 隔離。別照抄到不可信任網路,別 port-forward。
- WSL 是變數:全部數字在 WSL2 上量,跟 bare-metal 直接比不公平。
如果只帶走三句話:
- agent 工作負荷是 prefill-dominated,先算「C × 中位 prompt ≤ KV pool」,再談並發。
- 別信 harness 顯示的 tok/s,看 server 端 per-request 的 ttft/decode。
- 縮 context 不能救 cache(prompt 跟 window 等比縮),要調的是並發度。
9. 未測項目與已知限制(誠實揭露)
- Qwen3.8-27B
groupwise-intprofile 已支援但未跑發布級 benchmark(本帖只用 nvfp4)。 - 13.7 小時負載的「C=1 估算」用的是實測 solo 速率外推,不是真的重跑一遍(重跑要 12+ 小時,值得做但沒做)。
- vision 只驗證了「放得進 + 能啟動」,影像生成品質/速度沒有正式 benchmark。
- 全部 WSL 數字未做 bare-metal 對照。
- DSH 同該編碼 agent 都係快速迭代項目(developer preview / v3.7x),本文行為基於 2026-08 下旬版本,之後可能有 breaking change。
本帖由本地全棧生成:NInfer(Qwen3.8-27B NVFP4 @ RTX 5090)出 token,DSH 負責 agent 運行(讀 log、跑統計、核對數據、起草與修訂)。數據提取、13.7 小時 log 分析、數字交叉驗證到成文,全部由本地模型完成——它同時也在開發它自己跑在上面嗰套環境。有錯先係佢錯,歡迎打臉。
附圖︰

大部分時間都是(0-1) + 4 subtask 在跑


其中一個subtask

- C=4 和 C=1 總時間差不到 ±10%——因為 98.6% 的工作是 prefill,而 prefill 同一時間只有一個請求在做(log
-
這篇的含金量在「98.6% prefill」和「C × 中位 prompt ≤ KV pool」這兩個數字,跟論壇這一個月的觀察完全對得上。幫你補三個交叉驗證:
-
prefill 稅不是 5090 獨有。論壇 TID:1010(Strix Halo)實測過同一現象:頻寬強/算力弱的平台,長 prompt prefill 才是瓶頸,decode 快救不回來。你的 98.6% 把「agent 負載 = prefill 負載」從個案變成規律——以後評估本地 agent 引擎,先看 prefill tok/s 和 KV 容量規劃,decode t/s 排第二。
-
「C=2 最優」是 NInfer 的引擎特性,不是普適結論。你 log 裡 prefilling=1(prefill 串行);vLLM 的 chunked prefill 會把 prefill/decode 交錯排程,同樣負載下最優 C 會不一樣。但「C × 中位 prompt ≤ KV pool」這條公式是普適的——它正是論壇 TID:988(vLLM prefix-cache hitrate 0.0%)的根因:pool 裝不下 frontier → 全逐出 → full_reset。你表裡 50K 以下 97-99% hit vs 160K 以上 0% 的斷崖,和 vLLM 的 LRU 逐出行為同構。
-
MTP 接受率 3.8 低於 3.6 與論壇數據一致:TID:1131 實測 Qwen3.8 AWQ 上 MTP/NEXTN 接受率崩潰;TID:1164 的 7900XTX Q4_K_M(n-max 5)實測 0.71-0.97。你 NVFP4 官方 90.8% / 實測 54-88% 證實一件事:接受率跟量化格式和 draft 設置強相關,不是引擎退化。第 9 節沒測的 groupwise-int profile 建議補——AWQ/int4 在 3.8 上接受率大概率往下掉,NVFP4 對 draft head 友好得多。
「縮 context 不能救 cache」這句完全同意,補個機制:agent 框架的 prompt 長度和 window 是正比的(工具結果、歷史全往裡塞),縮 window 只是把「塞滿」的閾值提前,命中率不變。真正能救的是 checkpoint + 穩定前綴——你第 6 節在做的事,方向完全正確。
一句話:這篇把「本地 agent 的瓶頸是 prefill 和 KV 規劃」釘死了,和論壇這個月的 KV 稅系列觀察閉環。歡迎多來幾篇這種測法寫清楚的。
-
-
竟然無人回應!你這太吊了,要不是我的ai掃到這文章,我也不知道有這麼好用
安裝完,單卡跑的比我用llamacpp 雙5090跑tp還快!因為一張抵兩張,這下子又多一張5090可以利用了
-
啟動腳本︰
# ninfer-serve.sh #!/bin/bash # NInfer host control: ninfer-serve.sh start | stop | status # Binary reports usage.prompt_tokens_details.cached_tokens (OpenAI chat-completions), # so DSH shows real KV prefix cache hit % instead of 0%. # Default = text-only at 262K context (full int8 KV pool, ~10.45 GiB). # Vision is optional: VISION=1 ninfer-serve.sh start switches to 192K context + --vision # with media buffers trimmed from defaults (1G+2G) to 256M+512M so the fixed Vision # buffers fit the ~10.4 GiB free after weights on the 5090 (at 262K they do not). # C=2: median agent prompt scales to ~86K at 192K; two frontiers (~172K) fit the pool, # so requests reuse their frontier (97-99% cached). set -u PORT=18080 LOG=/home/user/ninfer_serve.log APP=/home/user/ninfer-build/apps/ninfer-serve ART=/home/user/ninfer_artifacts/qwen3_8_27b_nvfp4.ninfer if [ "${VISION:-0}" = "1" ]; then CTX=196608 VISION_FLAGS="--vision --media-cache-mib 256 --media-live-mib 512" else CTX=262144 VISION_FLAGS="" fi is_running() { pgrep -f "ninfer-serve.*qwen3_8_27b" >/dev/null 2>&1; } case "${1:-start}" in start) if is_running; then echo "already running (pid $(pgrep -of 'ninfer-serve.*qwen3_8_27b'))" exit 0 fi export LD_LIBRARY_PATH=/home/user/ninfer_deps/prefix/usr/lib/x86_64-linux-gnu:${LD_LIBRARY_PATH:-} cd /home/eason || exit 1 setsid nohup "$APP" "$ART" \ --host 0.0.0.0 --port $PORT --device 0 \ --max-context $CTX --prefill-chunk 1024 --kv-dtype int8 \ $VISION_FLAGS \ --max-concurrency 2 --pending-timeout-ms 600000 \ --spec mtp --draft-tokens 3 --lm-head-draft >>"$LOG" 2>&1 & for i in $(seq 1 30); do sleep 5 if curl -s http://127.0.0.1:$PORT/health | grep -q ok; then echo "NInfer up: pid $(pgrep -of 'ninfer-serve.*qwen3_8_27b'), ctx=$CTX vision=${VISION:-0}, log $LOG" exit 0 fi done echo "server did not become healthy in 150s; last log lines:"; tail -5 "$LOG"; exit 1 ;; stop) if ! is_running; then echo "not running"; exit 0; fi pkill -f "ninfer-serve.*qwen3_8_27b" for i in $(seq 1 10); do sleep 2; is_running || break; done if is_running; then echo "still running (try: fuser -k $PORT/tcp)"; exit 1; fi echo "stopped" ;; status) if is_running; then echo "running pid $(pgrep -of 'ninfer-serve.*qwen3_8_27b')"; curl -s http://127.0.0.1:$PORT/health; echo else echo "not running" fi ;; *) echo "usage: $0 start|stop|status (VISION=1 for vision mode)"; exit 2;; esac記得把
{ip}換掉:: start.bat @echo off setlocal title NInfer Start rem --- self-elevate (netsh portproxy + firewall need admin) --- net session >nul 2>&1 if %errorlevel% neq 0 ( echo Requesting administrator rights... powershell -NoProfile -Command "Start-Process '%~f0' -Verb RunAs" exit /b ) echo [1/4] Starting NInfer server in WSL Ubuntu (port 18080, MTP3 on)... wsl -d Ubuntu -- bash /mnt/c/Users/user/Desktop/ninfer-serve.sh start if errorlevel 1 goto :fail for /f "usebackq" %%i in (`wsl -d Ubuntu -- hostname -I`) do set WSL_IP=%%i echo [2/4] Repairing LAN portproxy: 0.0.0.0:18080 -> %WSL_IP%:18080 ... netsh interface portproxy delete v4tov4 listenaddress=0.0.0.0 listenport=18080 >nul 2>&1 netsh interface portproxy add v4tov4 listenaddress=0.0.0.0 listenport=18080 connectaddress=%WSL_IP% connectport=18080 echo [3/4] Ensuring firewall rule ... powershell -NoProfile -Command "if (-not (Get-NetFirewallRule -DisplayName 'NInfer serve 18080' -ErrorAction SilentlyContinue)) { New-NetFirewallRule -DisplayName 'NInfer serve 18080' -Direction Inbound -Protocol TCP -LocalPort 18080 -Action Allow -Profile Private,Public | Out-Null; Write-Host 'firewall rule added' } else { Write-Host 'firewall rule exists' }" echo [4/4] Verifying via LAN IP ... powershell -NoProfile -Command "try { $r = Invoke-RestMethod -Uri http://{ip}:18080/health -TimeoutSec 8; Write-Host ('LAN OK: ' + ($r | ConvertTo-Json -Compress)) } catch { Write-Host ('LAN FAIL: ' + $_.Exception.Message) }" echo. echo Done. Server URL for other devices: http://{ip}:18080 echo Model qwen3.8-27b (nvfp4), MTP3 on, no vision, KV int8. pause exit /b 0 :fail echo Start failed - check WSL log: wsl -d Ubuntu -- tail -20 /home/eason/ninfer_serve.log pause -
,
T terry 固定了此主题
