Qwen3.8-27B在24GB RTX PRO 4000 SFF上以256K上下文运行(432 GB/s带宽):通过MTP实现每秒50个token

Hacker News Top 新闻

摘要

本文详细描述了一项实验,该实验在24GB GPU上使用多token预测和自定义优化,实现了Qwen3.8-27B在256K上下文下每秒50个token的推理速度。

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

缓存时间: 2026/08/17 15:56

# Qwen3.8 27B 模型在 256K 上下文、24GB 显卡实现 50 TPS | Michał Piszczek 来源:https://piszczek.pl/blog/qwen38-27b-256k-50-tps-24gb-gpu 我为 Qwen3.8 的 MTP 预测器额外分配了 69.2 MiB 的精度。吞吐量从每秒 50.44 个令牌下降到 37.02 个令牌。这个结果概括了整个实验的核心:最佳的本地推理配置很少是由单独的"最佳"组件构成的。 我追求的是一个密集的 27B 模型、其完整的 262,144 令牌上下文、多模态输入、最大可用质量,并且在具有 24GB 显存的 NVIDIA RTX PRO 4000 Blackwell SFF 上实现投机解码。该服务器在打印 `model loaded` 之后,还必须能够承受真实的智能体工作负载。这次实验遵循了我之前撰写过的一个想法(https://www.linkedin.com/posts/michalpiszczek_three-days-ago-i-wrote-that-meta-had-finally-activity-7494047409161801728-MsHB):谨慎的操作可能和使用更大的模型同等重要。 最终完成的系统在当前的十次连续生产测试中,平均达到每秒 50.44 个令牌。在严格的运行时 A/B 测试中,定制的 llama.cpp 构建版本达到每秒 55.40 个令牌,而干净的主分支版本为每秒 45.42 个令牌,提升了 21.97%。与仅针对目标模型的贪婪解码相比,嵌入式 MTP 将吞吐量从每秒 21.19 个令牌提升至 59.46 个令牌,即提高了 2.81 倍。在真正占满的 256K 缓存的远端,它仍然能产生每秒 12.61 个令牌,而没有出现显存不足的错误。 这些数字来自不同的测试门,应分别看待。将它们合并成一个惊人的加速指标,虽然标题会更吸引人,但基准测试的可信度会下降。 > 最成功的配置来自于量化方案、预测器、CUDA 内核、内存布局和工作负载之间的契合。没有哪个组件能单独胜出。 ## 目标本就设定得不合理 Qwen3.8 27B(https://huggingface.co/Qwen/Qwen3.8-27B)是一个 64 层的密集模型。其重复模式包含三个门控 DeltaNet 层,后跟一个全注意力层,共计 48 个循环层和 16 个传统注意力层。它具有原生的 262,144 令牌上下文、一个单层 MTP 头,以及一个独立的 27 层视觉编码器。 硬件配置以一种有益的方式存在偏差: - **GPU0:** RTX PRO 4000 Blackwell SFF(https://www.nvidia.com/en-us/products/workstations/professional-desktop-gpus/rtx-pro-4000-sff/),24 GB 带 ECC 的 GDDR7 显存,192 位内存接口,432 GB/s 峰值内存带宽,24,467 MiB 报告容量,以及 sm120a 架构。它容纳目标模型、嵌入式 MTP、循环状态、计算图和 256K KV 缓存。 - **GPU1:** RTX 2000 Ada,15,996 MiB,sm89 架构。它容纳 F16 多模态投影器和其他辅助服务。 - **运行时:** Debian 13, CUDA 12.9.86, GCC 14.2,双架构 CUDA 构建。 只有 16 个全注意力层会随序列长度增长而扩展传统的 KV 缓存,这使得 256K 的目标看起来不那么荒谬。使用 Q4 的 K 和 V,该缓存(在分配器开销之前)大约占用 4.25 GiB。DeltaNet 则使用循环状态和检查点来替代。四个检查点是可用的最小数量;默认的 32 个检查点则占用了我本需他用的内存。 NVIDIA 标称的 432 GB/s 峰值带宽,是硬件上限,而非来自 llama.cpp 的应用层指标,但在这里它至关重要。自回归解码会反复流式传输量化权重,而随着上下文被填满,16 个注意力层的 KV 读取成本会越来越高。这就是为什么同一个配置,在生产任务上平均能达到每秒 50 个令牌,而在 261.5K 令牌缓存的末端只能达到每秒 12.61 个令牌。 最初的计划很简单:估算容量,选择量化方案,然后进行基准测试。但这台机器立刻让我明白,容量估算只是入场券。真正的考验在加载模型之后才开始。 ## 第一个赢家是 Q4_0,但它是个错误的赢家 我从 40K 上下文的公开 GGUF 文件开始。Q4_0 出人意料地强大。仅针对目标模型的解码达到每秒 22.40 个令牌,而使用 `n_max=3` 的 MTP 达到每秒 44.95 个令牌。它击败了更小的 Q3_K_M 和名义上更智能的 Q4_K_M 变体,因为文件大小和量化标签并不能描述实际运行的 CUDA 内核。 量化方案 | 仅目标模型 | MTP n=3 | 接受率 --- | --- | --- | --- Q3_K_M | 17.00 tok/s | 31.34 tok/s | 83.98% IQ4_XS, iMatrix | 20.63 tok/s | 34.40 tok/s | 64.87% Q4_0 | 22.40 tok/s | 44.95 tok/s | 80.40% Q4_K_M | 17.57 tok/s | 26.15 tok/s | 66.86% 然而,质量测试破坏了这个简单的答案。在一个相同的短篇 WikiText-2 控制测试中,IQ4_XS 得分为 6.1175 困惑度,而 Q4_0 为 6.3798。Q4_0 领先速度榜,但 Hermes 需要一个主力模型,而为换取几百毫秒的速度牺牲这点质量代价似乎太大。那样的话,我用一个 27B 模型就只是当成了大号自动补全工具。 另一个极端也失败了。Q4_1 达到了 6.1127 困惑度,略优于 IQ4_XS,但其内存占用使得 256K 上下文加上 F16 视觉编码器变得捉襟见肘。最佳平衡点存在于一个快速但粗糙的量化方案与一个精确但没有空间容纳系统其余部分的文件之间。 ## 加载 256K 上下文几乎无法证明什么 早期的容量测试看起来非常出色。Q4_0、MTP、Q4 KV、四个循环检查点和 F16 投影器都在 262,144 上下文长度下成功分配。但这并没有回答我真正关心的问题。 我用 261,500 个输入令牌填满槽位,生成了另外 256 个,然后重复使用热缓存。没有截断。没有显存不足错误。第一个 Q4_0 配置在缓存末端解码速度为每秒 12.06 个令牌,而在 40K 上下文附近时为每秒 44.95 个令牌。GPU 使用率保持在 99% 到 100%,而服务器大约使用一个 CPU 核心。瓶颈在于 16 个全注意力层读取巨大的、已被占用的 KV 缓存,而不是隐藏的 CPU 回退。 这改变了之后每次运行的基准测试方法。"262K 已加载" 被禁止出现在结果表格中。长上下文声明必须包含实际的令牌填充量、填充后的显存占用、热解码速度、截断状态和输出哈希值。 ## 现成的 NVFP4 量化未能通过质量关 Blackwell 有原生的 FP4 硬件,因此一个现成的 NVFP4-MEDIUM GGUF 看起来是显而易见的路径。其主体目标矩阵使用了 NVFP4,输出头为 Q8,嵌入层为 Q6,MTP 层为 IQ4_XS。它达到了每秒 40.46 个令牌的速度,并在大约 1,055 MiB 的空闲显存下完整适配了 256K 上下文加视觉编码器的配置。 它的困惑度是 6.4949。比 Q4_0 更差。 问题在于转换方案。来自源 FP8 检查点的注意力和 DeltaNet 权重与大型、容错的矩阵一起被扩展并重新量化为 NVFP4。原生算术使得文件处理速度快,但无差别的低精度量化损害了模型的敏感部分。硬件格式支持并不能告诉你应该将比特预算花在何处。 这次失败为我们设计定制量化提供了思路:使用 NVFP4 处理主体部分,然后只保护那些被我们自己的工作负载证明是重要的张量。 ## 我根据实际使用情况对模型进行了校准 校准语料库始于从 296 个 Hermes 会话中提取的 5,472 条消息。我将这些材料放在通用语料库之前,这样处理过的 153,600 个令牌就代表了编码、波兰语和英语对话、基础设施工作、工具调用以及我的智能体实际会遇到的尴尬混合内容。校准前进行了秘密扫描。其中没有 PEM 密钥、提供商令牌、GitHub 令牌、Slack 令牌或电子邮件地址。 `llama-imatrix` 收集了 497 个目标权重的重要性数据。NVFP4 在块量化过程中并不直接使用 iMatrix,因此我将该矩阵用作映射:大型容错张量保持原生 NVFP4;选定的注意力、DeltaNet 和 FFN 张量转换为 Q5_K 或 Q6_K;嵌入层使用 Q6_K;输出头使用 Q8_0。 第一个 5.14 BPW 混合方案以 6.0967 的困惑度成为质量胜者。但它也很慢,每秒 34.19 个令牌,而且太大,无法在 GPU 上同时容纳想要的投影器和 256K 上下文。这是一个好的实验,却是一个糟糕的生产模型。 第二个构建版本更紧凑: - **大小:** 16,321.38 MiB,5.01 BPW。 - **主体矩阵:** 原生 NVFP4。 - **敏感目标张量:** 使用 Hermes iMatrix 排名,选择 Q5_K 和 Q6_K。 - **嵌入层和输出层:** Q6_K 和 Q8_0。 - **嵌入式 MTP:** 原生 NVFP4。 - **困惑度:** 6.1197,而 Q4_1 为 6.1127。0.11% 的差距远低于这个短控制集的误差范围。 这成为了 `Qwen3.8-27B-Hermes-iMatrix-NVFP4-Balanced.gguf`。它保留了 Q4_1 参考模型的实测质量,运行更快,并为实际的推理服务栈留出了足够空间。 ## MTP 在 n=8 时存在一个陷阱 早期的扫描建议 `n_max=3`。值 4 到 7 由于被拒绝的草稿工作累积而变得越来越慢。然后 n=8 突然跳到了每秒 49.31 个令牌。 MTP n_max | TPS | 接受率 | 组合进程 VRAM --- | --- | --- | --- 3 | 43.11 | 78.73% | 18,352 MiB 4 | 37.79 | 62.22% | 18,502 MiB 7 | 29.10 | 42.95% | 18,950 MiB 8 | **49.31** | 48.33% | 19,100 MiB 9 | 49.02 | 43.95% | 19,250 MiB 12 | 43.31 | 34.34% | 19,700 MiB 20 | 30.60 | 19.97% | 20,900 MiB 曲线是锯齿状的。八个候选令牌恰好命中了一个有利的批处理和内核形状。九个并没有更快,而每个额外的候选令牌大约消耗 150 MiB 内存。在完整的 256K 分配下,n=9 在 `ubatch=256` 时因为多出一个 162 MiB 的 CUDA 图缓冲区而失败。将 ubatch 减小到 128 使其能够加载,但吞吐量下降到每秒 52.70 个令牌,32K 预填充性能也受到影响。n=10 则因为多出 81 MiB 而失败。即使一个实验性的调度器池也输给了一个 31 MiB 的分配。 我保留了 n=8,因为它是分配器开始产生显著影响之前的最后一个快速点。 ## 更精确的 MTP 让系统变得更差 我想要一个对照组来挑战 NVFP4 MTP 的选择。一个补丁过的 MTP 感知 iMatrix 运行处理了 300 个 Hermes 历史块,并添加了全部八个 MTP 矩阵。为了进行比较,所有 851 个非 MTP 张量都通过逐字节验证与生产版本完全相同。只有八个 MTP 权重张量发生了变化。 MTP 权重 | 额外大小 | 平均 TPS | 接受率 | 结果 --- | --- | --- | --- | --- 生产 NVFP4 | 基准线 | **50.441** | 48.329% | 保留 iMatrix Q5_K | 50.625 MiB | 48.733 | 46.751% | -3.39% 带关键 Q6_K 的 Q5_K | 69.219 MiB | 37.024 | 33.065% | -26.60% 更高位数的预测器可能更接近 BF16 源模型。但在生产环境中,它只有一个任务:预测这个量化的目标模型。MTP 头中的 NVFP4 误差恰好与 NVFP4 占比高的目标模型更匹配,因此其精确的提案存活率更高。独立的精度输给了量化-预测器对齐。 这改变了我对待预测器质量的方式。预测器和目标模型构成一个统一的量化系统,它们的交互决定了接受率和吞吐量。单独评估任何一半的质量都会错失重点。 ## DSpark 拥有整个第二个 GPU 但仍然失败 我还测试了 Qwen3.8-27B-DSpark(https://huggingface.com/RadixArk/Qwen3.8-27B-DSpark),这是一个具有马尔可夫头和置信度头的 1.36B 扩散模型预测器。一个 llama.cpp 补丁允许辅助模型在 GPU1 上运行,而目标模型保持在 GPU0 上。 最好的版本是 Q8_0,达到每秒 26.49 个令牌,比其每秒 19.70 个令牌的仅目标模型参考基准高出 34.5%。在同一比较中,嵌入式 MTP 达到了每秒 49.61 个令牌。DSpark 慢了 46.6%,并且使用了更多的总显存。该辅助模型是针对 FP8 目标训练的,而我们的是混合 NVFP4/Q5/Q6 目标。PCIe 流量和较弱的 Ada 显卡导致了其余的差距。 DSpark 的扩散机制有效,但这个辅助模型是为不同的目标训练的,在这台机器上失败了。 ## llama.cpp 的构建与模型本身几乎同等重要 一旦模型稳定下来,我逐个分支对运行时更改进行基准测试。在每次 A/B 测试中都禁用了 N-gram 投机,因为重复提示会教会缓存,并将表观吞吐量从每秒 46.77 个令牌推高到大约 180 个令牌。这在生产中有用,但在内核对比中则是“毒药”。 运行时 | 测量结果 | 决策 --- | --- | --- 干净主分支 b1045 | 45.422 tok/s | 基准线 #26001 + #26048 + #26705 | 45.866 tok/s | 保留,+0.98% 添加 #27173 MTP 链 | **55.402 tok/s** | 保留,相比干净主分支 +21.97% #27140 | 45.457 tok/s, 预填充 -1.59% | 拒绝 #26079 | 预填充 -1.96%, 热解码 -0.70% | 拒绝 最终的构建版本固定了 #26001(https://github.com/ggml-org/llama.cpp/pull/26001)、#26048(https://github.com/ggml-org/llama.cpp/pull/26048)、#26705(https://github.com/ggml-org/llama.cpp/pull/26705)、#27173(https://github.com/ggml-org/llama.cpp/pull/27173)、#24891(https://github.com/ggml-org/llama.cpp/pull/24891)和 #25635(https://github.com/ggml-org/llama.cpp/pull/25635)为已审核的提交哈希。这些补丁涵盖了门控 DeltaNet、CUDA 调度、更快的 Q4/Q5 投机验证、链式 MTP、循环检查点正确性以及 Flash Attention 位交错。 Flash Attention 补丁值得单独说明。在 32K 上下文下,它将预填充速度从每秒 759.38 个令牌提升至 815.64 个令牌,提高了 7.41%;热解码速度从每秒 37.26 个令牌提升至 38.23 个令牌,提高了 2.61%。它在注意力工作负载足够大、能够产生影响的地方提供了帮助。 构建器如果检测到任何 PR 头发生变动就会拒绝继续构建。它构建 sm89 和 sm120a 架构,然后运行采样、量化、门控 DeltaNet、Flash Attention 和 NVFP4 矩阵测试。没有这些检查,下一次上游更新可能会将一个快速的私有二进制文件变成一次宕机事件。 ## 最终的生产配置 当前活跃的模型是 `Qwen3.8-27B-iMatrix-NVFP4-256K-MTP`。重要的运行时设置如下: ``` CUDA_VISIBLE_DEVICES=0,1 MTMD_BACKEND_DEVICE=CUDA1 LLAMA_SPEC_CHAIN=1 GGML_CUDA_GRAPH_OPT=1 llama-server \ --model Qwen3.8-27B-Hermes-iMatrix-NVFP4-Balanced.gguf \ --device CUDA0 --n-gpu-layers 999 --fit off \ --ctx-size 262144 --parallel 1 --ctx-checkpoints 4 \ --flash-attn on \ --cache-type-k q4_0 --cache-type-v q4_0 \ --batch-size 512 --ubatch-size 256 \ --temp 0.6 \ --spec-type draft-mtp --spec-default \ --spec-draft-n-max 8 --spec-draft-n-min 0 --spec-draft-p-min 0 \ --spec-draft-type-k f16 --spec-draft-type-v f16 \ --spec-draft-backend-sampling \ --mmproj mmproj-F16.gguf \ --reasoning-preserve --jinja --metrics ``` `--spec-default` 在此构建中添加了 N-gram 投机。它保持启用状态,因为智能体会话会重复代码、工具模式和提示前缀。它从未出现在比较基准测试的数字中。堆栈中的温度为 0.6,而推理努力程度仍由 Hermes 根据请求控制。我不会全局强制设置为 xhigh。 `--agent` 是故意省略的。Hermes 拥有工具和 MCP。启用 llama-server 的智能体层会重复工具循环并扩大代码执行面。`--fit off` 也是故意的:自动调整会静默地更改上下文或卸载模型,以为其默认的显存余量预留空间,从而使我们测量好的配置失效。 F16 投影器在 GPU1 上占用 982 MiB。这比将其挤在 GPU0 上要好,也比运行一个独立的 4B 视觉模型好得多。活跃的 17 GiB 模型和二进制文件保留在本地 NVMe 上以实现快速重启;不活跃的实验则存储在较慢的网络存储上。 ## 我保留的数据 测试场景 | 结果 | 含义 --- | --- | --- 生产环境,10 次运行 | 50.441 tok/s 平均值, 49.420-51.397 | 当前可重复的工作点 严格运行时 A/B 测试 | 45.422 到 55.402 tok/s | +9.980 tok/s, +21.97% 贪婪目标模型 vs MTP | 21.189 到 59.456 tok/s | +180.6%, 2.81 倍 完整 261.5K 缓存 | 12.606 tok/s | 真实的远端上下文解码 全上下文预填充 | 226.750 tok/s | 261,500 输入令牌 GPU0 满载时 | 23,952 / 24,467 MiB | 大约 515 MiB 物理余量 GPU1 投影器 | 982 MiB | 视觉模型常驻内存 全上下文运行在没有截断或显存不足错误的情况下生成了 256 个令牌。冷启动和两个热启动输出具有相同的哈希值。十次连续的生产测试在其配置内也是确定性的。 仍有一个正确性注意事项。仅目标模型的贪婪解码和 MTP n=8 在量化目标上会产生不同的续写结果,这与 llama.cpp 的开放问题 #25618(https://github.com/ggml-org/llama.cpp/issues/25618)中描述的批处理不变性问题相符。两种模式在内部都是确定性的,输出也是连贯的,但这个最大吞吐量配置与仅目标模型解码相比,并不是按位分布保持不变的。当需要该特性时,n=1 是更安全的无损设置。 ## 值得保留的错误 我会从生产任务分布和三个硬性门槛开始:质量、真实上下文填充和确定性 A/B 测试。量化标签会放后考虑。 我也学到了要保留最后几百 MiB 的显存。我们在一个调优阶段有意在约 76 MiB 空闲显存下运行。它工作正常,直到一个 31 MiB 的调度器分配和

相似文章