第二张 AMD 显卡该怎么用?一次 128K Agent 双卡推理实测
-
提示:本文主要针对 AMD ROCm 平台,具体性能结论未必能直接套用到其他平台或者是硬件上,但其中关于多卡模式、长上下文与投机解码的部分方法和经验,或许仍有一定参考价值。
最初考虑折腾一套双卡平台,除了工作上的需求,多少也只是想玩一玩硬件,顺便弥补学生时代看着顶级配置流口水,却又求而不得的遗憾。
但真正让我开始认真看待“双卡”平台的,是在本地运行 Qwen3.8 完成了多项测试之后,我第一次清楚地感觉到开源模型在个人电脑上,已经不再只是聊聊天,或者部署一个去审查版本、写一点“刘备文”那么简单了。
许多过去必须依赖云端模型的任务,现在已经有机会留在本地完成。而比能力提升更重要的是,我开始慢慢信任本地模型,也开始愿意把真实、复杂,甚至需要持续数小时的工作交给它(也正是我现在正在做的)。而当我真正愿意把工作交给本地模型之后,瓶颈也随之发生了变化,问题不再只是它能不能运行,而是面对越来越重的实际任务,它还能不能在可以接受的时间里给出结果。
如果一次交互仍然需要等待太久,那么本地模型即使具备完成任务的能力,也很难真正转化为日常生产力。所以“能够使用”与“流畅使用”之间仍然隔着一道很现实的门槛,这个门槛就是速度。
而这篇文章,正是围绕这道门槛展开的一次实测。我想回答的不只是双卡能不能把模型跑起来,而是在真实的 128K Agent 工作负载下,第二张显卡究竟能够带来多少提升、TP 与 PP 应该如何选择、RCCL、MTP 和 DFlash2 又能否真正缩短等待时间。
因为字数比较多,所以每一部分我都通过回复来隔开,方便大家快速阅读。
服务器配置:
主板: 铭瑄 MS-iCraft B850 AIGA
CPU: AMD Ryzen 7 9700X
内存: 48GB DDR5-6000
显卡: Radeon AI PRO R9700 32GB + RX 9070 16GB
硬盘: 致态 TiPro9000 2TB NVMe
系统: Ubuntu 26.04 LTS,Kernel 7.0
ROCm: 7.14.0 -
零、 从单卡边界开始
这次双卡测试并不是从零开始组建一台AI服务器,而是建立在一张AMD Radeon AI PRO R9700已经能够承担实际工作的基础上。它拥有32GB显存,单独运行Qwen3.8-27B并没有问题。但真正促使我考虑第二张显卡的,是随着使用不断深入,我开始越来越“信任”模型,也开始不断地给它加担子。
最初,我交给本地模型的可能只是一个独立问题、一个代码片段,或者一份篇幅有限的文档。但随着任务复杂度不断提高,完整的代码仓库、运行日志、内部文档、历史会话和工具输出,开始同时进入它的工作上下文。
尤其是在Harness持续调度的长程任务中,模型需要保留已经完成的分析、修改记录、命令输出和失败信息,才能在后续步骤中继续工作。每进行一次搜索、编译、测试或诊断,新的结果都会继续追加到上下文中。原本只有几千token的会话,很快就会增长到数万token。到了后来,64K以上不再是偶尔触及的极限“工况”,而逐渐成为长程任务中的常见状态。在一些复杂的编码、逆向分析和故障诊断任务中,上下文甚至会长时间停留在100K到128K附近。
这时,问题已经不再只是显存够不够。
R9700依然能够把模型运行起来,但上下文越长,第一次载入完整资料所需的Prefill时间就越长。即使缓存能够命中,后续新增的用户输入和工具返回仍然需要在已有的长上下文上完成增量预填充,并且模型逐个生成输出token时,也需要持续访问越来越庞大的KV Cache,模型仍然能够完成任务,但它开始变慢了。
这种变慢通常不会表现为显存不足或程序报错,更多时候只是一次工具调用后的等待、一段代码生成过程中的停顿,或者模型在十几万token深度下逐渐下降的输出速度。单次等待似乎还可以接受,但在一个需要连续运行数小时、经历几十次工具调用的Agent任务中,这些延迟最终都会累积成真实的时间成本。因此,我考虑增加第二张显卡的初衷,并不只是获得更多显存,也不是单纯为了把更大的模型装进去。我真正想解决的问题是一个很现实的问题那就是:
当64K到128K上下文已经成为日常工作负载时,第二张显卡能否让模型继续保持足够流畅的响应速度?
换句话说,R9700已经解决了本地模型能不能承担这项工作的问题,而接下来的这张副卡,则需要去解决怎样能更快地完成这些工作的这个问题。
-
一、为什么是RX 9070?
严格来说,RX 9070并不是我为这套系统确定的最终升级方案。它更像是一张便宜的的“测试卡”,我想先用它验证主板、PCIe拓扑、P2P和ROCm多卡路径,再决定未来是否值得升级到双R9700,我选择它,主要有三个原因:
1、 P2P
第一个原因,也是我在增加第二张显卡之前最没底的。不是Linux和ROCm能不能识别两张卡,而是这套消费级B850平台,究竟能不能真正实现PCIe Peer-to-Peer,也就是P2P访问。
我的主板拥有两条物理全长插槽,也能够把来自CPU的PCIe通道拆分成x8/x8,但这并不等于两张显卡一定能够直接访问彼此的显存。PCIe拓扑、BIOS、IOMMU和驱动实现中的任何一个环节,都可能让看起来一切正常的双卡平台,在真正进行跨卡通信时退回CPU内存中转,这也是我在租来RX 9070之前最担心的问题。
虽然P2P不可用,第二张显卡不会完全失去价值,它仍然可以承担一部分模型权重和KV Cache,也可以在PP模式下作为容量扩展卡使用。但这和我真正想测试的东西已经不是一回事了。
PP只需要在不同模型阶段之间传递数据,而TP几乎每一层都要进行跨卡同步。如果这些通信必须绕经CPU内存,双卡就可能从共同加速退化成只是勉强共同运行,甚至因为通信和同步开销而比单卡更慢。所以在实验开始之前,我其实已经为它准备好了两种完全不同的结局:
1) 如果平台不支持P2P,就把RX 9070降级为容量扩展卡或者普通副卡,不再对TP性能抱太高期待。以后要升级那我就卖掉R9700,再补差价更换一张48GB魔改版RTX 4090 D,不再考虑双卡。
2) 如果平台支持P2P,那就把RX 9070当成进入多卡世界的第一张实验卡,实际看看PP、TP和RCCL究竟能为本地推理带来多少提升,同时也为未来可能升级的双R9700提前验证平台、积累经验。
幸运的是,最后的结果走向了第二条路。
实测中,这张B850主板确实能够让两张显卡运行在PCIe 5.0 x8/x8,并实现双向P2P访问,跨卡传输速度约为26.08GiB/s。对我来说,这才是整套双卡实验真正成立的那一刻。
不过P2P测试通过并不代表双卡一定会更快。它只是证明两张显卡具备了直接协作的基本条件,排除了所有数据都必须绕经CPU内存的这一最糟糕的情况。如果把后面的性能测试看作一场比赛,那么P2P不是最终成绩,而只是一张入场券。但至少到了这里,TP、PP和RCCL终于值得被认真放到同一张测试表上了。
2. 架构
第二个原因,则是RX 9070与R9700在软件和硬件层面具有较高的一致性。
两张显卡都属于RDNA4,并且在ROCm中使用相同的LLVM目标gfx1201。这意味着llama.cpp的HIP内核可以面向同一个GPU目标进行构建,不需要像R9700搭配RX 7900 XTX那样,同时处理RDNA4 gfx1201与RDNA3 gfx1100两套不同目标。
这并不代表跨架构双卡一定不能工作,但在一个本身仍带有实验性质的多卡环境中,少一种架构、编译目标和驱动路径的差异,就少一种出现兼容问题时需要排查的变量。而且从规格上看,两张卡也有一些适合组成双卡的共同点。根据AMD官方规格,R9700和RX 9070都拥有256-bit显存接口与640GB/s标称显存带宽,但RX 9070只有56个CU和16GB显存,FP32峰值也明显低于R9700。
当然这种组合仍然是不对称的,在TP中R9700可能需要等待较慢的RX 9070,16GB的显存容量也限制了模型和KV Cache的分配比例。但与跨代架构组合相比,它至少减少了软件层面的变量。而相同的标称显存带宽,也让RX 9070在部分偏向显存带宽的Decode负载中,不至于仅凭显存容量和CU数量就被视作一张过慢的副卡。
项目 R9700 RX 9070 架构 RDNA4 RDNA4 LLVM目标 gfx1201 gfx1201 计算单元 64 56 显存 32GB 16GB 显存位宽 256-bit 256-bit 显存带宽 640GB/s 640GB/s FP32峰值 47.8TFLOPS 36.1TFLOPS 3. 价格
京东有“租赁”服务,RX 9070的售价低于6000元。通过租赁方式使用两个月,租金大约为750元。而RX 9070 XT的两个月租金则接近1100元,两者相差三百多元。
虽然RX 9070 XT拥有64个CU和更高的运行频率,计算能力确实强于RX 9070。但两者同样只有16GB显存,显存位宽同为256-bit,标称显存带宽也同为640GB/s。
考虑到这次实验的首要目的,是验证主板P2P、ROCm多卡和llama.cpp的TP/PP路径,而不是直接搭建最终配置,我没有必要为尚未确认的平台额外增加三百多元试错成本。
RX 9060 XT 16GB虽然价格可能更低,也同属于RDNA4,但它使用的是gfx1200目标,只有32个CU、128-bit显存接口和320GB/s标称显存带宽,只有RX 9070和R9700的一半。这意味着它不仅引入了另一个GPU编译目标,计算规模与显存带宽也和R9700相差更大。在PP中它仍然可以承担一部分层和显存容量,但在需要频繁同步的TP中,更可能成为限制整体速度的参与者。
由于这次实验本来就在为未来的双R9700做平台验证,选择RX 9060 XT得到的结果也不够具有代表性。
综合来看,RX 9070并不是R9700最完美的长期搭档,却是当时最适合验证这套平台的一张测试卡。它没有RX 9070 XT那么强,也没有第二张R9700那么理想,但它用相对有限的试错成本,提供了16GB额外显存、相同的gfx1201目标和相同的标称显存带宽。
但对我来说,这已经足以让它完成这次实验最重要的任务,就是回答这套平台,究竟值不值得继续走多卡路线。
-
二、两张显卡,究竟应该怎样分工?
RX 9070已经选好了,P2P测试也顺利通过了测试。但这只解决了两张显卡能不能直接交换数据的问题,还有一个更加关键的问题没有回答,那就是它们究竟应该怎样一起计算?
毕竟,llama.cpp可不会因为系统里多出一张显卡,就自动把两张卡的性能相加,显卡插上去只是第一步,模型权重、KV Cache和计算任务究竟怎样分配,仍然需要由我们自己选择。
相同的硬件可以采用完全不同的模型切分方式,而切分方式会直接影响跨卡通信频率、显存分配以及最终的性能特征。这一步选得是否合适,可能决定第二张显卡究竟是一块加速器,还是一项额外的通信负担。
目前llama.cpp的--split-mode支持none、layer、row和tensor四种模式。其中none只使用单张显卡,真正涉及多卡切分的是layer、row和tensor。
row模式会按行切分部分模型权重,但中间结果和KV Cache仍主要放在指定的主显卡上。它没有充分利用两张卡共同分担长上下文KV Cache,与这次利用48GB总显存服务128K Agent的目标并不完全一致,因此本文不再展开测试(官方也不推荐使用)。
最终,我真正需要比较的是两条路径,layer模式(也就是下文所说的PP),tensor模式(也就是仍处于实验阶段的TP)。
它们使用的是同一套硬件,却代表了两种完全不同的多卡思路。把不同的模型层交给不同显卡(PP)
PP在llama.cpp中对应 --split-mode layer。
它会把模型的不同层分配给两张显卡。例如,前面一部分层由R9700负责,后面一部分层由RX 9070负责。数据完成一段计算后,再通过PCIe传递给下一张显卡继续处理,它更像是把一条生产线拆成了两个工段。
这种方式的优势是跨卡通信相对简单。模型不需要在每一层都让两张显卡交换中间结果,通常只需要在不同模型阶段的边界传递数据。因此,即使PCIe互联速度远不及显存带宽,PP仍然比较容易获得可用的性能。
兼容性也相对成熟,量化KV Cache、视觉模型和独立草稿模型通常更容易在PP模式下运行。
但PP的问题同样明显,那就是对于单个token来说,两张显卡并不是同时完成同一层计算的,而是依次经过两个模型阶段。R9700完成自己的部分之后,RX 9070才能继续处理后面的层。
所以在单并发的Agent任务中,这种串行关系会限制逐token生成速度。即使两张卡都在参与推理,也不代表生成速度一定会比单卡更快。
让两张显卡共同计算每一层(TP)
TP对应 --split-mode tensor。
与PP不同,TP不是把不同模型层分别交给两张卡,而是把同一层中的张量切分到两张显卡上。R9700和RX 9070会同时计算这一层的不同部分,然后再通过跨卡通信合并结果,继续进入下一层,它更像是让两个人同时完成同一道工序。
这种方式更有机会降低单个token的生成延迟,也更加符合我最初希望双卡共同加速的设想。但它对跨卡通信的要求明显高于PP,因为两张卡几乎每一层都要交换和归约数据,任何通信延迟都会不断累积。所以TP是本次实验的预期目标。
这也是PCIE-P2P对TP格外重要的原因。如果没有P2P,每一层的同步都可能绕经CPU内存,而即使P2P已经可用,PCIe延迟、两张卡的性能差异以及PCIE归约实现,仍然可能限制最终收益。
两种模式可以简单概括为:
项目 PP / Layer Split TP / Tensor Split 切分对象 不同模型层 同一层中的张量 跨卡通信 主要发生在阶段边界 几乎每层都要通信 对P2P依赖 相对较低 很高 单流Decode 容易受串行阶段限制 更有机会降低延迟 长提示Prefill 通常较有优势 受跨卡同步影响 软件兼容性 相对成熟 仍带有实验性质 异规格双卡问题 阶段负载不均 快卡频繁等待慢卡 存算比例
在本次测试中无论使用PP还是TP,都不能不加思考地把两张显卡按照50/50平均切分。
R9700拥有32GB显存和64个CU,RX 9070则拥有16GB显存和56个CU。两张卡的显存容量比例接近2:1,但计算单元比例只有大约53:47。这意味着显存分配和算力分配之间,本身就不存在一个绝对完美的比例。
如果按照50/50切分,计算负载看起来比较接近两张卡的CU比例,但RX 9070只有16GB显存。在128K上下文、FP16 KV、mmproj和投机解码组件同时存在时,它会比R9700更早接近显存上限,也很难为计算缓冲区和软件版本变化留下足够余量。
如果完全按照32GB与16GB的显存容量切成67/33,又可能把过多计算交给R9700,没有充分利用RX 9070的56个CU。最终,这次严格对照测试主要使用了一个相对保守的折中比例:
--tensor-split 65,35
它更接近两张卡的显存容量比例,优先保证16GB的RX 9070不会过度接近显存边界,同时让计算能力更强、显存更大的R9700承担更多模型权重和额外组件。
在128K、FP16 KV、完整模型、mmproj和MTP同时加载的情况下,两种实际测试过的比例大致表现为:
切分比例(R9700 / RX 9070) R9700显存占用 RX 9070显存占用 65/35 约23.3GB 约12.9GB 60/40 约22.1GB 约14.1GB 60/40能够更充分地使用RX 9070,但副卡显存余量会进一步缩小,而65/35则为16GB显存留下了更安全的运行空间。因此,后面的严格性能对照统一采用65/35,避免显存压力变化干扰测试结果。
在测试开始之前,还有一个很容易踩坑的问题,那就是设备顺序。
本机原始设备编号为:
GPU0 = RX 9070 GPU1 = R9700但测试时使用了:
HIP_VISIBLE_DEVICES=1,0这条环境变量重新排列了ROCm设备顺序。进入llama.cpp之后,R9700成为逻辑上的ROCm0,RX 9070成为ROCm1。
所以:
--tensor-split 65,35在本文中实际表示:
R9700:65 RX 9070:35这里的65和35本质上是相对权重,并不强制要求总和等于100写成65/35,只是更方便按照百分比理解。
另外值得一提的是,--tensor-split这个名字也容易让人误以为它只对TP有效。实际上,在layer模式中,它同样用于决定不同显卡获得模型层的相对比例,在tensor模式中,则用于控制模型张量和KV Cache在不同设备间的目标分配比例。
还需要注意,65/35只是模型切分的目标权重,并不意味着两张显卡最终的显存占用、计算量和执行时间都会严格保持65/35。
KV Cache、计算缓冲区、输出层、mmproj、草稿模型以及无法无限细分的模型层,都会让最终结果发生偏移。它不保证两张卡最后一定按照同样的百分比工作。
RCCL
TP会把同一层的模型张量分到两张显卡上并行计算。完成各自的部分后,两张卡还需要对中间结果进行Reduce和Sync,才能继续进入下一层。这个过程会在模型的许多层中反复发生。
如果把P2P理解成两张显卡之间的一条直达道路,那么RCCL负责的,就是怎样在这条道路上组织跨卡数据交换。
RCCL是AMD面向多GPU集合通信提供的运行库,可以为Reduce、AllReduce等操作提供专门的通信实现。启用RCCL之后,llama.cpp可以使用这套路径完成TP中的部分跨卡Reduce,减少普通后端路径中的同步和数据合并开销。
但需要强调的是,RCCL并不是一个让TP从不能运行变成能够运行的开关!
在没有RCCL时,llama.cpp依然可以通过普通后端路径完成跨卡Reduce,启用RCCL只是尝试让这部分通信更加高效。最终能够带来多少提升,取决于跨卡通信在整个推理过程中的耗时占比。
如果瓶颈主要来自跨卡Reduce,那么RCCL的收益就会比较明显。如果主要时间已经消耗在长上下文Attention、显存访问或者模型本身的计算上,那么RCCL即使优化了通信,在总耗时中的提升也可能很有限。
-
三、哪条路线更快?
前面讲了这么多硬件、切分比例和通信方式,到这里终于该看成绩了。
不过,在公布结果之前,还需要补上最后两个会出现在表格里的名字,那就是 MTP 和 DFlash2 。
它们都属于投机解码方案。简单来说,就是先尝试一次预测多个候选token,再由主模型统一验证,如果候选token被接受,主模型就不必严格按照一次一个token的方式向前生成。两者的区别在于MTP使用模型自身携带的多token预测能力,不需要额外加载独立草稿模型。而DFlash2需要加载一个额外的草稿模型,由草稿模型提出候选,再交给主模型验证。
经过兼容性测试后,最终形成了三条可以实际运行的路线:
路线 多卡模式 投机方式 定位 TP + MTP Tensor Split + RCCL 模型内置MTP 双卡性能路线 PP + MTP Layer Split 模型内置MTP 简单、省显存的PP路线 PP + DFlash2 Layer Split 外部DFlash2 Q4草稿 PP下追求更高解码性能 PS:至于TP + DFlash2,它会在当前llama.cpp的分片输出权重与GET_ROWS路径上失败,因此没有进入正式性能对照。这个问题会在后文单独说明。
为了尽量减少变量,我重新进行了一轮对照,保持以下条件一致:
项目 条件 主模型 Qwen3.8-27B HauhauCS-Aggressive Q6_K_P 双卡比例 R9700 / RX 9070 = 65/35 上下文容量 163840 tokens 主模型KV Cache FP16 mmproj 加载相同的BF16视觉投影 并发 parallel=1R9700功耗上限 210W RX 9070功耗上限 200W 采样 temperature=0,seed=123TP路线 TP + RCCL + MTP, n-max=3PP路线 PP + DFlash2 Q4_K_M, n-max=4测试时,每个端点都尽量保持输入内容、输出长度和缓存状态一致。我选择了四种更接近日常Agent使用方式的负载。
第一种是96-token短输入,并强制生成512个token。这里的96是96个token,不是96K。它主要考察短输入、持续生成时的Decode能力,Prefill数字只作为记录,不适合拿来判断长提示吞吐。
第二种是从零载入129481个token,再生成256个token。它模拟缓存完全失效、服务重启或者上下文压缩后重新载入完整历史的情况。
第三种是命中128965个token的长前缀缓存,只追加1547个新token,再生成256个token。它最接近日常Agent完成一次工具调用后,把新结果追加到既有上下文中的场景。
第四种是在命中129996个token之后,一次性追加31413个token,再生成256个token。它模拟工具突然返回大量日志、源码或文档的突发情况。
TP + MTP与PP + DFlash2结果:
测试场景 路线 Prefill Decode 总墙钟时间 96输入 + 512输出 TP + MTP 225.823 t/s 44.901 t/s 11.813秒 96输入 + 512输出 PP + DFlash2 206.037 t/s 42.116 t/s 12.606秒 129481冷输入 + 256输出 TP + MTP 397.916 t/s 33.610 t/s 333.067秒 129481冷输入 + 256输出 PP + DFlash2 269.625 t/s 24.424 t/s 490.752秒 命中128965,追加1547 + 256输出 TP + MTP 226.741 t/s 36.621 t/s 13.872秒 命中128965,追加1547 + 256输出 PP + DFlash2 159.674 t/s 22.839 t/s 20.938秒 命中129996,追加31413 + 256输出 TP + MTP 221.005 t/s 34.119 t/s 149.716秒 命中129996,追加31413 + 256输出 PP + DFlash2 147.937 t/s 22.997 t/s 223.533秒 TP + MTP相对PP + DFlash2的领先幅度:
测试场景 Prefill领先 Decode领先 总墙钟缩短 96-token短输入、长生成 9.60% 6.61% 6.29% 129K冷重建 47.58% 37.61% 32.13% 129K高命中、追加约1.5K 42.00% 60.34% 33.75% 约130K高命中、突发约31K 49.39% 48.36% 33.02% 结果比我最初预期的更加明确。
在96-token短输入中,两条路线的差距并不大。TP + MTP的总时间只缩短了6.29%,说明当输入很短、主要时间用于持续生成时,两种投机方案都能获得比较接近的效果,但进入真正的长上下文之后,差距迅速拉开。
在129K冷启动中,TP + MTP将总时间从490.752秒缩短到333.067秒;在高缓存命中、只追加约1.5K的情况下,总时间从20.938秒缩短到13.872秒;面对约31K的突发追加,总时间也从223.533秒缩短到149.716秒。
三种长上下文工况的最终结果非常接近,TP + MTP都把总时间缩短了大约32%到34%,这也推翻了我在测试前的一个重要预期。
我原本认为,PP通信更少,应该更擅长完整吞入长提示。TP则主要在深上下文Decode中占优。但严格复测表明,在这套具有真实P2P、启用RCCL的R9700 + RX 9070平台上,TP不仅提高了Decode,也显著提高了129K冷启动和长上下文追加时的Prefill。换句话说,TP + MTP并不是只在某一个指标上跑出了更漂亮的数字,而是在三种最接近真实128K Agent的工作负载中,都取得了更短的端到端等待时间。
因此,这轮测试给出的第一个核心结论是:
在当前硬件、模型和软件构建下,TP + RCCL + MTP是这套双卡系统的默认性能路线。
不过,这张表同时留下了另一个很有意思的问题,PP + DFlash2在部分测试中拥有更高的草稿接受率和更长的连续接受长度,按直觉似乎应该生成得更快,但最终却没有赢得端到端速度。
为什么接受了更多候选token,反而还是更慢?
这就是下一部分需要回答的问题。
-
四、接受率≠速度。
投机解码最容易让人产生误解的指标,就是接受率。
从原理上看,草稿模型一次提出多个候选token,主模型验证后接受得越多,理论上就越能减少逐token前向计算。因此,看到更高的接受率和更长的连续接受长度时,我们很自然地会认为它应该更快,但实际速度并不只由接受率决定。
一次投机解码所消耗的时间,包含以下几个部分:
草稿模型生成候选token -> 主模型批量验证候选 -> 接受或者拒绝候选并更新KV Cache -> 多卡之间完成模型计算和数据同步 -> 采样器选择最终输出。
所以更准确的理解应该是:
实际速度 ≈ 每轮最终接受的有效token ÷ 草稿生成、主模型验证、跨卡同步和采样的总耗时
这就好像是一个公式,接受率只能反映分子的一部分,却没有告诉我们为了获得这些候选,系统在分母上付出了多少成本。
DFlash2接受率更高,却没有赢下对照实验
在96-token短输入中,PP + DFlash2的接受率为57.96%,平均连续接受长度为3.32,TP + MTP的接受率只有48.24%,平均连续接受长度为2.44。
单看这两个数字,DFlash2明显更漂亮,但最终Decode速度却是TP + MTP:44.901 t/s 而 PP + DFlash2:42.116 t/s。TP + MTP仍然更快,端到端时间也从12.606秒缩短到了11.813秒。
在129K冷启动测试中,同样的现象更加明显:
路线 接受率 平均接受长度 Decode TP + MTP 52.88% 2.58 33.610 t/s PP + DFlash2 57.28% 3.27 24.424 t/s PP + DFlash2接受了更多候选,平均连续接受长度也更高,但Decode仍然明显落后。
DFlash2提高了PP的投机效率,但它无法消除PP本身的串行分层路径。对于每一个需要主模型验证的token,数据仍然要依次经过R9700与RX 9070负责的模型阶段。相反,TP + MTP虽然接受率不一定更高,但两张显卡能够同时参与同一层的主模型计算。在真实P2P与RCCL都能够工作的情况下,主模型基础路径上的优势,最终超过了DFlash2在接受率上的领先。
所以这张主对照表能够证明“TP + MTP比PP + DFlash2更快”,却还不能单独证明“MTP这种投机算法比DFlash2更好”,如果要把多卡模式和投机算法拆开,就必须让它们在相同的PP环境中再比一次。
MTP与DFlash2的差距其实很小
为了只比较两种投机方案,我又补充了一轮PP内部对照,结果如下:
测试场景 PP + MTP PP + DFlash2 端到端结果 96输入 + 512输出 267.90 prefill / 39.70 decode / 13.30秒 206.04 prefill / 42.12 decode / 12.61秒 DFlash2快5.22% 129K输入 + 256输出 260.32 prefill / 23.81 decode / 508.14秒 269.62 prefill / 24.42 decode / 490.75秒 DFlash2快3.42% 命中约129K,追加1547 + 256输出 149.12 prefill / 25.17 decode / 20.59秒 159.67 prefill / 22.84 decode / 20.94秒 MTP快1.65% 命中约130K,追加31413 + 256输出 140.30 prefill / 21.77 decode / 235.71秒 147.94 prefill / 23.00 decode / 223.53秒 DFlash2快5.17% DFlash2在四项测试中三项领先,但它的领先幅度并不大,基本集中在3%到5%。
唯一由MTP获胜的,是高缓存命中后只追加约1.5K的Agent工况。这里PP + MTP的Prefill低于DFlash2,但深上下文Decode达到25.17 t/s,高于DFlash2的22.84 t/s,最终以20.59秒对20.94秒小幅获胜。
这组结果说明,两种投机方案并不存在数量级上的差距。
DFlash2通常能够获得更长的连续接受长度,在多数PP工况中略快,MTP则不需要加载外部草稿模型,在部分深上下文增量生成中也可能取得更好的Decode表现。
因此,主对照中TP + MTP领先32%到34%,主要原因并不是MTP在投机算法上全面击败了DFlash2,而是TP主模型计算路径本身取得了明显优势。
MTP更省显存,也更容易部署
两种PP投机方案在显存分配上有差异。
路线 R9700显存占用 RX 9070显存占用 PP + MTP 约25.0GB 约12.9GB PP + DFlash2 Q4 约26.7GB 约11.7GB DFlash2需要把额外草稿模型放在R9700上,因此比MTP多占用大约1.7GB主卡显存。它还需要单独下载草稿文件,并使用支持DFlash2的实验性llama.cpp构建。
MTP则直接使用模型内部能力,不需要额外草稿文件,也为R9700留下了更多显存余量。
所以,如果PP只是TP不可用时的兼容降级路线,我更倾向于把MTP作为默认选择。如果愿意为多数工况大约3%到5%的提升,增加约1.7GB显存占用和一套额外草稿依赖,再考虑启用DFlash2会更合理。
为什么没有TP + DFlash2?
既然TP的主模型路径更快,而DFlash2在PP中又能获得不错的投机效果,一个很自然的想法就是:
能不能把它们组合起来,使用TP + DFlash2?
我确实进行了尝试,但很可惜当前版本无法正常运行。
DFlash2的候选选择器需要从主模型的输出权重中取出指定token对应的行,也就是执行GET_ROWS。在TP模式下,这张输出权重表已经被切分到两张显卡上,当前llama.cpp的多卡后端还不能在这条路径中,正确完成跨分片取行与结果聚合。
最终表现为split axis、shared tensor或者backend ownership相关错误,缺少DFlash2候选选择器面对分片输出权重时的完整语义。
所以至少在当前构建中,DFlash2只能留在PP路径上。
-
五、RCCL究竟带来了多少提升?
前面提到,RCCL是对跨卡Reduce路径的一次优化。
这次测试分别在4K短上下文和128K长上下文下,对启用与关闭RCCL进行了严格对照。
4K上下文:Prefill提升约9%
TP构建 Prefill Decode 不使用RCCL 1183.30 t/s 28.53 t/s 使用RCCL 1291.31 t/s 29.18 t/s 提升 9.13% 2.27% 在4K测试中,RCCL带来的Prefill提升比较明显,从1183.30 t/s提高到1291.31 t/s,增幅约为9.13%,Decode也有所提高,但幅度只有2.27%。
这说明RCCL确实优化了TP的跨卡通信,但跨卡归约并不是Decode中的唯一成本。即使通信变得更高效,模型计算、显存访问和采样等部分仍然不会因此消失。
128K上下文:收益收敛到约2%
TP构建 Append Prefill Deep Decode 不使用RCCL 266.89 t/s 19.28 t/s 使用RCCL 273.24 t/s 19.55 t/s 提升 2.38% 1.44% 第二组测试在已有约128K上下文的情况下,继续追加约1K新输入并生成输出。
到了128K,RCCL带来的提升明显缩小,增量Prefill从266.89 t/s提高到273.24 t/s,提升2.38%;Deep Decode从19.28 t/s提高到19.55 t/s,提升1.44%。
我更倾向于把这种变化理解为瓶颈占比发生了转移。
在4K工况下,跨卡通信和同步在总耗时中占有相对明显的比例,因此优化归约路径能够直接反映到Prefill速度上。随着上下文增长到128K,Attention和KV Cache访问开始消耗更多时间,跨卡通信即使变快,在整个任务中的占比也随之下降。
RCCL仍然有效,只是它能够优化的那部分,已经不再是主要矛盾,所以,对这套平台来说,RCCL值得作为TP的默认构建选项,这份收益不算惊人,但方向稳定,而且没有在本次测试中观察到明显负作用。既然决定使用TP,就没有理由主动放弃它。
-
六、我应该怎样部署?
经过前面的测试之后,我并不打算为每一种任务都准备一套完全不同的启动参数。
理论上,针对冷启动、缓存追加、短输入生成和大段突发内容分别切换Llama的启动配置,或许能够再挤出一点性能,但在实际工作中,频繁重启服务、修改参数和重新建立缓存,本身也会带来额外成本。
所以我更需要的是一条能够覆盖大多数128K Agent任务的默认路线,所以我选择了,TP + RCCL + MTP。
核心配置为:
HIP_VISIBLE_DEVICES=1,0 --split-mode tensor --tensor-split 65,35 --ctx-size 163840 --parallel 1 --cache-type-k f16 --cache-type-v f16 --flash-attn on --fit off MTP n-max=3这选择163840作为分配容量,并不意味着日常任务一定要把上下文用到160K,我的实际目标仍然是把主要工作上下文长度控制在128K附近,额外留下大约32K空间,用于工具突然返回大量日志、源码或者文档。这样既能避免上下文刚到128K就立刻触发裁剪,也能为Agent完成当前步骤留下足够余量。
-
结论:第二张显卡究竟值不值得?
回到最开始的问题,那就是这套AMD平台究竟值不值得继续折腾多卡?
我的答案是:
值得,但需要清楚自己得到的是什么。
RX 9070并没有让R9700的性能简单翻倍。这套同架构异规格双卡仍然受到显存容量不对称、计算能力差异和PCIe通信的限制。TP需要快卡等待慢卡,PP又无法自动提高单流生成速度,额外的驱动、构建和参数维护也都是真实存在的成本。
但第二张显卡同样不只是增加了16GB显存,在严格同条件测试中,TP + RCCL + MTP把129K启动、高缓存命中追加和31K突发追加三种长上下文任务的总等待时间缩短了大约32%到34%。
对于一次十几秒的短对话,这种差距可能不算久,但对于一个持续运行数小时、不断搜索、编译、测试和调用工具的Agent任务,每一轮节省下来的几秒或者几分钟,最终都会累积成可以真实感受到的工作效率。
这也是这次实验给我最重要的答案:
双卡没有让本地模型突然变成另一种级别的产品,但它确实让一个已经能够完成工作的模型,变得更适合持续工作。
RX 9070也完成了它作为“测试卡”的任务。
它证明了这张B850主板可以实现PCIe 5.0 x8/x8和双向P2P,证明了ROCm上的TP与RCCL能够在消费级平台上获得实际收益,也暴露了DFlash2在TP分片权重路径中的兼容问题。更重要的是,它让我在真正购买第二张R9700之前,先用相对有限的成本看清了多卡能够带来什么,又需要付出什么。
如果未来升级到双R9700,两张卡拥有相同的显存容量和计算规模,切分与同步应该会比现在更加自然。
至于这篇文章本身,如果后来有人在相似的平台上尝试AMD双卡,能够因为这些记录少走一点弯路,那么这次折腾就已经超过了它最初的意义。