3090 单卡跑 Qwen3.8-27B:NInfer 上机实测(180K 上下文 / C2 并发 / 4bit KV)
-
3090 单卡跑 Qwen3.8-27B:NInfer 上机实测(180K 上下文 / C2 并发 / 4bit KV)
NInfer 在论坛里火了一阵(原帖 讲的是它把 NInfer 移植到 SM86),
我机器上有一张 3090,原本跑的是调优过的 vLLM 栈,这次把服务换到 NInfer 上,看看到底能到什么程度、跟原来差多少。
别人写过的背景不重复,这篇只写我这台机器的数字、能直接抄的配置,以及那几个不改就起不来的参数。显卡 单张 RTX 3090 24G(SM86),功耗墙 300W(这卡默认 350W,影响见测评) 驱动 595.71.05 引擎 NInfer-3090 v0.6.1(官方 Linux x64 预编译包) 权重 neroued/Qwen3.8-27B-NInfer@ revision18dfc887(container v2,18.2 GB)部署 Docker(CUDA 12.8 runtime 底座 + 官方预编译二进制)
一、部署:三步,全都能直接抄
1. 拿模型(必须钉 revision)
HF 仓库的
qwen3_8_27b.ninfer在 2026-09-15 升级成 container v3,而 v0.6.1 的预编译二进制只认 v1/v2 容器,
直接下 main 会报artifact magic is not NInfer v1 or v2。钉到 v2 容器那个 revision:curl -L -C - --fail -o qwen3_8_27b.ninfer \ "https://huggingface.co/neroued/Qwen3.8-27B-NInfer/resolve/18dfc887423fa5aabf3cb56fac41490e462b3fab/qwen3_8_27b.ninfer" # 18,210,531,328 B sha256 eec39564993d6e9c7d5e383382a760f093465c9d163ec9a1bd6b80199514bf3e2. 打镜像(10 秒,不编译)
上游只发 Dockerfile(源码 245 步 CUDA 编译)和预编译二进制,没有现成镜像(GHCR 上这个包匿名拉取是 DENIED)。
最省事的路:拿官方 Linux x64 预编译包塞进官方 CUDA runtime 底座。FROM nvidia/cuda:12.8.0-runtime-ubuntu24.04 # GeForce 卡不能用 forward-compat 的新 libcuda,不删会在启动时报 cudaErrorCompatNotSupportedOnDevice RUN rm -rf /usr/local/cuda-12.8/compat /usr/local/cuda/compat RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates curl \ && rm -rf /var/lib/apt/lists/* COPY ninfer /usr/local/bin/ninfer COPY ninfer-serve /usr/local/bin/ninfer-serve ENTRYPOINT ["ninfer-serve"]预编译包:
ninfer-rtx3090-linux-x64-0.6.1-rtx3090.tar.gz
(sha256eb6a6e5b4b318dcf5a8173ccdae8d98acbe730606a50d87eaf120353962965b5),解包后两个二进制就是全部。3. 启动(四个参数是踩出来的,别改)
docker run -d --name ninfer --gpus '"device=0"' --ipc=host -p 18020:18020 \ -v $PWD/models:/models:ro ninfer-3090:0.6.1 \ /models/qwen3_8_27b.ninfer \ --host 0.0.0.0 --port 18020 --model-id qwen3.8-27b --api-key "$KEY" \ --max-context 184320 --kv-capacity 184320 \ --max-concurrency 2 --max-pending-requests 16 --pending-timeout-ms 900000 \ --prefill-chunk 1024 --kv-dtype rk8v4 --spec mtp --draft-tokens 3 --lm-head-draft参数 为什么必须这么写 --kv-capacity 184320(显式)auto在 C2 + MTP3 下直接拒绝启动:引擎最小运行时 6.68 GiB + 1 GiB 自动余量 > 权重加载后剩下的 6.63 GiB--pending-timeout-ms 900000默认超时下,C2 的第二个长上下文请求会在排队时 error inference request expired while waiting for admission--kv-dtype rk8v4int8 的 KV 在 C2 下最多只能开到 160K;rk8v4(K8/V4)能到 180K,实测两者速度没有差别 不加 --vision开视觉要多占约 2.1 GiB,C2 下上下文从 180K 掉到 136K(余量只剩 48 MiB) 显存账(这张 24G 卡):权重 16.67 GiB + KV 池(184320 token)5.61 GiB,模型加载 19 秒,启动后余量 1.02 GiB。
踩坑一句话版:v3 模型读不了(要钉 revision)、
kv-capacity auto起不来(要显式)、fp8KV 在 SM86 上被引擎拒绝(没有对应内核)、长上下文 C2 第二个请求会排队超时(要调大 pending timeout)、开 vision 只能 136K。划重点:单张 3090 上,
180K 上下文 + 4bit KV(rk8v4) + C2 并发 + 关 vision是反复对比后的最佳组合。
再往上加要么牺牲并发(C1),要么牺牲上下文(开 vision 只到 136K),要么牺牲速度(int8 + 关投机解码换 180K,但 decode 掉到 35 tok/s)。
二、测评
测试条件:单个 Docker 实例、
--max-concurrency 2、--kv-dtype rk8v4、MTP3 投机解码、默认采样、
输入是真实长度的中英混合上下文(coding = 一整段代码 + 改代码要求;agent = 系统提示 + 工具定义 + 多轮工具返回 + 提问),
每条请求最多生成 160 token。输入超过约 90K 时一个 180K 的 KV 池装不下两份,那两行是单请求(表里并发列写 1)。功耗墙 300W(这台的设置;卡默认 350W)。满载实测:
功耗 SM 频率 温度 满载(util > 60%,n=43 采样) avg 298 W / max 300 W(贴墙) avg 1538 MHz(1395–1665) 74 °C 放开到 350W 实测 decode +8%(54.6 → 59.0 tok/s,SM 1410 → 1545 MHz),我为了控温没放。
coding 场景(长代码上下文 + 改代码)
输入 token 并发 TTFT prefill decode/路 decode 合计 MTP 接受率 功耗 avg/max 8,359 2 9.2s / 19.0s 903 tok/s 35.7 / 38.2 tok/s 74 tok/s 48.2% 292 / 300 W 32,305 2 39.1s / 80.1s 823 tok/s 43.3 / 46.6 tok/s 90 tok/s 58.3% 294 / 300 W 64,233 2 87.2s / 177.5s 736 tok/s 55.3 / 47.7 tok/s 103 tok/s 54.1% 297 / 300 W 128,089 1 209.8s 611 tok/s 51.5 tok/s 52 tok/s 66.7% 295 / 300 W 173,832 1 319.6s 544 tok/s 41.1 tok/s 41 tok/s 52.2% 296 / 300 W agent 场景(system + 工具定义 + 多轮工具结果 + 提问)
输入 token 并发 TTFT prefill decode/路 decode 合计 MTP 接受率 功耗 avg/max 8,533 2 9.6s / 19.8s 884 tok/s 66.8 / 67.6 tok/s 134 tok/s 98.2% 290 / 300 W 33,057 2 40.5s / 82.0s 815 tok/s 79.5 / 77.9 tok/s 157 tok/s 98.2% 296 / 300 W 65,829 2 90.1s / 181.2s 730 tok/s 74.8 / 73.6 tok/s 148 tok/s 98.2% 296 / 300 W 131,419 1 217.5s 605 tok/s 63.2 tok/s 63 tok/s 96.3% 296 / 300 W 178,335 1 331.6s 538 tok/s 58.9 tok/s 59 tok/s 96.3% 296 / 300 W 四个观察
- prefill 是串行的,C2 的两个请求不是同时开始吃 prompt:第二个的 TTFT ≈ 排队(第一个的 prefill 时间)+ 自己的 prefill。
32K 输入时 39s / 80s,64K 时 87s / 177s,都是这个规律。所以 C2 的并发收益主要体现在 decode 阶段,不是 TTFT。 - prefill 速度本身随输入变长而下降:8K 时 ~900 tok/s,64K 时 ~735,178K 时只有 ~545 tok/s。
长上下文真正的代价是 TTFT(178K 输入要等 5.5 分钟),不是 decode。 - decode 几乎不随上下文衰减:coding 场景 36~55 tok/s、agent 场景 59~80 tok/s,178K 输入下反而还有 41~59。
- MTP 接受率完全看内容形态:agent 的工具调用轨迹是结构化 JSON,接受率 96~100%(每轮 3.9~4.0 个 token);
自由文本/代码只有 44~67%(每轮 2.4~3.0)。这也是 agent 场景 decode 比 coding 高一截的原因 —— 投机解码对结构化输出特别划算。
和 vLLM(我上一版用的栈)比:prefill 差一倍,原因是算力路径
同一张卡、同一个模型(Qwen3.8-27B W4A16),我上一版跑的是 syv-ai/HyperQwen 系的 vLLM 栈
(那套打了四十多个补丁 + 自定义 int8 预填注意力内核,不是原生 vLLM 开箱),本机实测:输入长度 1K 4K 16K 64K 100K+ vLLM(INT8 激活路径) 2,056 2,170 1,965 1,412 1,161@100K、934@178K ninfer(这篇) ~920 ~920 892@20K 736 611@128K、544@178K 单流 decode 同理:vLLM 那套 110–118 tok/s,ninfer 41–59 tok/s。两个方向都差大约一倍。
原因不是 ninfer 写得烂,是两条算力路径的物理上限差 2 倍。 27B dense 每 prefill 一个 token 约 54 GFLOP:
用的单元 3090 上的算力 理论上限 实测达到 ninfer 反量化 → FP16 tensor core 71 TFLOPS ~1,315 tok/s 925(70%) vLLM INT8 tensor core(激活也量化) 142 TOPS ~2,630 tok/s 2,170(82%) ninfer 已经把 FP16 这条路吃到七成。我把
--prefill-chunk从 512 扫到 3584(911 / 925 / 927 / 925 tok/s),
没有可调余量;它的权重是 4–6 bit 混合精度、激活是 FP16,所以只能走 FP16 MMA。
想翻倍得让激活也走 int8,而 ninfer 现在没有这个开关(--kv-dtype只管 KV cache)。
也正因如此,给它换 int8 权重是负收益 —— decode 是显存带宽瓶颈,权重从 4.6 bit 涨到 8 bit,每步要读的字节多 74%,decode 只会更慢。顺带对齐第三方数据,ninfer 在 dense 27B 这级别并不难看(llama.cpp CUDA 在 3090 上跑 Qwen3.5-27B Q4_K:
4K 1,104 / 16K 977 / 32K 848 / 64K 679 tok/s):浅上下文它低约 15%,64K 以上反而反超。
网上那些 5,000+ tok/s 的 prefill 数字是 7B 小模型或 3–4B 激活的 MoE,跟 dense 27B 不是一个量级,别被带偏。所以怎么选:要吞吐、要短提示首字快(IDE agent、批量 API),vLLM 那套明显更强;
要单卡 180K + 双并发 + Anthropic 原生协议 + 部署极简(一个预编译包,无补丁无编译),ninfer 更省事。
我把它定位成"够用的长上下文单卡后端",不拿它跟调优过的 vLLM 比跑分。和官方数字的差距(为什么不是 71 tok/s)
官方单用户 71 tok/s 那组数据是 Windows 版跑出来的,README 里写得很清楚:
"A real-artifact Linux generation and Linux performance qualification remain open" —— Linux 二进制没做性能认证。
除去平台差异,我这边的缺口主要在两处:MTP 接受率(2.4 vs 2.83 token/轮)和 功耗墙(300W 把 SM 压在 ~1.5 GHz)。
参数上和官方 C1 示例逐字一致,没有漏开关。
三、一句话总结
NInfer 在 3090 上确实能用,单卡 180K 上下文 + 双并发当个人 agent 后端够了;
但它的甜区是"上下文长、并发低、部署省事",不是跑分 —— 同卡同模型上,调优过的 vLLM 栈在 prefill 和单流 decode 上都快约一倍,
根因是算力路径(FP16 vs INT8 tensor core),不是实现质量。这点我不粉饰,选它买的是"一个预编译包跑起 180K + Anthropic 协议"。抄配置的话,把上面那四个参数照抄,尤其
--kv-capacity显式写 和--pending-timeout-ms调大 —— 这两个不改,C2 根本跑不起来。 - prefill 是串行的,C2 的两个请求不是同时开始吃 prompt:第二个的 TTFT ≈ 排队(第一个的 prefill 时间)+ 自己的 prefill。
-
结论一句话,prefill和decode都不如 syv-ai/HyperQwen 。 3090单卡还是选这个方案吧
-
@davidwei0826 数字收了。两个口径提醒,免得结论下得太重:
- 你把这卡功耗墙从 350W 压到 300W,而 prefill 是计算密集、decode 是带宽密集,两者对功耗墙的敏感度不同;如果对照的 HyperQwen 用的是默认墙,这个差值会混进结论。至少把两边锁到同一功耗墙再比一次。
- 4bit KV 在 180K 下减少的是每 token 要读的 KV 字节数,方向上利好 decode;你现在连 decode 也更差,那差距更可能来自 kernel 或 offload 路径,而不是量化本身。把 prefill / decode 分两段(pp 和 tg)单独贴曲线,结论会清楚很多。
同功耗墙、同 KV dtype、同 prompt 跑齐,如果 HyperQwen 还是全赢,那结论就硬了。
-
按照小特说的,和 HyperQwen(同一张卡上的 vLLM 栈)比:pp / tg 分开看,出现 crossover。基本上单卡还是用HyperQwen的方案更好。
先说测量口径:
- 同一张卡(单 3090,24G),同一个功耗墙 300W(
gpu-power-limit.service把两张 3090 都锁 300W,卡默认/最大是 350W;
实测满载 avg 286-300W、峰值 SM 1,890-1,980 MHz,供核对)。 - pp 与 tg 分开测:pp = prompt token / TTFT(流式取首 token 时间);tg = 生成窗口内的 token/s。
- prompt 冷缓存:每个长度用独立随机文本 + 唯一 nonce 开头,响应里
cached_tokens=0可自证没吃前缀缓存
(我第一版没做这一步,16K/32K 的 pp 被前缀缓存虚高到 2,257/3,154,已作废重测)。 - 生成端固定 256 token、关闭思考;KV 精度两边都是"4bit 级"但方案不同:HyperQwen 是 KVarN k4v2,ninfer 是 rk8v4(K8/V4)。
HyperQwen 单流 pp / tg 曲线:
输入 token cached TTFT prefill decode 功耗 avg/max 955 0 0.75 s 1,276 107.7 175/299 W 3,668 0 1.85 s 1,978 110.6 249/299 W 14,546 0 6.99 s 2,081 82.5 270/298 W 29,113 0 15.4 s 1,886 57.5 283/299 W 58,031 0 37.0 s 1,569 37.8 287/298 W 116,101 0 99.1 s 1,171 31.4 286/300 W 159,249 0 161 s 990 19.9 288/299 W 和 ninfer 并排(同卡同墙):
输入 HyperQwen pp ninfer pp HyperQwen tg ninfer tg ~1-4K 1,276-1,978 ~920 107.7-110.6 ~52 ~29K 1,886 823 57.5 ~47 † ~58K 1,569 736 37.8 ~48-55 † ~116K 1,171 611 31.4 51.5 ~159K 990 544 19.9 41.1 † 这两点是 C2 两路并发下的单路值,不是 C1。
pp 差一倍的根因是算力路径:27B dense 每 prefill 一个 token 约 54 GFLOP;3090 的 FP16 tensor 是 71 TFLOPS、INT8 是 142 TOPS。
HyperQwen 走 int8 激活(INT8_ACT=int8+ int8 QK 预填注意力),14.5K 时 2,081 tok/s ≈ 112 TFLOPS ≈ INT8 峰值的 79%;
ninfer 是"反量化 → FP16 MMA",925 tok/s ≈ 50 TFLOPS ≈ FP16 峰值的 70% —— 两条路的物理上限本来就差约 2 倍,不是谁写得烂。
(ninfer 侧我把--prefill-chunk从 512 扫到 3584 毫无变化,也说明它没有可调余量。)但 tg 的形状完全不同,而且出现 crossover:decode 从 110 掉到 19.9(5.5×),
而 ninfer 的长上下文 decode 反而更稳 —— ≤30K 时 HyperQwen 快约 2×,~60K 被追平,≥116K 时 ninfer 反超 1.6-2×。
方向上说得通:KVarN 4bit 的反量化开销随深度增长,DFlash2 草稿只看 2K 窗口,
长上下文下草稿命中率掉下来;而 ninfer 的 ReplaySSM/MTP 在深上下文里保住了接受率。
所以"4bit KV ⇒ decode 更快"这条对本文的 ninfer 不成立 —— 它的 decode 优势来自别处,长上下文反超也不是量化的功劳。取舍:短提示响应、批量 API、要 prefill 吞吐 → HyperQwen 明显更强;
单卡要 180K 长上下文 + 长对话里的稳定 decode,ninfer 在深上下文反而更划算。 - 同一张卡(单 3090,24G),同一个功耗墙 300W(
-
按照小特说的,和 HyperQwen(同一张卡上的 vLLM 栈)比:pp / tg 分开看,出现 crossover。基本上单卡还是用HyperQwen的方案更好。
先说测量口径:
- 同一张卡(单 3090,24G),同一个功耗墙 300W(
gpu-power-limit.service把两张 3090 都锁 300W,卡默认/最大是 350W;
实测满载 avg 286-300W、峰值 SM 1,890-1,980 MHz,供核对)。 - pp 与 tg 分开测:pp = prompt token / TTFT(流式取首 token 时间);tg = 生成窗口内的 token/s。
- prompt 冷缓存:每个长度用独立随机文本 + 唯一 nonce 开头,响应里
cached_tokens=0可自证没吃前缀缓存
(我第一版没做这一步,16K/32K 的 pp 被前缀缓存虚高到 2,257/3,154,已作废重测)。 - 生成端固定 256 token、关闭思考;KV 精度两边都是"4bit 级"但方案不同:HyperQwen 是 KVarN k4v2,ninfer 是 rk8v4(K8/V4)。
HyperQwen 单流 pp / tg 曲线:
输入 token cached TTFT prefill decode 功耗 avg/max 955 0 0.75 s 1,276 107.7 175/299 W 3,668 0 1.85 s 1,978 110.6 249/299 W 14,546 0 6.99 s 2,081 82.5 270/298 W 29,113 0 15.4 s 1,886 57.5 283/299 W 58,031 0 37.0 s 1,569 37.8 287/298 W 116,101 0 99.1 s 1,171 31.4 286/300 W 159,249 0 161 s 990 19.9 288/299 W 和 ninfer 并排(同卡同墙):
输入 HyperQwen pp ninfer pp HyperQwen tg ninfer tg ~1-4K 1,276-1,978 ~920 107.7-110.6 ~52 ~29K 1,886 823 57.5 ~47 † ~58K 1,569 736 37.8 ~48-55 † ~116K 1,171 611 31.4 51.5 ~159K 990 544 19.9 41.1 † 这两点是 C2 两路并发下的单路值,不是 C1。
pp 差一倍的根因是算力路径:27B dense 每 prefill 一个 token 约 54 GFLOP;3090 的 FP16 tensor 是 71 TFLOPS、INT8 是 142 TOPS。
HyperQwen 走 int8 激活(INT8_ACT=int8+ int8 QK 预填注意力),14.5K 时 2,081 tok/s ≈ 112 TFLOPS ≈ INT8 峰值的 79%;
ninfer 是"反量化 → FP16 MMA",925 tok/s ≈ 50 TFLOPS ≈ FP16 峰值的 70% —— 两条路的物理上限本来就差约 2 倍,不是谁写得烂。
(ninfer 侧我把--prefill-chunk从 512 扫到 3584 毫无变化,也说明它没有可调余量。)但 tg 的形状完全不同,而且出现 crossover:decode 从 110 掉到 19.9(5.5×),
而 ninfer 的长上下文 decode 反而更稳 —— ≤30K 时 HyperQwen 快约 2×,~60K 被追平,≥116K 时 ninfer 反超 1.6-2×。
方向上说得通:KVarN 4bit 的反量化开销随深度增长,DFlash2 草稿只看 2K 窗口,
长上下文下草稿命中率掉下来;而 ninfer 的 ReplaySSM/MTP 在深上下文里保住了接受率。
所以"4bit KV ⇒ decode 更快"这条对本文的 ninfer 不成立 —— 它的 decode 优势来自别处,长上下文反超也不是量化的功劳。取舍:短提示响应、批量 API、要 prefill 吞吐 → HyperQwen 明显更强;
单卡要 180K 长上下文 + 长对话里的稳定 decode,ninfer 在深上下文反而更划算。@davidwei0826 这组数据做得干净,功耗墙、冷缓存自证、pp/tg 分离都锁上了,结论我认可:单卡 3090 综合仍是 HyperQwen 更划算。补两点让 crossover 更硬:
- 你标 † 的 29K/58K 是 C2 两路并发的单路值,而 1-4K 和 116K/159K 大概率是 C1。并发会同时压 KV 带宽和调度,两点不能和单流曲线直接连——crossover 的确切位置可能就在这段之间漂。29K/58K 补一组 C1 再连曲线,就能把交叉点钉在某个长度区间。
- pp 差一倍归因到 INT8 vs FP16 MMA,从算力上限看成立(2,081 tok/s 约 INT8 峰值 79%)。tg 的交叉方向也自洽:短 ctx 由权重带宽 + 投机接受率主导,DFlash2 吃满;长 ctx 里 KVarN 反量化开销随读 KV 字节数增长、草稿 2K 窗口命中率掉,而 ninfer 的 ReplaySSM/MTP 保住了接受率。所以反超不是 4bit KV 的功劳,这点你写对了。
取舍我同意:短提示/高吞吐 API 用 HyperQwen;单卡长上下文 + 长对话稳定 decode 用 ninfer。
- 同一张卡(单 3090,24G),同一个功耗墙 300W(
-
9–12 t/s 偏低。同卡同方案(单卡 3090、单流 C1、冷缓存、功耗墙 300W)实测 116K 上下文约 31 t/s、159K 约 19.9 t/s,所以先按下面几条定位,通常不用换方案。
- 先分清瓶颈在显存带宽还是 KV 落 host。跑 decode 时看
nvidia-smi dmon:SM 高、mem 也高是正常带宽受限;如果 SM 不高、但 PCIe 有明显流量,基本是 KV/权重被 offload 到 CPU,长 ctx 下每 token 读 KV 全走 PCIe,就是这个掉到 10 t/s 的形态。 - 看 vLLM 启动日志里
GPU KV cache size: N tokens是否覆盖你的实际上下文。--max-model-len开太大(比如 256K)会按上限分配 block,24G 上更容易触发抢占/换页,按实际要用的长度设准。 - 确认 KV 精度真的走了 k4v2:KV 相关的开关要显式打开。退回 fp16 KV 时长 ctx 每 token 读取字节翻倍,decode 会直接掉一半。
- 查并发/抢占:vLLM 日志里搜
Preemption。多人同时请求会把单流压到几条,先按 C1 单流测基线。 - 关掉非必要开销:
--enforce-eager若开着(禁用 CUDA graph)会牺牲 decode 性能;采样用默认,别叠 grammar 或大 JSON 约束。
把启动命令、模型版本/commit、实际 max-model-len,以及一段 decode 时的
nvidia-smi dmon(含 SM%/mem%/power)贴出来,我对着数据判断是 offload、KV 精度还是调度问题。 - 先分清瓶颈在显存带宽还是 KV 落 host。跑 decode 时看