EXL3 现已支持 ninfer-ext

Reddit r/LocalLLaMA 工具

摘要

ninfer-ext 是 NInfer 的扩展分支,新增了对更大 Qwen 模型的支持、更快的推测式解码、Agent 服务功能,以及针对 Qwen3.8-27B 在 NVIDIA RTX 5090 上的 EXL3 量化支持。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/09/30 08:25

giveen/ninfer-ext 来源:https://github.com/giveen/ninfer-ext

ninfer-ext

ninfer-ext 是 Neroued/ninfer(https://github.com/Neroued/ninfer)的扩展分支,Neroued/ninfer 是一个为 Qwen 模型编写的、在单张 NVIDIA GeForce RTX 5090 上运行的从零实现的 C++/CUDA 推理引擎。该分支在上游引擎的基础上增加了四项能力:

  • 在单张 32 GB GPU 上运行 Qwen3.8-Flash-Next。 这是一个约 180B 参数的 MoE 模型,其路由专家存放在固定内存(pinned Host memory)中,并由设备端专家缓存作为支撑。原版 NInfer 无法加载它。
  • 在 Qwen3.8-27B groupwise-int 上更快的投机解码。 与原版在相同产物上的对比中,DFlash2 在 1–4 个并发请求下快 13–48%,MTP 快 8–32%。
  • 面向长时间运行的 Agent 的服务能力。 上下文缓存的抢救与锚定(salvage and anchoring)、统一的 Host 层预算、OOM 恢复,以及若干协议扩展。
  • Qwen3.8-27B 的 EXL3 量化。 一种格码(trellis-coded)格式,提供 4.0 和 3.5 bpw,配有专用的 C++/CUDA 量化器和算子内核(EXL3 量化)。它产出的产物是本分支所有 Qwen3.8-27B 构建中体积最小的,并且在困惑度(perplexity)和 KL 散度两项指标上都是最优;上游没有任何东西能生成或运行这些产物。

它并非在所有场景下都比原版更快。Qwen3.8-27B nvfp4 和 Qwen3.6-35B-A3B 与原版打平,而在两种场景下原版领先。与原版 NInfer 的对比列出了双方数据。

其余部分均来自上游:架构、.ninfer v3 产物格式、官方产物,以及兼容 OpenAI 与 Anthropic 的服务器。该分支跟踪上游的 master(当前为 bace20dc,即 upstream remote),其代码树已移植到 C++23。

快速上手

1. 环境要求

  • 64 位 Linux 系统和一张 RTX 5090(sm_120a)。不支持其他 GPU。
  • 支持 sm_120a 的 CUDA toolkit。CUDA 13.3 已经过验证。
  • CMake 3.28 或更新版本、支持 C++23 的宿主编译器、Ninja 和 pkg-config。
  • FFmpeg 开发库(libavformat、libavcodec、libavutil、libswscale)以及 libcurl >= 7.85。
  • 仅 Qwen3.8-Flash-Next 需要:约 128 GB 主机内存和 127 GB 磁盘空间(详情)。

2. 构建

git clone https://github.com/giveen/ninfer-ext.git
cd ninfer-ext
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
cmake --build build -j

这会构建出 build/apps/ninfer-serve(HTTP 服务器)、build/apps/ninfer(一次性 CLI)和 build/apps/ninfer-perplexity。项目没有 install 目标,因此请直接从构建目录运行。cmake --preset dev 还会构建测试和基准测试(构建配置)。

3. 获取模型

上游的官方 v3 产物可直接使用,无需修改:

模型权重产物下载
Qwen3.8-27Bnvfp4qwen3_8_27b_nvfp4.ninferneroued/Qwen3.8-27B-nvfp4-NInfer(https://huggingface.co/neroued/Qwen3.8-27B-nvfp4-NInfer)
Qwen3.8-27Bgroupwise-intqwen3_8_27b.ninferneroued/Qwen3.8-27B-NInfer(https://huggingface.co/neroued/Qwen3.8-27B-NInfer)
Qwen3.8-27Bexl3 4.0 bpwqwen3_8_27b_exl3_4bpw.ninferjabbatheduck/ninfer-ext-models(https://huggingface.co/jabbatheduck/ninfer-ext-models)
Qwen3.8-27Bexl3 3.5 bpwqwen3_8_27b_exl3_3p5bpw.ninferjabbatheduck/ninfer-ext-models(https://huggingface.co/jabbatheduck/ninfer-ext-models)
Qwen3.6-27Bnvfp4qwen3_6_27b_nvfp4.ninferneroued/Qwen3.6-27B-nvfp4-NInfer(https://huggingface.co/neroued/Qwen3.6-27B-nvfp4-NInfer)
Qwen3.6-27Bgroupwise-intqwen3_6_27b.ninferneroued/Qwen3.6-27B-NInfer(https://huggingface.co/neroued/Qwen3.6-27B-NInfer)
Qwen3.6-35B-A3Bgroupwise-intqwen3_6_35b_a3b.ninferneroued/Qwen3.6-35B-A3B-NInfer(https://huggingface.co/neroued/Qwen3.6-35B-A3B-NInfer)
hf download neroued/Qwen3.8-27B-nvfp4-NInfer qwen3_8_27b_nvfp4.ninfer --local-dir models
hf download jabbatheduck/ninfer-ext-models qwen3_8_27b_exl3_4bpw.ninfer --local-dir models

表格中两行 exl3 是本分支自行量化、发布在 Hugging Face 上的产物,可直接下载并运行(EXL3 量化)。

另有两个产物需要自行转换:带 DFlash2 权重的 Qwen3.8-27B(说明)和 Qwen3.8-Flash-Next(说明)。转换后的产物内嵌本分支的聊天模板(转换指南)。已有的 v2 下载可以在本地升级。

4. 启动服务并发送请求

./build/apps/ninfer-serve models/qwen3_8_27b_nvfp4.ninfer \
  --max-context 32768 --kv-capacity auto --max-concurrency 2 \
  --spec mtp --draft-tokens 5 --fixed-draft --lm-head-draft
curl http://127.0.0.1:8080/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{"model": "qwen3.8-27b", "messages": [{"role": "user", "content": "Reply with one short sentence."}], "max_tokens": 64}'

服务器支持 OpenAI Chat Completions 与 Responses,以及 Anthropic Messages,包括流式输出和工具调用。--model-id 可修改其报告的模型名;--host 0.0.0.0 可将其暴露到本机之外,--api-key 则要求请求携带密钥。

不启动服务器、直接得到一次性回答:

./build/apps/ninfer models/qwen3_8_27b_nvfp4.ninfer \
  --prompt "Explain prefill and decode." \
  --max-context 32768 --spec mtp --lm-head-draft

回答输出到 stdout,诊断信息和耗时报告输出到 stderr。CLI 指南、HTTP 服务指南和示例覆盖了所有选项;每个二进制的 --help 都会列出当前可用的参数。

Docker。 执行 docker build --tag ninfer:local .,然后将带 --host 0.0.0.0 的 ninfer-serve 命令连同挂载好的模型目录一起传给 docker run --gpus all。

推荐设置

以下是按模型测得的最快设置。“并发”指同时有多少个请求在解码;请将 --max-concurrency 设为该数字,因为每增加一个并发槽位,无论是否被使用,都会预留 KV 和计算图内存。

表中标注“由负载决定”的单元格,其背后的数据见性能和与原版 NInfer 的对比。

模型1 个请求2–4 个并发8 个并发
Qwen3.8-27B nvfp4--spec mtp --draft-tokens 5 --fixed-draft --lm-head-draft相同相同
Qwen3.8-27B groupwise-int(DFlash2 权重)--spec dflash2 --draft-tokens 7 --lm-head-draft相同相同
Qwen3.8-27B groupwise-int(官方产物)--spec mtp --draft-tokens 5 --fixed-draft --lm-head-draft--spec mtp --lm-head-draft--spec mtp --draft-tokens 5 --fixed-draft --lm-head-draft
Qwen3.8-27B exl3 4.0 / 3.5 bpw--spec mtp --draft-tokens 3 --fixed-draft未测量未测量
Qwen3.6-35B-A3B--spec mtp --lm-head-draft2 个请求时同上;4 个请求时,长推理同上,短散文不加 --spec长理选用 --spec dflash --draft-tokens 7 --lm-head-draft;短散文不加 --spec
Qwen3.6-27B(两种)未测量;请从 Qwen3.8-27B 中相同权重的那一行起步
Qwen3.8-Flash-Next不加 --spec不加 --spec不加 --spec

理由如下:

  • 27B 模型。 投机解码在任何并发下都有收益。在长推理负载上,nvfp4 上固定 5 token 的 MTP 草稿比自适应默认值快 3–17%。在 groupwise-int 上,2–4 个请求时自适应默认值更优,1 个和 8 个请求时固定 K=5 更优。只要产物带有 DFlash2 权重,DFlash2 就是 groupwise-int 上最快的模式。
  • EXL3。 目前只测量了单请求设置:固定 3 token 的 MTP 草稿,与其他 27B 产物使用的投机形态相同。并发场景尚未测量。
  • 35B-A3B。 一到两个请求时,自适应 MTP 在长推理上与最佳固定草稿相差 5% 以内,在短提示上则领先。从 4 个请求起,投机解码对短散文反而有害:在 512 token 的作文上,纯解码在 C=4 时比 MTP 快 14%,C=8 时快 32%。在 C=8 的长推理上,DFlash 配 7 个草稿是最快的模式。
  • Flash-Next。 解码速度受限于通过 PCIe 拉取专家。一次验证轮最多路由四列,触及的专家数量超过被接受草稿所节省的量。纯解码在单请求下比 MTP 快 26%,C=8 时快 6%。
  • 自适应默认值(仅 --spec mtp) 在负载未知或混合时是合理选择,详见自适应 MTP 草稿长度。

对速度有影响的其他设置:

  • 保持 CUDA Graphs 和前缀复用开启。 两者均为默认行为;--no-cuda-graph 和 --no-prefix-reuse 仅供调试和基准测试使用。
  • 按实际需要设置 --max-context。 KV 池与其它内容一样占用同一块显存。在 Flash-Next 上它直接从专家缓存中划出,缓存越小意味着 PCIe 拉取越多。
  • --kv-dtype fp8 可将长上下文的 KV 内存减半。在 Flash-Next 上,2,048 token 的提示之后它还能快 3%;其他模型在本项目中尚未测量其速度影响。
  • 会重发长历史的 Agent 循环: 用 --host-cache-mib(例如 16384)给 Host 缓存层留出空间,使可复用前缀能保留在固定内存中。
  • Flash-Next: 保持 --expert-cache auto(默认值),并确保有足够的空闲主机内存,使 n-gram 表维持在页缓存映射状态(由 --ngram-residency auto 决定)。
  • 上下文超过显存容量: 增加 --kv-stream;参见 KV 流式传输。

示例:面向四个长上下文 Agent 的 Qwen3.8-27B nvfp4:

./build/apps/ninfer-serve models/qwen3_8_27b_nvfp4.ninfer \
  --max-context 131072 --kv-capacity auto --max-concurrency 4 --kv-dtype fp8 \
  --host-cache-mib 16384 \
  --spec mtp --draft-tokens 5 --fixed-draft --lm-head-draft --preserve-thinking

KV 流式传输

KV 流式传输让单个请求的上下文可以超出显存中 KV 缓存的容量。每个上下文中最近的部分保留在 GPU 上;更早的、以 64 token 为单位的完整页会转移到固定主机内存中,注意力算子内核从那里读取。这样一来,上下文长度由 --max-context(最多不超过模型的 max_position_embeddings)以及你为 Host KV 层分配的内存决定,而不再受限于显存。

工作原理

  • 每个活跃请求拥有均等的 GPU KV 份额,即 --kv-capacity / --max-concurrency。它可以借用空闲的份额,并在其他请求需要时归还。
  • 某请求超出其 GPU KV 配额后,会把最旧的完整页迁移到 Host KV(--host-kv-mib,或针对整个 Host 层使用 --host-cache-mib)。最前几页和最近的尾部始终留在 GPU 上。
  • 每一步都会按层将被溢出的页复制到 GPU 上,并与上一层的计算重叠执行。在 Flash-Next 上,稀疏注意力只从内存读取选中的 token,并在 GPU 上保留一份注意力索引。
  • 准入时会为每个请求的提示加上输出上限、超出其配额的部分预留 Host KV。放不下的请求需等待正在运行的请求完成;永远放不下的请求会收到 HTTP 400。
  • 上下文缓存仍然有效:后续轮次、分支和共享前缀会就地复用已溢出的 KV,而不会重新做 prefill。

用法

加上 --kv-stream,并按溢出量设置 Host KV 层的大小:

./build/apps/ninfer-serve models/qwen3_8_27b_nvfp4.ninfer \
  --max-context 262144 --kv-capacity auto --max-concurrency 2 --kv-dtype fp8 \
  --kv-stream --host-kv-mib 65536
  • Host KV 大小: 每个请求可以溢出(提示 + max_tokens − 配额)个 token 的 KV。每个 token 的 KV 字节数为全注意力层数 × KV 头数 × 头维度 × 2(K 与 V)× 每个元素的字节数;若启用 MTP,则还要加上 MTP 层。请按实际并发的溢出量来设定 --host-kv-mib。
  • KV dtype: 与 BF16 相比,--kv-dtype fp8 可将每个溢出步骤读取的字节数减半。
  • --kv-capacity: 份额越大,每个上下文保留在 GPU 上的部分越多,溢出越少。
  • GPU 开销: 两个暂存缓冲区各自容纳完整 --max-context 的一层 KV,在 262k 上下文时使用 FP8 约共 1 GB,使用 BF16 约共 2 GB。
  • 固定内存: Host KV 在引擎整个生命周期内保持固定(pinned)状态,请为操作系统和页缓存留出空间。

性能

能放进其 GPU 份额的请求以常驻速度运行。一旦请求发生溢出,每一步都要通过 PCIe 读取其溢出的 KV:

负载(上下文 / GPU 份额)流式常驻
27B nvfp4,BF16 KV,35k / 12k,解码31 tok/s71 tok/s
Flash-Next,80k 提示 / 12k 份额,解码41–44 tok/s46–51 tok/s
27B 对话,在已溢出的 30.6k 上下文上的第二轮,TTFT188 ms170 ms(全新 prefill:4.2 秒)

两种情况下 prefill 都以常驻速率运行,流式输出与常驻运行结果一致。稠密模型的解码受主机到 GPU 的拷贝带宽(约 50 GB/s)限制,因此会随着上下文溢出比例增大而变慢;Flash-Next 几乎不变慢,因为它只读取选中的 token。

限制

  • DFlash 与 DFlash2 的草稿生成以及离线评分(ninfer-perplexity)不支持 --kv-stream。
  • 在 27B 上测试到两个并发的约 236k token 的 FP8 请求,在 Flash-Next 上测试到 80k token。设计细节见 Paged KV cache §6.5。

与原版 NInfer 的对比

上游原版 bace20dc 与本分支(引擎为 19f38b77)在相同的 RTX 5090 上、使用相同的产物、在同一套测试框架下运行。

方法。 上游的解码饱和度套件(tools/bench/run_serve_concurrency.py):

  • KV 与采样: INT8 KV,以及上游的随机采样配置。投机模式统一使用 --lm-head-draft。
  • 负载: C 个并发请求,每个请求从一条长推理(AIME)提示生成 4,096 个 token。
  • 指标: 在解码批次等于 C 的区间内,稳态解码 tok/s。
  • 重复次数: 每个数值是两次运行的平均值。单次运行可能波动约 10%,因此差异在约 5% 以内视为打平。
  • 调度: 原版与分支在每轮内交替运行,唯一例外是分支的固定 K=5 运行,它们作为独立会话稍后执行。

尚未重新测量。 分支的两个后续提交会影响下表数据:NVFP4 MLP 投影的 tensor-core A16 内核(e348a5d2,在仅分支运行中使 27B nvfp4 的 MTP 提升 5–9%)和自适应 MTP 批量草稿长度(52b06393)。下表早于这两个提交。

ninfer-ext 领先的场景

每个单元格给出分支的聚合解码 tok/s,然后是相对原版在该并发下最佳模式的变化幅度。

产物ninfer-serve 参数C=1C=2C=4C=8
Qwen3.8-27B groupwise-int + DFlash2--spec dflash2 --draft-tokens 7 --lm-head-draft260 +40%403 +48%522 +13%727 +3%
Qwen3.8-27B groupwise-int,仅 MTPC=1 和 C=8:--spec mtp --draft-tokens 5 --fixed-draft --lm-head-draft203 +22%658 +8%
C=2 和 C=4:--spec mtp --lm-head-draft308 +29%520 +32%
Qwen3.8-Flash-Next nvfp4参见 Qwen3.8-Flash-Next仅分支仅分支仅分支仅分支

作为参考,原版的最佳成绩:DFlash2 为 186 / 271 / 462 / 705 tok/s,仅 MTP 为 167 / 239 / 395 / 607 tok/s(C=1–2 时 K=3,C=4–8 时 K=5)。原版在加载 Flash-Next 产物时报 tensor: unknown member divisors 错误。

groupwise-int 的增益与已合并的 Q5 K-split MMA 路径(上游 PR #292)在小批量下的效果一致,该路径针对的是投机解码的验证轮。NVFP4 产物不含 Q5 权重,因此与原版打平。该归因尚未通过 A/B 实验独立验证。

原版领先或打平的场景

产物场景原版分支变化
Qwen3.8-27B groupwise-int

相似文章