实测机器:MacBook Pro,Apple M4 Pro,48GB 统一内存

模型:Qwen3.8-Flash-Next REAP-288 MLX 4-bit

运行方式:oMLX 0.6.4,PLE mmap/NVMe 流式读取

X上看到大佬在M4 Max上就能跑起:Qwen3.8-Flash-Next,这太有吸引力了

当时我就咨询了方案,方案全部上传后,今天才完成测试(HuggingFace下载速度实在是太慢了)

最终在我48GB 的 M4 Pro 上跑起了一个磁盘目录接近 70GB、服务报告完整体积 71.87GB 的模型:Qwen3.8-Flash-Next REAP-288 MLX 4-bit。首次加载约 10.14 秒,稳定解码大约 26~29 token/s,8K 输入也能完成推理。

这件事看起来不太符合直觉。48GB 统一内存里还要留空间给 macOS、Metal 和 KV Cache,一个 70GB 模型怎么可能装得进去?答案并不是 macOS 把几十 GB 权重悄悄塞进了 swap,也不是把模型降级成一个小模型。真正起作用的是三件事叠加:REAP 专家裁剪、4-bit 量化,以及把 51B 参数的 PLE n-gram 表留在 NVMe 上按需读取。

最后真正驻留的模型约 39.02GB,oMLX 估算总驻留约 40.57GB。模型能跑起来,但剩余空间也决定了它不可能在这台机器上兑现配置文件里的 262K 上下文。这篇记录完整部署过程,也把 prefill、decode 和实际上下文边界一次测清楚。

01|70GB 装进 48GB,不是数学算错了

先把几个容易混淆的数字拆开。这个模型下载到磁盘后约 70GB,oMLX 读取权重索引后报告完整模型体积为 71.87GB。如果按照普通方式让全部权重常驻统一内存,48GB Mac 肯定跑不起来。

但模型并不是一个均匀的 71.87GB 权重块。它由约 125B 参数的主模型和约 51B 参数的 PLE n-gram embedding table 组成。后者虽然很大,每生成一个 token 实际只会访问其中极少量的行,没有必要把整张表一直放在内存里。

oMLX 0.6.4 会读取模型仓库里的 ple-store.json 和 ple_storage 配置。当完整模型超过内存保护上限时,它把 PLE 表保持为外部存储,通过 mmap 从 NVMe 按行读取。最终日志给出的关键数字是:完整体积 71.87GB,实际模型驻留约 39.02GB,估算总驻留约 40.57GB。

所以这不是“70GB 压成了 39GB”,也不是通用的 SSD 专家卸载。主模型仍然驻留在统一内存中,只是那张 51B 参数、访问极稀疏的 n-gram 表没有全量进内存。

02|第一层缩减:512 个专家裁到 288 个

Qwen3.8 Flash Next 是 180B 级别的 MoE 架构,包含 48 个路由层。原始模型每层有 512 个专家,每个 token 路由到其中 10 个。这个版本使用 REAP 根据量化权重上的显著性,把每层保留的专家数从 512 裁到 288,但没有缩窄 top-10 路由宽度。

这一步把官方 Model Card 中的 Q4 基线从 98GB 降到了约 68GB。代价也写得很清楚:HumanEval pass@1 从 93.9% 下降到 91.5%。288 并不是随便挑的整数,而是作者在一组裁剪阶梯里找到的质量与体积折中点;继续裁到 256 个专家,HumanEval 会降到 88.4%,稀有名称的采样可靠性也明显恶化。

这里需要注意,专家裁剪不会让它变成一个“只有 288 个小模块”的轻量模型。每个 token 的确只激活 10 个专家,但被保留的专家权重仍然要存储和加载。它减少的是模型总权重,不等于消除了内存需求。

03|第二层缩减:主模型和 PLE 都做 4-bit 量化

这个仓库不是 BF16 模型。主模型使用 MLX affine 4-bit 量化,group size 为 64;PLE n-gram 分片同样是 4-bit,但 group size 为 32。若没有量化,即便做完 REAP 裁剪,48GB 机器也没有运行空间。

模型的关键维度如下:

REAP 解决的是“保留多少专家”,4-bit 解决的是“每份权重占多少空间”,PLE 外部存储解决的是“哪些权重必须常驻”。缺少其中任何一层,这个 48GB 部署都成立不了。

04|真正跨过 48GB 门槛的,是 PLE 外部存储

Model Card 对 39GB 模式的解释很直接:每个 token 只读取 51B n-gram 表中的几百字节,因此运行时可以不让整张表常驻内存。oMLX 从 0.6.4 开始支持这条路径,流式读取时的 logits 与全量驻留路径一致。

这里的“流式”容易让人误会。它不是每生成一个 token 就从 SSD 读取几十 GB,也不是把整个主模型逐层换入换出。真正从 NVMe 按需读取的是访问稀疏的 PLE 表,所以 SSD 延迟没有按模型总尺寸线性放大。代价依然存在:Model Card 没有单独公布 oMLX 流式模式的官方 decode 数据,并明确说明流式读取会损失一部分吞吐。

我在这台机器上的实测是 26~29 token/s。这个数字低于 Model Card 提到的某些 pmlx 峰值,但 pmlx 的 65 token/s 对应 PLE 全量驻留、短上下文峰值,不是 39GB 流式模式。把两种内存路径混在一起比较,会得出完全错误的结论。

05|下载模型并准备 oMLX 0.6.4

权重总计 150 个仓库文件,其中有 131 个 safetensors 分片。下载中断后可以继续,不需要从头再来,但是HuggingFace的下载速度是真让人崩溃:

1
2
3
4
5
mkdir -p ~/Develop/AI/Qwen3.8-Flash-Next-REAP-288-MLX-4bit

hf download sh0wie/Qwen3.8-Flash-Next-REAP-288-MLX-4bit \
--local-dir ~/Develop/AI/Qwen3.8-Flash-Next-REAP-288-MLX-4bit \
--max-workers 12

Model Card 要求 oMLX 0.6.4 或更新版本。注意一定要使用0.6.4这个版本

1
2
brew install jundot/omlx/omlx
omlx --version

06|48GB Mac 还要先处理 Metal 上限

第一次启动并没有成功。macOS 当时给 Metal 的默认 wired memory 上限只有 36GiB,而 oMLX 估算流式模式仍需要约 40.57GiB。服务端口虽然能起来,首次推理却会返回容量错误。

我临时把上限调整到 45,056MiB,也就是 44GiB:

1
2
sudo sysctl iogpu.wired_limit_mb=45056
sysctl iogpu.wired_limit_mb

这是系统级临时设置,重启后会恢复。需要手动恢复时可以执行:

1
sudo sysctl iogpu.wired_limit_mb=0

44GiB 不是让模型可以随意吃满 44GiB。oMLX 还会保留 5% 的 prefill 安全空间,因此实际 safety cap 约为 41.8GiB。系统只剩几 GB 余量时,不应该同时运行另一个大型模型,也不适合开一堆重内存应用。

07|启动 OpenAI 兼容服务

最终启动命令很短。–model-dir 指向包含模型目录的父目录,–memory-guard-gb 48 低于 68GB 全驻留需求,oMLX 会自动选择 PLE SSD offload:

1
2
3
4
5
cd ~/Develop/AI

omlx serve \
--model-dir ~/Develop/AI \
--memory-guard-gb 48

服务默认监听 http://127.0.0.1:8000/v1。先检查模型列表:

1
curl http://127.0.0.1:8000/v1/models

再发一个最短请求触发首次加载:

1
2
3
4
5
6
7
8
9
10
curl http://127.0.0.1:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "Qwen3.8-Flash-Next-REAP-288-MLX-4bit",
"messages": [
{"role": "user", "content": "Reply with exactly: READY"}
],
"temperature": 0,
"max_tokens": 16
}'

这台机器首次模型加载约 10.14 秒。日志确认 PLE mode 为 mmap,实际驻留约 39.02GB,说明真正走到了 Model Card 描述的 NVMe 流式路径,而不是在后台偷偷加载 68GB 全量权重。

08|完整实测:Prefill 最高约 149 token/s

我按照 128、512、1K、2K、4K、6K、7K、7.68K 和 8.19K 九个档位重新跑了一轮。每一档使用唯一 prompt,temperature=0,避免主动命中 prefix cache。

由于当时的 oMLX 会把流式内容聚合成一个 SSE 块,不能直接把首个网络数据包当成真实 TTFT。我改用 max_tokens=1 的请求测端到端 prefill/TTFT,再用同长度、不同前缀的长输出请求减去这段时间,估算纯 decode。它包含 HTTP、调度、聊天模板和首 token 采样开销,所以更接近日常 API 体感,不是只统计内核执行时间。

1K~2K 输入时 prefill 接近 148~149 token/s,4K~8K 则保持在 132~138 token/s。Decode 整体稳定在 26~29 token/s,6K 和 7K 的长输出复测仍有 25.6、26.5 token/s,没有因为上下文接近上限就突然掉到个位数。

09|配置写着 262K,本机实际只有 8,217 tokens

模型配置声明的最大上下文是 262,144 tokens,但那代表架构支持范围,不代表 48GB Mac 可以真的分配出相应 KV Cache 和 prefill 临时内存。模型本体已经占去约 39~41GB,留给长上下文的空间非常有限。

我在 8.2K 附近继续按 10 token 步进探测,结果如下:

保护器在 kv_len=8,224 的 prefill chunk 处触发,预测峰值约 45.29GB,超过 41.8GiB safety cap。也就是说,这套配置的实测最大成功输入是 8,217 tokens,不是 262K,也不应该把 8,217 直接写进生产配置。

我的日常建议值是 7,680 tokens,留出约 500 tokens 缓冲。对 Coding Agent 来说,这个容量能完成不少单文件和中等规模任务,但离超长仓库上下文仍有明显距离。如果目标是 32K 甚至更高上下文,优先考虑跑在64G Mac上面。

参考来源

  1. Qwen3.8-Flash-Next REAP-288 MLX 4-bit Model Card
  2. Qwen3.8-Flash-Next
  3. oMLX
  4. mlx-vlm