# 双 RX 7900 XTX 跑 FlashNext,ESXi 直通、近 256K 同机实测和空闲功耗
-
双 RX 7900 XTX 跑 FlashNext,ESXi 直通、近 256K 同机实测和空闲功耗
搭这台机器的时候,我想的是把长文、代码和图片都放在本地跑。比如一整份材料发进去,最后问的可能是开头提过的一件小事。我想让模型读完以后,还能把那个细节找出来。材料一长,上下文很快就到了二十多万 token。平时我也想让服务一直开着,想用就直接发请求。
两张 7900 XTX 就是拿来分担同一个请求的。每张卡有 24 GiB,FlashNext 的 Q5 权重比两张卡加起来还大,长上下文的缓存也要占显存。我的做法是让两张卡分层跑,常用的专家权重尽量留在显存里,其余主要放内存。后来翻日志,Q5 那轮光是驻留在系统内存里的专家权重就有约 78 GiB。虚拟机里约 108 GiB 的内存,这里实实在在用上了。
两张卡是怎么接的
主机跑 ESXi,两张卡都直通给同一台 Ubuntu 虚拟机。启动后我先确认 ROCm 能认到两张卡,再去看引擎日志,确认两边各跑 24 层,也都分到了专家权重。
项目 这台机器 CPU / 虚拟机 AMD EPYC 7K62,客机分配 50 vCPU, MemTotal108.06 GiB显卡 2 × RX 7900 XTX, gfx1100,各 24 GiB,ESXi PCIe 直通系统 / 推理栈 Ubuntu 24.04,ROCm 7.2.1,Strata 引擎 0.1.56,HIP gfx1100构建引擎版本 源码 ffa80d73133ea5c548aefe57889a16e8085a938f,构建开启STRATA_MMQ_KQUANTS双卡用法 gpu=[0,1],layer_split="24",parallel=1,STRATA_PEER_TRANSPORT=host客机里看 PCIe,两个设备都报
5.0 GT/s ×32。我把这项当客机枚举信息记录。物理槽位速率、主机侧 NUMA 和卡间带宽,要用宿主机上的测量回答。这轮比较固定的是两张卡分层和host传输。
图 1 两张 7900 XTX 直通给同一台虚拟机,分担一个推理请求。
后来做 Q5 对照的时候,我从加载模型开始就记显存,长请求和 102 道题也算在内。用原专家表那轮,两张卡采到的最低剩余显存分别是约 1,363 MiB 和 1,078 MiB。两张都是 24 GiB,跑起来剩的余量也有差别。我后来就分开记两张卡,看哪张先吃紧。
最早也试过 Q6,能跑完当时的 17 项,近 256K 长文的首字中位数却到了 438 秒。七分多钟才开始回,日常用实在等不住,我就接着试 Q5。早期的测试参数后来有调整,这个数就当是当时的折腾记录。下面的 Q4 和 Q5 是换成同一批请求以后重新测的。
请求都跑完了,怎么还有 200 多瓦?
先贴两张 11 月 4 日的监控原图。第一张看着像一半负载,后来再看,GPU 那个圈已经红到 100% 了。显存两张都显示 95%,功耗却从 138 W 跳到了 224 W。

图 2 监控面板显示 GPU 50%,双卡功耗 138 W。

图 3 同一天的另一张截图,GPU 100%,双卡功耗 224 W。
我先去看是不是还有请求在跑。队列已经空了,输出 token 计数也没再动,可两张卡还是都报 100%,连续 9 次采样的合计功耗在 220–233 W。面板上的 GPU 百分比是两张卡的平均值。之前看到 50% 的时候,拆开查就有过一张 0%、另一张 100% 的情况。停掉推理服务做对照,两个忙碌读数都降到了 0%。
后来改 HIP 硬件队列上限逐档试。原来的
GPU_MAX_HW_QUEUES=8,Q5 载入等请求时双卡功耗中位数 227 W,停服务、模型卸载后是 23 W。六队列和五队列在载入后都出现过一张卡报 100%,双卡合计 99 W。四队列跑完短请求看着正常,等 102 题跑完,队列清空后又出现单卡 100%,合计 109.5 W。三队列那轮,我在同一个进程里跑完 102 题和 17 项功能、性能检查,分别测到 23 W 和 25 W,两卡忙碌读数都是 0%。把
GPU_MAX_HW_QUEUES=3用到正式服务,做完 API 验证后模型还在显存里,双卡合计 19.5 W。这里的瓦数来自显卡遥测,按两张卡相加。
图 4 每行是对应状态下 6 次采样的中位数,功耗按两张显卡合计。
四队列那次最容易看走眼,刚启动的十几瓦只说明当时那一小段正常。后面我用三个窗口分别看短请求、102 题、整套长文和图片跑完后的状态。这个结果是我这台 ESXi 直通机配 ROCm 7.2.1 的实测,具体是哪条队列卡住,目前只查到现象跟推理进程的存续一起变化。
Q4 和 Q5 各要等多久
版本 权重来源 主 GGUF 合计 Q4 cygnal/Qwen3.8-Flash-Next-Heretic2-IQ4XS-NGQ4-GGUF,revisionb7a350f3ba4c29962cefe9e69af225f142cda9bc98,403,355,776 B,约 91.65 GiB Q5 orcarouter/Qwen3.8-Flash-Next-Uncensored-GGUF的Q5_K_M,revision98eb3357afd966c25cadd082c7f830eafde7aa52134,106,668,160 B,约 124.90 GiB,三片 体积只算主 GGUF。两边实际使用各自的权重、pack、tokenizer 路径和视觉投影,推理引擎、环境变量和采样对齐。Q4 名字里有 IQ4XS,实际 GGUF 的专家张量混用了
IQ4_XS、IQ4_NL和Q5_1,内置 PLE 表为Q4_0。这是两套可用权重的同机对照,分数差包括了各自的权重版本。两边都用 INT8 KV、
--kv-resident 32768、MTP4、双窗口、--expert-cache auto和--vram-reserve-mib 1536。262,144 是上下文上限,32,768 是配置的显存 KV 驻留窗口。双窗口是引擎的流水线设置,服务并发仍是 1。每边先跑同一条预热请求,再按 128K 长文一条、近 256K 长文三条、近 256K 代码三条的顺序跑。两边八条实际请求的 payload SHA 都逐条一致。测速请求关闭 thinking,
temperature=0.2、top_p=0.95、top_k=20、seed=42。正式计时的七条每条生成 256 token,实际cache_n=0。PP 和 TG 取引擎的prompt_per_second和predicted_per_second,TTFT 取客户端从发请求到第一个可见字符的时间。128K 实际输入 131,072 token,近 256K 实际输入 261,632 token,近 256K 两组各取三次中位数。请求 读入 Q4 / Q5(tok/s) 生成 Q4 / Q5(tok/s) 首字 Q4 / Q5(秒) 128K 长文 2210.4 / 1447.5 53.8 / 36.2 59.53 / 90.82 近 256K 长文 2537.5 / 1848.5 63.2 / 45.6 103.45 / 141.84 近 256K 代码 2568.6 / 1917.1 65.7 / 53.3 102.17 / 136.78 近 256K 长文三次的生成区间是 Q4 60.6–68.3、Q5 42.7–49.0 tok/s。长请求里埋了要找回的细节,这几轮两边都找到了。Q5 的长文 TTFT 中位数约 142 秒,后面 256 个 token 的生成阶段约 6 秒。拿它查一份长材料里的某个细节,前面这段等候才是我每天能感觉到的差别。
日志里能看到的差别
两套都用
--expert-cache auto,实际装进 GPU 的专家数差得不少。我把这轮七条长请求的日志对齐后,单独摘了几项。日志字段 Heretic2 Q4 OrcaRouter Q5 GPU 常驻专家槽位,两卡合计 13,459 9,379 RAM 中的专家补集 52.53 GiB 78.18 GiB 128K 长文 GPU 专家命中率,单次 88.5% 73.8% 128K 长文 decode 文件等待,单次 34.5 ms 1,443.0 ms 近 256K 长文 GPU 专家命中率,三次中位 95.4% 89.0% 近 256K 长文 KV 显存命中率,三次中位 96.26% 96.15% 近 256K 长文 decode 文件等待,三次中位 1.1 ms 97.8 ms MTP 草稿接受/提议,七条合计 876 / 1,260(69.5%) 850 / 1,296(65.6%) 这轮 Q4 的专家权重整体更小,自动分配多塞进了 4,080 个 GPU 槽位。近 256K 长文的 KV 显存命中都在 96% 左右,专家命中率拉开的差距更大,MTP 接受率也有变化。要把那几十秒的首字差距拆到专家搬运、PLE、内核和其他开销,还得另做逐项对照。上面的文件等待是 decode 日志里的字段,和整条请求耗时分开看。
跑得更快,为什么我还是留了 Q5
同一套冻结 102 题,Q4 答对 69 题,Q5 答对 78 题。同题集、同评分器、同采样,两边请求失败和 GPU 视觉协议失败都是 0。按类别看是下面这组。
题类 Q4 Q5 中文 16/20 18/20 推理 8/20 10/20 代码 10/20 11/20 金融 19/24 21/24 普通读图 10/12 12/12 文档读图 6/6 6/6 我翻了几道原始答案。有一道图表题,Q4 连最大项的数值都读对了,可题目要的是类别名,它把数字填了进去。还有一道代码题,题目要单参数的函数,Q4 生成了两个参数,照原来的函数签名调用就对不上。也有像有效税率这种算错的。两份权重版本本身有差异,我就按这两个版本在这套题上的结果选。
中间还试过给 Q5 重排专家,把预跑时更常用的那批提前放进显存。我的想法很简单,常用的先放进去,看看能不能少等几次从内存拿数据。两组的显存预留相同,两张卡的专家槽位分别是 4,616 和 4,763,只换初始排序。
这轮还踩了个测速坑。请求里已经写了
cache_prompt=false,但第一次重发同一份 128K 提示,日志显示还是复用了 131,065 个 token,实际只重新读了 7 个。那条结果作废。重跑时在隔离测速请求加strata_checkpoint=false,实测每条cache_n=0。两边都先预热引擎,再完整读入长提示。同一次 Q5 对照里,预热后 128K 生成从 45.7 掉到 38.8 tok/s,近 256K 长文从 48.0 掉到 41.7 tok/s。近 256K 代码那条冷测生成从 36.4 升到 40.0 tok/s,首字从 163.92 秒拉到 230.45 秒,预热后生成也从 51.1 降到 50.0 tok/s。102 题从 78 到 77,唯一变化是
code-histogram由对变错。我测完就回到原专家表了。现在机器就常驻 Q5。长材料发一次要等多久,我心里大致有数。对我自己的用法来说,多等这几十秒还能接受。答完以后两张卡能降下来,服务还能继续挂着,这台机子才算顺手。
跑分时用的参数
两套对照共用的 Strata
args如下。模型、投影、pack 和词表路径按安装位置填。GPU 列表、layer_split和parallel在服务配置里设置,值见上面的硬件表。--pack "$PACK_DIR" --native "$MODEL_GGUF" --ple-gguf "$PLE_GGUF" \ --expert-profile "$EXPERT_PROFILE" --expert-cache auto --resident-experts \ --prefill auto --pool-workers 24 --max-context 262144 \ --kv int8 --kv-resident 32768 --ple-io direct --ple-row-cache 1048576 \ --spec 4 --spec-min-p 0.65 --mtp "$MTP_DIR" --mtp-q4 all \ --mtp-window 4096 --mtp-draft-vocab "$DRAFT_VOCAB" --pipeline-windows 2 \ --pcie-frac 0 --vram-reserve-mib 1536 --adapt-every 24 \ --prompt-cache 8 --prompt-cache-every 8192 \ --conversation-cache-slots 2 --conversation-cache-mib 8192 \ --conversation-cache-min-tokens 4096 --stats --vision两边共用的引擎环境变量也贴在这里。调优表的文件名保留,路径按本机填写。
export STRATA_RDNA_MMVQ_WAVE=3 export STRATA_PEER_TRANSPORT=host export STRATA_HIP_WMMA=1 export STRATA_SELECT_WMMA=1 export STRATA_PREFILL_CPU_SHARE=auto export STRATA_HC_Q8=0 export STRATA_PREFILL_AUTO_MAX=32768 export STRATA_PREFILL_RING=384 export STRATA_ROUTE_TAIL_SKIP=0 export STRATA_HEAD_MIX_MULTI=1 export STRATA_ONE_TOKEN_COMMIT=0 export STRATA_AUX_CPUS=off export STRATA_GR_RDNA=1 export STRATA_GR_RDNA_Q8=1 export STRATA_RDNA_Q6K=1 export STRATA_GR_RDNA_A8=1 export GPU_MAX_HW_QUEUES=3 export STRATA_QFUSE=1 export STRATA_COMBINE_CPU=1 export STRATA_VERIFY_QDEDUP=1 export STRATA_WAIT_FUSE=1 export STRATA_ROUTE_PUBLISH=1 export STRATA_GR_INJ_UP=1 export STRATA_GR_FUSED=1 export STRATA_GDN_HEAD=1 export STRATA_GDN_CHUNKED=1 export STRATA_GDN_CHUNK_GDV=0 export STRATA_PA_KLDS=1 export STRATA_GDN_PP=1 export STRATA_GDN_CONVL2=1 export STRATA_GDN_NOY=1 export STRATA_PF_GEMM=1 export STRATA_PF_PAD=1 export STRATA_HC_UPMIX=1 export STRATA_PF_HCDOWN=1 export STRATA_FUSE_CONVAB=1 export STRATA_PA_FAST=1 export STRATA_PF_GROUP_RES=1 export STRATA_KV_HOST_DMA=1 export STRATA_DP_PREFILL=0 export STRATA_RESIDENT_PIN=0 export STRATA_RESIDENT_HEADROOM_GIB=12 export STRATA_HIPBLASLT_TUNING="$HIPBLASLT_TABLE" # gfx1100-hipblaslt-100202.txt export STRATA_MTP_STAGE0=0 export HSA_USERPTR_FOR_PAGED_MEM=0 export STRATA_PF_FUSED=1 export STRATA_PF_FUSED_KQ=1 export STRATA_PF_F16=1