前段时间 Qwen 发布了 Qwen3.8-27B、Qwen3.8-flash-next 两个小模型,在 AA 的排行榜单上,甚至超过了了半年前的各家旗舰模型(比如 Opus 4.6、Kimi 2.6、GLM-5.2)

Artificial Analysis Intelligence Index (11 Oct '26)

难道本地小模型真王朝了?!不过很遗憾我手上只有一台 4080s 的显卡,12G 显存只能勉强跑一跑 Qwen3.8-27B 的量化版,而且这种稠密模型的推理速度实在是感人。正巧 9 月苹果最新的 Mac Studio 要发布了,我必须得下一单尝尝咸淡(对不起买不起更贵的 M5 Ultra 了😭)

29507d3f4de2f9c0cfb87bbc622dad4f

昨天到货后,马上迫不及待跑了一下 Qwen3.8-Flash-Next。这次我测试的两个目标是:

  1. **Qwen3.8-Flash-Next-REAP-288-MLX-4bit**:社区作者 sh0wie 提供的专家剪枝版本,每层保留 512 个专家中的 288 个,并使用 4-bit 量化。全套下载约 73.52 GB,主要优势是占用空间更小,下面简称 REAP。
  2. **Qwen3.8-Flash-Next-MLX-Serve-mixed-4-8bit**:社区作者 ddalcu 提供的混合量化版本,保留全部 512 个专家,专家权重主要使用 4-bit,Attention 等部分使用 8-bit。全套下载约 107.33 GB,配合 mlx-serve 运行,下面简称 mixed。

目前个人本地模型推理在成本上完全没有任何优势,如果不是为了学习研究,目前阶段不建议购买任何本地推理机器。

为什么我不买 DGX Spark?因为我认为 Mac Studio 的功耗更低,日常使用更舒服,且保值率更高,而且目前 DGX Spark 虽然有 CUDA 生态的优势,的统一内存带宽只有 273 GB/s,在 decode 时是一个短板,只能说未来提升空间很大,可以期待一下 DGX Spark 2代。

1. 模型部署

机器配置前面已经贴过了:18 核 CPU、40 核 GPU、128G 统一内存,系统是 macOS 27.0。两个模型对应的推理引擎如下:

模型 推理引擎
REAP mlx-vlm 0.7.6 / MLX 0.32.3
mixed mlx-serve 26.10.1

推理框架

这次优先选 MLX,是因为它针对 Apple Silicon 的 GPU 和统一内存做了专门优化,适合在 Mac 上跑量化大模型:

  • Metal 量化算子:为量化矩阵乘法、矩阵与向量乘法提供专用 GPU 实现,按输入形状选择计算路径,覆盖 Prefill 和逐 token Decode。Metal 实现
  • 统一内存:CPU、GPU 直接访问同一份数组,混合执行时减少数据复制和额外内存占用。统一内存设计
  • 计算图优化:引擎使用 mx.compile 时,可以合并重复计算、融合算子,减少中间结果的内存读写和执行开销。编译优化

这些优化直接服务于推理性能。再加上两个模型已有配套实现,量化权重、ngram 加载和 MTP 都可以沿用作者的方案;本文使用的 mlx-vlm、mlx-serve 负责具体的模型加载和推理服务。

Mac 上也有其他选择:

方案 特点 这次的取舍
MLX + 配套推理引擎 Apple Silicon 专用 Metal 算子、统一内存与计算图优化 优先使用原生优化,并沿用模型作者的配套实现
llama.cpp 支持 Metal 加速、GGUF 模型和多种量化精度,也支持 CPU/GPU 混合推理 换用它需要对应的 GGUF 权重,并核对这个模型的架构和 MTP 支持
PyTorch + MPS 通过 Metal 在 Apple GPU 上运行 PyTorch,适合沿用和修改 PyTorch 模型代码 量化算子和模型所需功能仍要逐项确认,不能把 CUDA 部署命令直接搬过来

llama.cpp 同样针对 Apple Silicon 做了 Metal 优化,PyTorch 也有 MPS 后端。具体谁更快取决于模型、量化格式和算子实现,本文没有做跨框架对照测速。

量化精度

按完整权重约 180B(含 ngram、MTP)估算,各档精度的体积如下:

权重精度 理论体积
BF16 / FP16 360 GB
8-bit 180 GB
6-bit 135 GB
4-bit 90 GB

以上为统一位数下的十进制 GB,未计量化元数据和运行开销;实际常驻内存还受 ngram 按需加载影响。模型组成

这次选 4-bit 为主的方案,给长上下文留出内存。mixed 的路由专家用 4-bit,Attention、共享专家和输出层等用 8-bit,路由器、归一化等保留 BF16;REAP 在 4-bit 量化之外进一步剪枝,换取更低的占用。mixed 精度配置 · REAP 剪枝说明

Ngram 表与内存映射

下载模型时会发现,里面还有一张很大的 ngram embedding 表。它根据相邻 token 的组合查出向量,mixed 版本将它单独放在 ngram_table.bin 中,光这一个文件就有约 32 GB。

如果把整张表都加载进内存,会挤掉不少留给上下文和其他应用的空间。这次两个模型都用了 mmap(内存映射):文件放在 SSD 上,用到哪部分就读取哪部分,读过的文件页由 macOS 缓存。这样就不用让整张表始终常驻内存。

配置 REAP mixed
ngram 加载方式 ple_storage,SSD mmap ngram_table.bin,SSD mmap
相关设置 cache_rows=0 关闭 ple-gpu,设置 MLX_SERVE_NGRAM_WARM=0
MLX 缓存上限 4 GiB 8 GiB

MLX_SERVE_NGRAM_WARM=0 是关闭整表后台预热,避免主动把整张表读进系统缓存。mmap 仍然会占用一部分内存,只是文件页可以由系统按需回收。

MTP 推测解码

这次还分别测了开启和关闭 MTP(Multi-Token Prediction,多 token 预测)的情况。它在这里用于推测解码:先由预测模块提出后面几个 token,再交给主模型验证。如果一次能接收多个 token,就有机会比逐个生成更快。

不过,预测和验证也有开销,所以开了不一定更快。REAP 还需要额外加载约 5.24 GB 的预测模型(drafter),mixed 的模型包里已经带了对应权重。后面我们直接看两种模式的速度差别。

整理文章时,mixed 作者已经推荐新的 iQ 版本,旧包停止更新。下面的数据对应这次实际测过的 mixed-4-8bit,新版还没有在本机复测。

2. 内存占用

先看内存。测速时活动监视器里 REAP 的 Python 进程大约占 49 GB,比下载下来的文件小不少。前面说的 ngram 按需加载就是原因之一,所以不能直接用模型文件大小来判断运行时占用。

下面是整个测速过程中,MLX 记录到的最高内存分配量:

方案 MLX 最高分配量
REAP 55.46 GB
REAP + MTP 60.68 GB
mixed 80.64 GB
mixed + MTP 84.30 GB

这轮始终只加载一个大模型、处理一个请求,两套方案都能跑完接近 256K 的输入。mixed 普通模式最高约 81 GB,开 MTP 后约 84 GB;REAP 则分别约 55 GB 和 61 GB,能给其他应用多留一些空间。

这些数值不包含整机所有占用,也不能和活动监视器里的进程内存直接相加。测试期间没有采样到新增的 Swapouts(内存换出)记录;如果还要同时开其他模型或应用,就要另外留出余量。

3. 推理性能

我平时会用到 200K 左右的上下文,所以这次没有只测几百个 token,而是从 512 一直测到了接近 256K,看看输入变长后会掉多少速度。

测试方法

输入分为八档:512、4,096、16,384、32,768、65,536、131,072、200,000 和 261,632 token。最后一档给 262,144 token 的窗口留了一部分输出空间,下面简称 256K 档。

中文、英文取固定的 Wikipedia 文本,代码取 CPython 3.12.0 标准库源码,按需要的长度截取。每份输入前、中、后各放一个校验码,让模型先找出来,再分析内容,顺便检查长输入里的信息能不能被读到。

两个模型各测普通模式和 MTP 模式,32K 及以下重复三次,64K 及以上重复两次,共 240 个独立测试项。每次最多输出 256 token,temperature=0,关闭思考和前缀缓存;输入按每批 1,024 token 处理,也就是 prefill chunk=1024。

这里先区分一下 Prefill 和 Decode:把一大段内容发给模型后,模型要先处理这些输入,这一步叫 Prefill;随后开始往外生成回答,叫 Decode。表里的 tok/s 就是每秒处理或生成的 token 数。

所以看长上下文性能,要同时看多久开始回答,以及开始后输出多快。只看 Decode,很容易忽略前面处理长文的等待时间。

测试结果

中文、英文和代码在不同输入长度下的 Prefill 与 Decode 速度

图中实线是普通模式,虚线是 MTP,误差棒表示各轮的最小值到最大值。下面列出全部八档结果,数值取同一档中文、英文、代码全部轮次的中位数。首 token 时间从发出请求开始计算,也包含分词等开销。

输入档位 方案 Prefill(tok/s) Decode(tok/s) 首 token 时间(秒)
512 REAP 1,141.3 43.5 0.5
512 REAP + MTP 1,177.3 42.9 0.4
512 mixed 1,580.5 66.8 0.3
512 mixed + MTP 1,557.5 94.3 0.3
4K REAP 1,285.6 39.3 3.2
4K REAP + MTP 1,308.5 46.1 3.1
4K mixed 2,183.5 62.4 1.9
4K mixed + MTP 2,175.9 95.2 1.9
16K REAP 1,262.7 38.1 13.0
16K REAP + MTP 1,268.2 43.1 12.9
16K mixed 2,144.0 61.3 7.6
16K mixed + MTP 2,147.3 91.1 7.6
32K REAP 1,241.2 35.5 26.4
32K REAP + MTP 1,221.9 41.5 26.9
32K mixed 2,131.0 61.3 15.4
32K mixed + MTP 2,045.1 83.8 16.0
64K REAP 1,096.9 31.0 60.3
64K REAP + MTP 1,091.2 30.7 60.6
64K mixed 1,827.6 59.8 36.6
64K mixed + MTP 2,060.7 92.7 31.8
128K REAP 1,086.9 25.1 120.8
128K REAP + MTP 1,083.8 24.4 121.2
128K mixed 1,922.8 58.4 68.5
128K mixed + MTP 1,969.2 79.0 66.6
200K REAP 1,047.5 20.9 191.2
200K REAP + MTP 1,044.9 19.0 191.6
200K mixed 1,855.5 56.3 108.0
200K mixed + MTP 1,892.6 74.8 105.9
256K REAP 970.8 17.8 269.8
256K REAP + MTP 995.1 15.1 263.2
256K mixed 1,856.5 56.0 141.1
256K mixed + MTP 1,823.3 74.5 143.9

长上下文下,mixed 的优势比较明显。普通模式在 200K 和 256K 两档都能保持约 56 tok/s,REAP 则降到了约 21 和 18 tok/s。mixed 处理输入也更快:200K 输入约等 108 秒开始回答,REAP 约等 191 秒。

不过,256K 输入即使用 mixed,也要等约 141 秒才开始输出。这次关掉了前缀缓存,每次都得重新处理整段输入;日常多轮对话如果能复用历史缓存,等待时间需要另外测,不能直接套这张表。

MTP 性能对比

mixed 在 200K 档从 56.3 提升到 74.8 tok/s,约快了 33%,256K 档也差不多。但首 token 时间基本没变,也就是说,它主要加快的是开始回答之后的输出速度。

REAP 的结果正好相反:两个长输入档位都变慢了,还多占了约 5 GB 内存。按这轮结果,mixed 可以开 MTP 试试,REAP 跑长上下文时就保持普通模式。

我还对比了相同输入下的首轮完整回答。REAP 开关 MTP 的 24 份回答全部逐字一致,mixed 只有 3 份一致。输出不同不等于质量下降,不过如果需要复现普通模式的回答,就不要在两种模式之间切换。

附:测试配置

硬件环境

项目 配置
机器 Mac Studio,Apple M5 Max
CPU / GPU 18 核 / 40 核
统一内存 128 GiB
系统 macOS 27.0

引擎配置

项目 REAP mixed
模型 sh0wie/Qwen3.8-Flash-Next-REAP-288-MLX-4bit ddalcu/Qwen3.8-Flash-Next-MLX-Serve-mixed-4-8bit
固定版本 668f31b 3191916
引擎版本 mlx-vlm 0.7.6 / MLX 0.32.3 mlx-serve 26.10.1
Ngram 加载 SSD mmap,ple_storage SSD mmap,ngram_table.bin
Ngram 设置 cache_rows=0 关闭 ple-gpu,MLX_SERVE_NGRAM_WARM=0
MLX 缓存上限 4 GiB 8 GiB
MTP 关闭、开启各测一轮完整档位 关闭、开启各测一轮完整档位

测试参数

项目 设置
同时加载模型 / 并发请求 1 / 1
目标输入长度(token) 512、4,096、16,384、32,768、65,536、131,072、200,000、261,632
上下文窗口 262,144 token
最大输出长度 256 token
Temperature / Seed 0 / 42
思考模式 / 前缀缓存 关闭 / 关闭
Prefill 分块 1,024 token
重复次数 32K 及以下每项 3 次,64K 及以上每项 2 次
独立测速项 240 项
服务重启 每个上下文批次重启
系统文件缓存 不强制清空
中文语料 Wikimedia Wikipedia 20231101.zh,前 100 条训练文本
英文语料 Wikimedia Wikipedia 20231101.en,前 100 条训练文本
代码语料 CPython v3.12.0,Lib/*.py 按路径排序,排除 test 目录