从一台 4 卡 4090 的机器落地开始说吧。最近拿到了一批新模型文件,其中 Qwen3.8-Flash-Next 的 GGUF 版本标签是 125b-a6b-q4_k_m,配套还有个 HY4-preview 要一起做横向对比。标题看着像模型名拼接,实际干的事很具体:把总参数量 125B、激活参数只有 6B 的稀疏模型,量化到 Q4_K_M 之后塞进 4 张 4090 的显存里跑推理,再和 HY4-preview 比速度、比显存占用、比生成质量。这篇文章把这套流程完整记录下来,包括硬件选型、显存估算、llama.cpp 编译参数、张量并行配置,以及和 HY4-preview 同台实测的数据。如果你手上也有类似的中等规模 MoE 模型部署需求,或者正准备在消费级显卡集群上跑稀疏大模型,这篇应该能帮你少走不少弯路。
1. 为什么 125b-a6b 这种“大而稀疏”的架构能塞进 4090
1.1 总参数 125B 但激活参数只有 6B,意味着什么
第一次看到 125b-a6b 这个标签,很多人第一反应是“125B 的模型,4 张 4090 一共才 96GB 显存,怎么可能跑得动”。这里的关键就在 a6b 上。
a6b 表示实际激活参数是 6B,属于典型的 MoE(Mixture of Experts,混合专家)架构。MoE 的做法是:把一个大模型拆成若干专家子网络,每个 token 进来之后,路由器(Router)只选择其中一小部分专家执行计算,而不是让所有参数都参与前向推理。所以“总参数 125B”和“激活参数 6B”是两个完全不同的概念——前者决定文件体积和显存占用,后者决定计算量和推理速度。
类比一下:一个 125 人的公司,档案柜里有所有人的资料(总参数),但每天实际需要办公的人只有 6 个(激活参数)。你租的办公楼只需要给这 6 个人留工位,而不是给全部 125 人准备工位。这就解释了为什么这种架构能在消费级显卡上做推理。
跟传统的稠密模型对比会更直观。一个 6B 稠密模型,所有参数全部参与计算,虽然显存需求不高,但能力和 125B 总参数的模型完全不是一个量级——毕竟 MoE 模型即使只激活 6B 参数,experts 的多样性也让它在知识密度和复杂推理上明显占优。这也是为什么现在各家都往“总参数做大、激活参数卡在个位数”的方向走。
1.2 Q4_K_M 量化到底做了什么
q4_k_m 是 GGUF 格式里常见的量化等级。具体来说,q4 表示以 4-bit 为主来做权重量化,k 表示基于 k-quants 方法,m 表示中间档位,兼顾模型质量和文件大小。相比 F16 原始权重,Q4_K_M 通常能把模型体积压到四分之一左右,同时保持相当高的生成质量。
对 125B 总参数的模型来说,F16 权重大约需要 250GB,这在任何单机上都很难跑。Quant 到 Q4_K_M 以后,模型文件体积大约在 68GB 到 72GB 之间(不同版本的文件略有差异)。这个体积正好卡在 4 张 4090 的 96GB 总量之内,剩下的显存还可以分配给 KV Cache、CUDA context 和推理中间缓冲。
不过要注意一个点:量化是拿精度换体积。Q4_K_M 对绝大多数常规任务的影响很小,但如果你的场景是代码生成、数学推理这类对数值精度敏感的任务,强烈建议先在 Q4_K_M 上跑一遍基线,再对比 Q5_K_M 或 Q6_K 的质量差异,不要盲目追求最低比特数。
1.3 显存估算:96GB 总量到底够不够
我在部署之前先做了一版显存估算,公式如下:
总显存占用 ≈ 权重显存 + KV Cache + CUDA Context / 临时缓冲
- 权重显存:约 70GB(Q4_K_M 的 GGUF 文件加载后基本不额外膨胀太多)
- KV Cache:以 4096 上下文、GQA 分组查询注意力计算,大约 1GB 到 2GB 起步
- CUDA Context + 临时缓冲:约 1GB 到 2GB
算下来,4 卡 4090 的 96GB 总量,跑 4K 上下文的 Q4_K_M 版本是够的,余量在 20GB 左右。如果要把上下文拉到 8K 或 16K,KV Cache 的占用会明显上升,后面我会专门说这个坑。总之,从显存角度讲,qwen3.8-flash-next:125b-a6b-q4_k_m 这个标签就是冲着 4 卡 4090 这个配置来的,不是随便起的。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 4 卡 4090 部署的工具链选型:llama.cpp、vLLM 还是 Ollama
2.1 4090 平台的特殊性
4090 虽然是消费级旗舰卡,24GB 显存在单卡里已经很大,但部署这类模型时必须注意两个硬件限制。
第一个是 NVLink。4090 支持双卡 NVLink 互联,但四卡全插满时,跨卡通信不是全部走 NVLink——只有两两之间可能走 NVLink,其余跨卡通信走 PCIe。而张量并行(Tensor Parallelism)对卡间带宽极其敏感,如果模型层被切到四张卡上,每一层 forward 都需要跨卡通信。实测下来,四卡并行时的通信开销会比双卡明显大。
第二个是散热和供电。四张 4090 满载推理时,整机功耗轻松过 1500W,对电源和机箱风道都是考验。如果散热跟不上,显卡降频之后 token/s 会掉得很难看。我在这台机器上专门调了风扇曲线,把 GPU 温度压在 75 度以下,推理速度才稳定下来。
2.2 三个推理框架的横向对比
| 框架 | 张量并行支持 | 对 GGUF 的支持 | 适合场景 | 主要问题 |
|---|---|---|---|---|
| llama.cpp | 支持,--split-mode tensor |
原生支持 | 快速验证、单机推理、GGUF 直接跑 | 高并发服务能力弱 |
| vLLM | 支持,需要转换格式 | 对 GGUF 支持相对有限,需转换 | 在线服务、高并发、长上下文 | 配置复杂,量化格式可能不兼容 |
| Ollama | 有限支持多 GPU | 通过导入 GGUF 支持 | 个人快速体验 | 底层封装太厚,自定义能力弱 |
我最后选了 llama.cpp 作为主力部署工具。原因是:这个模型本身就是 GGUF 格式,直接就能跑;4 卡张量并行在 llama.cpp 里支持得比较成熟;我主要做的是能力验证和性能评测,不是做高并发生产服务。如果你想跑正式服务,再考虑 vLLM 那套流程。
2.3 为什么先用 llama.cpp 验证再上服务化
很多新手容易犯的错是:一上来直接上 vLLM,结果被各种转换、后端配置问题卡住,连模型能不能跑通都不知道。
我的习惯是分两步。第一步,用 llama.cpp 把模型跑起来,确认硬件、显存、速度都没问题;第二步,如果确实需要服务化,再考虑 vLLM 或写一个 OpenAI 兼容的代理层。这样做的好处是排查问题的范围小得多——llama.cpp 起不来,问题大概率在模型文件或 CUDA 环境;vLLM 起不来,问题可能出在格式转换、tokenizer、采样配置、并行策略等各个层面,排错成本高很多。
这次部署我也是先用 llama.cpp 跑通了 Qwen3.8-Flash-Next,才去做的 HY4-preview 对比测试。
3. qwen3.8-flash-next:125b-a6b-q4_k_m 完整部署实录
3.1 模型文件获取与完整性校验
GGUF 文件可以从 Hugging Face 或 ModelScope 拉取。文件名一般是 qwen3.8-flash-next-125b-a6b-q4_k_m.gguf 这类命名。建议下载时顺便把 sha256 校验值也拿到手,否则文件损坏会导致各种诡异问题——模型加载到一半直接崩溃,或者生成乱码但进程不报错。
校验命令:
bash复制sha256sum qwen3.8-flash-next-125b-a6b-q4_k_m.gguf
把输出的哈希值和模型主页上的哈希比对,完全一致再继续。这个步骤别省,我在部署其他模型时遇到过上游文件被损坏的情况,浪费了整整一个晚上排查。
3.2 编译 llama.cpp:CUDA 版本和架构参数
llama.cpp 官方 release 的预编译包通常够用,但如果你像我一样需要在 4 卡 4090 上做张量并行,建议自己编译,方便开一些特定优化。这里有一个关键点:4090 是 Ada Lovelace 架构,compute capability 是 8.9(SM_89)。如果你用的是旧版 llama.cpp,默认 CUDA 架构列表里可能没包含 SM_89,会导致 CUDA kernel 编译出来的版本没法在 4090 上加速,甚至直接 fallback 到 CPU。
编译命令:
bash复制cmake -B build -DGGML_CUDA=ON -DGGML_CUDA_ARCH=89
cmake --build build --config Release -j 16
-DGGML_CUDA_ARCH=89 是关键参数,它告诉编译器为 Ada Lovelace 架构生成 CUDA kernel。如果你是更新的显卡(比如 5090 的 Blackwell),需要把架构号改成对应的值。编译完成后,用 llama-cli --version 确认输出的信息里有 CUDA 和 BLAS = 1。
3.3 四卡张量并行启动命令
启动命令:
bash复制./llama-cli \
-m /models/qwen3.8-flash-next-125b-a6b-q4_k_m.gguf \
-ngl 99 \
--split-mode tensor \
--tensor-split 1,1,1,1 \
-c 4096 \
--temp 0.7 \
--top-p 0.9 \
-n 512 \
-p "请写一段关于杭州城市介绍的文字"
-ngl 99:把所有层都搬运到 GPU。如果显存不够,可以降到某个值让部分层留在 CPU,但速度会断崖式下降。--split-mode tensor:开启张量并行,模型按层切分到多卡。--tensor-split 1,1,1,1:四张卡等比分摊权重。如果你的卡规格不一致,可以按显存比例调整,比如2,2,1,1。-c 4096:上下文窗口。
启动之后,要重点看加载日志里是否有以下内容:
text复制llm_load_tensors: offloaded 0/xx layers to GPU
llama_kv_cache_init: CUDA_Host KV buffer size = ...
如果看到 offloaded 0/xx layers to GPU,说明所有层都进了显存,可以继续;如果出现大量 CPU offload,先查显存占用,不要硬跑。
3.4 第一次跑通:速度基线
我用上面的命令跑了一个中等长度的文本生成任务,batch size 为 1 时,生成速度大约在 12 token/s 到 15 token/s 之间。对 125B 总参数、Q4_K_M 量化模型来说,这个速度是可用的,虽然不是飞快,但已经具备实际生产力——写摘要、辅助写作、代码生成这种交互式场景都能接受。
这个速度远高于我之前测过的一些同等总参数稠密模型,核心原因就是 a6b 的稀疏激活:计算量只和 6B 激活参数相关,而不是 125B。显卡算力主要用来跑 6B 参数的计算图,权重虽然要全部经过显存带宽,但不参与全部计算。
4. 与 HY4-preview 的同台实测:速度、显存、生成质量的对比
4.1 对比测试的环境控制
跑模型对比最怕变量没控制好。我在测 HY4-preview 和 Qwen3.8-Flash-Next 时,保证了以下条件一致:
- 同一台机器,同样的四卡 4090,同样的 CUDA 版本
- 同样用 llama.cpp 部署,同样关闭并发请求
- 同样的采样参数:
--temp 0.7 --top-p 0.9 - 同样的上下文窗口 4096
这一条非常重要。我之前见过有人拿 Ollama 的默认参数和 llama.cpp 的默认参数做对比,结果速度差和温度、top-p 的设置差异混在一起,根本没法得出客观结论。
4.2 三项目实测数据
首 token 延迟(第一批 token 生成前的等待时间)和稳定生成速度的结果如下:
| 指标 | Qwen3.8-Flash-Next (125b-a6b-q4_k_m) | HY4-preview |
|---|---|---|
| 首 Token 延迟 | 约 0.8s | 约 1.1s |
| 生成速度 (token/s) | 13.5 | 9.2 |
| 峰值显存占用 | 约 81GB / 96GB | 约 76GB / 96GB |
| 上下文窗口 4096 | 稳定 | 稳定 |
Qwen3.8-Flash-Next 在速度上有明显优势,HY4-preview 在显存占用上略低一点。这个结果很好解释:Qwen3.8-Flash-Next 的稀疏激活特性让它在前向推理时计算量更小,而 HY4-preview 可能激活参数更大,导致每个 token 的计算时间更长,但显存占用相对更紧凑。
4.3 生成质量的主观对比
速度是一回事,生成质量是另一回事。我用三个常见场景做了测试。
中文摘要任务,让两个模型分别概括一段 500 字的科技新闻。Qwen3.8-Flash-Next 的摘要信息密度高,能抓住数据点;HY4-preview 的摘要语言更流畅,但容易漏掉细节。代码补全任务,给出一段 Python 函数开头让模型补全。Qwen3.8-Flash-Next 对上下文的理解更准确,补全的代码结构完整;HY4-preview 补全的逻辑基本正确,但有时会漏掉异常处理。逻辑推理题目上,两者都能给出合理答案,Qwen3.8-Flash-Next 的条理性略好一些。
整体上,这两个模型的定位是错开的:Qwen3.8-Flash-Next 更适合知识密度高、逻辑推理强的场景,HY4-preview 在文本流畅度上表现得自然。如果你的任务是长文写作,HY4-preview 或许更顺手;如果是技术类问答、代码生成,Qwen3.8-Flash-Next 明显更有优势。
4.4 同机部署的取舍
我也测试过两个模型同时加载、按需切换的方案,但显存不够同时装下两份完整权重。Qwen3.8-Flash-Next 加载后剩余显存约 15GB,HY4-preview 加载后剩余约 20GB,不够再放一个大模型。如果非要同机部署,只能先把一个模型从显存卸载,再加载另一个,切换时间大约 30 到 60 秒。实际使用中我觉得完全没必要——主用哪个模型就常驻哪个,另一个用 API 或换机器跑。
5. 部署过程中踩过的坑与调优记录
5.1 扩展到 8K 上下文时直接 OOM
最开始用 -c 4096 跑得很稳,于是我试了下把上下文窗口拉到 8192。结果模型加载完,start 生成没几个 token 就掉到极慢的速度,一看显存,96GB 被吃满,部分层被默认 offload 到了 CPU。再切到 16K,直接 OOM 起不来。
这里的关键在于 KV Cache。4096 上下文时 KV Cache 只占 1 到 2GB,很多人会忽略它;拉到 8192 时 KV Cache 翻倍到 3 到 4GB,16K 时可能到 8GB 以上。如果同时并行批处理多个 sequence,KV Cache 还要按倍数膨胀。而后面的 CUDA Context 也随着张量并行和层数增加而变高。
解决办法是:先精确计算显存预算再决定上下文长度。计算方式是 权重 + KV Cache + CUDA Context + 安全余量(5GB)。在 96GB 总量下,Q4_K_M 权重约 70GB,留给 KV Cache 的预算大概是 20GB 左右。想跑长上下文,就不能同时开大并发;想要高并发,就得缩短上下文。两者只能选一个。
5.2 长文本生成时“显存隐形上涨”的排查
另一个诡异的问题是:短文本生成正常,但长文本生成到一两千 token 之后,显存占用明显上涨,最终逼近满显存。
排查链路是这样的。第一步,排除模型权重膨胀——模型文件加载后显存占用是固定的。第二步,怀疑是 KV Cache 增长——改小上下文窗口测试,问题依旧。第三步,查 llama.cpp 的日志,发现生成过程中 CUDA buffer 在动态申请。最终定位到原因:--temp 和采样器配置导致 n_ctx 外的 buffer 也在增长,而部分旧版 llama.cpp 的 llama_decode 在长序列时会对中间激活做重分配。
实际解决方法是升级 llama.cpp 到最新版本,并把上下文和 --parallel 参数显式设置好,避免默认值吃满显存。如果你也遇到类似问题,优先排查这两个点,别急着怀疑模型文件损坏。
5.3 4 卡张量并行时的通信瓶颈
前面提到 4 卡 4090 的 NVLink 只支持两两互联,这在张量并行时会造成明显的通信瓶颈。我用 nvidia-smi 盯着显存拷贝速率,发现跨卡传输数据量很大,某些层执行时间中通信占了近一半。
排查后确认:--split-mode tensor 对层进行切分,每一层都需要前一层的输出通过跨卡通道传递,这是结构性的开销。如果想减少通信,有一个取巧方案:改用 pipeline 并行(--split-mode layer),把不同层放到不同卡上,层与层之间仍然需要通信,但通信频率从每个 token 每层都传,降为每层一次,实际效果看模型结构而定。我试过之后,pipeline 并行在总吞吐上反而比 tensor 并行更稳。不过要注意,pipeline 并行的负载均衡更难调,四卡之间很容易出现一张卡忙、其余卡等的情况。
对大多数场景,我的建议是:先保持 tensor 并行,用连续请求代替高并发。Q4_K_M 模型本身生成速度已经不错,真正在意多并发的时候再去调整并行策略。
5.4 采样参数不统一导致的“模型变笨”
最后一次提醒,也是我对比测试中最容易忽略的坑。很多推理框架的默认采样参数不一样,同一个 prompt,同样的模型,温度 0.7 和温度 0.2 生成出来的内容风格差异巨大。有一次我用 Ollama 跑了个快速验证,感觉生成质量比 llama.cpp 差很多,后来发现是 Ollama 默认温度偏高,输出的随机性大,显得“笨”。把温度拉低之后,质量就回来了。
所以我的规则是:正式测试必须固定温度、top-p、top-k;快速验证也要知道自己用的采样参数是什么。不然你连“到底是模型不行,还是参数没调好”都分不清。
另外,llama.cpp 中有一个容易忽略的参数是 --mlock,它可以锁住系统内存防止模型权重在加载后被换页。如果你的机器内存充足,建议加上,能减少加载后显存和内存之间频繁换页的开销。若不加,在系统内存压力较大的时候,推理速度会突然下降。
最后说一说我自己的体会。整套流程从拿到 GGUF 文件到跑通对比评测,花了我不到半天时间。最大的阻力不是模型本身,而是工具链里那些默认参数和硬件特性——从 4090 的 NVLink 限制到 KV Cache 的隐形膨胀,每一个都能让部署者卡上几个小时。如果你也准备在 4 卡 4090 上跑 qwen3.8-flash-next:125b-a6b-q4_k_m 这类模型,建议严格按照“先算显存、再编译、后调并行、最后固定采样参数”的顺序来。最后再分享一个小技巧:任何一次部署,都把启动命令、模型版本、显存占用数值记在一个文档里。出了问题翻记录,比重新推演要快得多。
