两块RTX 4090实际能同时运行多少个代理?三周的llama.cpp并发数据——软上限5 @ 64k,硬上限9,以及原因。

Reddit r/LocalLLaMA 新闻

摘要

关于在两块RTX 4090 GPU上使用llama.cpp并发运行多个AI代理的基准测试报告,揭示了性能限制和Qwen模型的最佳配置。

我是一家中型非营利组织的首席技术官,我为该工作运行一个本地的Qwen堆栈作为编码子代理——部分出于原则(我处理的大量内容不应外泄),部分是因为批量代码工作的令牌费用在非营利预算下增长迅速。在三周内,我测试了四个模型和三种量化方法,以回答一个问题:实际上可以同时运行多少个代理,在什么上下文长度下,直到它不再有用?我完整地写下这个答案,因为我总是在评论中部分回答它。每当提到并发、量化选择或专家卸载时,我都会凭记忆输入这部分内容,而回复总是某个版本的为什么——为什么选择那个量化,为什么是那个插槽数量,为什么不多加几个代理。评论线程中的片段不是回答这个问题的好方法,所以这里一次性给出完整内容,包括数字和导致错误数字的错误。简而言之:我最初试图让一个122B MoE运行得更快,但放弃了,最终转向了一个27B模型——不是因为27B更准确(它并没有明显更准确),而是因为其他方面都更好。然后我发现,增加代理超过某个点后,没有任何收益。硬件:2× RTX 4090(可用44.6 GiB),Threadripper TRX50,128 GB DDR5——重要的是,四个内存通道中只有两个被占用(2×64 GB),这最终非常重要。使用llama.cpp,Windows系统,全程q8_0 KV。1. 为什么我没有保留122B(上图画廊中的图表1:测试的模型)Qwen3.5-122B-A10B在UD-IQ4_XS量化下运行——解码速度为18.75 tok/s,带专家卸载——但淘汰它的数字是每个代理工具调用11.91秒,而27B为3.40秒。对于一个每个任务进行数十次调用的代理,每次调用3.5倍的时间差异就是全部关键。Qwen3.6-35B-A3B在原始解码速度上看起来是赢家(78 tok/s——这是一个3B激活的MoE),并且它具有最快的每次调用时间。它还在五个执行的代码任务中失败了一个,涉及滑动窗口错误,而27B是5/5通过。这就是为什么“解码tok/s”是错误的头条指标,而我首先用每次调用秒数来解释的原因。我还尝试了Qwen3.8-Flash-Next(125B MoE,Qwen4架构预览),一旦发布就进行了测试。它需要一个未合并的PR构建,经过全面调整——专家放置、线程数、--poll、--cpu-strict、加载模式、llama.cpp自身的内存适配器——它最高达到23.7 tok/s。瓶颈不在GPU。在103.7 GiB权重中,71.7 GiB是路由专家,26.8 GiB是2000万行的n-gram嵌入表,因此大部分存在于系统RAM中,并通过内存总线每令牌流式传输。在--n-cpu-moe 40下,我测得有效带宽为14.1 GB/s,而峰值为83.2 GB/s——因为四个通道中两个是空的。一个单3090系统配DDR4能提供比我更好的绝对专家带宽。填充另外两个通道比我尝试的任何标志都更有价值,而我无法在当前价格下证明购买RAM的合理性。所以:搁置了,老实说,并公布了数字。2. 准确性:我无法区分量化方法在并发数字之前,使其有意义的注意事项。我的探测器在长文档的15%、50%和85%深度植入三个事实,并有六行诱饵行引用错误区域的相同字段,因此模型必须跨数万个令牌匹配标识符,而不是模式匹配标签。每个代理获得自己的文档,其中包含自己的秘密在独立数字带中,因此跨槽泄漏是可检测的。结果配置:24个召回率低于1.0,0个错误区域答案,0个跨槽泄漏,0个截断回复,0个未完美回答的最大提示,最大回答提示为251,557令牌。Q4_K_M、Q6_K_XL和Q8_K_XL在所有情况下都得分为1.00,包括九个代理运行时。这是天花板上的平局,意味着“无法区分”,而不是“相等”。诚实的说法是:在长达251k令牌的带干扰的长程检索中,这三种量化方法之间没有可测量的准确性差异。这不是“Q4是无损的”。检索达到饱和;需要跨植入事实综合的任务可能会在这里没有区分时区分开它们。因此,量化选择取决于吞吐量、延迟和显存——而不是质量。3. 并发结果(上图画廊中的图表2:并发缩放)相同模型,每个代理相同64k上下文,三种量化在每个量化能容纳的最大插槽数下:量化 插槽@64k 完成/分钟 中位数等待 聚合预填充 Q8_K_XL 3 1.54 112s 1,481 tok/s Q6_K_XL 5 1.63 168s 1,582 tok/s Q4_K_M 9 1.52 330s 1,572 tok/s 三倍的代理将总吞吐量改变了6%,并三倍了等待时间。机制在最后一列:聚合预填充吞吐量恒定在大约1,500令牌/秒,无论插槽数量如何。系统有一个预填充预算。插槽分割它;它们不乘以它。这特定于这种工作负载形状,并值得明确说明:代理提示巨大,回复简短,因此预填充占主导。解码密集型工作负载将更好地批处理——解码是带宽受限的,批处理将权重读取分摊到插槽上。不要将此概括为“并发对llama.cpp没有帮助”。软上限和硬上限软上限——64k下5个代理。超过此点,添加的插槽停止购买吞吐量,开始购买延迟。这不是失败,而是一个糟糕的交易:在9个代理下,你等待330秒获得与112秒时相同的1.5完成/分钟。硬上限——64k下9个代理,这是显存,而不是计算。(上图画廊中的图表3:显存预算)每个配置都在44.6 GiB上限的3.5 GiB内。更小的量化购买插槽,然后立即将节省的费用花在KV缓存上:Q4比Q8释放14 GiB权重,并将其中的17.5 GiB交给缓存。硬上限是算术,而不是调优。单个代理,巨大上下文(上图画廊中的图表4:上下文与延迟)延迟与提示大小线性相关,量化之间几乎无差异——拟合预填充速率Q4 1,482 tok/s,Q6 1,346,Q8 1,340。10%的差异,而不是插槽数量可能暗示的3倍。Q8显示出更高的固定成本(12.8秒截距 vs ~5.5秒);每个量化n=6,所以将其视为观察,而不是发现。我的操作点:UD-Q6_K_XL,5个并发代理,每个64k(总上下文327,680),41.1 GiB。与Q4在9个代理下的总工作量相同,但每个代理等待时间减半,权重保真度更高,且多出2.6 GiB的余量。Q4购买的额外插槽只有在没有东西等待它们时才值得拥有。4. KV缓存:q8_0,以及一种只挂起的组合测试在相同权重下,65k上下文,代码针对隐藏测试执行:K / V 通过率 编辑保真度 长程召回 tok/s 显存@65k q8_0 / q8_0 1.00 1.00 1.00 30.1 29.9 GiB f16 / f16 1.00 1.00 1.00 30.4 31.3 GiB没有可测量的质量差异,没有速度差异,便宜1.4 GiB。q8_0现在是我的标准。陷阱:混合--cache-type-k f16 --cache-type-v q8_0从未完成长预填充——在900秒内没有答案,两次,在一个比匹配q8_0配置在32秒内回答的提示更小的提示上。匹配的f16在24秒内回答了相同提示。匹配你的K和V类型。5. 将其用作Claude Code内的子代理值得提前知道的一点:Claude Code的模型字段只接受其自己的模型。你不能将本地模型注册为Claude Code子代理。你可以做的是将其作为MCP服务器暴露,因此它成为协调器调用的工具。我的是一个stdio MCP服务器,暴露local_ask_about_files、local_implement、local_edit_file、local_engine_status,指向127.0.0.1:8080上的27B。分工:协调器计划和决策。前沿子代理处理需要第一次正确的工作。本地Qwen回答“这些文件中有什么”,起草实现,并进行批量编辑——高令牌、低风险的工作,否则会烧预算。编辑保真度是决定这是否可用的指标。一个改写区域返回“未找到要替换的字符串”,这读起来像工具损坏而不是更差的量化。在Q6与q8_0 KV下,它测量为1.00。为了压力测试,我使用一个个人副项目——一个带有Rust模拟的游戏。
查看原文

相似文章