工具大全
AI 教程作者:Coocon2026年9月3日610 次阅读约 7 分钟阅读

同一台 24G Mac mini、同一个 DFlash 2:MLX 上加速 1.9x,llama.cpp 上减速 50% 还 OOM

同一台 24G Mac mini、同一个 DFlash 2:MLX 上加速 1.9x,llama.cpp 上减速 50% 还 OOM

问题背景

这是这台 24G Mac mini M4 上投机解码对照的第三篇。前情两篇:

  • DFlash 2 实测:MLX 栈外挂 2B 草稿模型,6.5 → 11.7–12.2 tok/s,稳定 1.8–1.9x;
  • Qwen3.8 原生 MTP 实测:llama.cpp 栈挂模型自带的 MTP 头,结果净减速(散文 -24%),根因是 llama-batched-bench 实测出的 Metal 批量解码摊薄只有 1.13x。

MTP 那篇留了个自然的问题:llama.cpp 在 PR #27342 里也合并了 DFlash 2 支持(--spec-type draft-dflash),z-lab 官方还发布了配套的 GGUF 草稿模型。同一个 DFlash 2 算法,换到 llama.cpp 栈上跑,是复刻 MLX 的 1.9x,还是重蹈 MTP 的净减速?

这个问题值得测,因为两个假设只能活一个:如果 MTP 输在"草稿质量",那 DFlash 2 这个更强的草稿器(官方 CUDA 评测接受长度 5.1–5.4)应该能赢;如果输在"Metal 验证批量不摊薄"这个后端税,那换什么草稿器都没用。

先说结论:后端税赢了,而且比预想的更狠。官方 README 推荐的 --spec-draft-n-max 7 在这台机器上直接 Metal OOM(可复现);能稳定跑完的 n-max 3 散文从 6.0 掉到 3.0 tok/s(-50%);代码提示词接受率高达 83.8%、平均一步接住 3.5 个 token——依然亏 23%。同一台机器上 MLX 栈是 1.8–1.9x。

问题分析

三篇对照下来,这台机器上的投机解码格局是一张 2×2 的表:

MLX 栈 llama.cpp 栈(Metal)
DFlash 2 外挂草稿 1.8–1.9x(上上篇) 本篇:0.5x,官方推荐配置 OOM
原生 MTP 头 无现成挂载路径 0.76x(上篇)

上篇已经量化了 llama.cpp Metal 的病根:批量验证不摊薄。llama-batched-bench 实测 batch 8 聚合吞吐 6.90 tok/s,对比单流 6.10,摊薄比只有 1.13x(CUDA 同场景是 3.34x)。投机解码的收益公式里,"验证 n 个草稿 ≈ 验证 1 个 token"这个近似在这个后端上不成立。

所以动手前的预判是:DFlash 2 的草稿质量再好,也要过验证这道税关。但实测出来的账比"验证税"还多一层——草稿本身在这条栈上也贵得离谱,后面用数字算。

技术方案与选型

目标模型:继续用 unsloth UD-Q4_K_S(15.36GB),和上篇 MTP 实测完全同一个文件、同一套 server 参数(-c 8192 -ngl 999 -fa on -b 512 -ub 512)。这样昨天的 baseline 直接可比,今天实测 baseline 也确实精确复现了(6.00 vs 6.00 tok/s)。

草稿模型:z-lab 官方 GGUF 三个量化里选 Q4_K_M(1.14GB)——一是对齐 MLX 实测用的 --draft-bits 4,二是官方自己的评测里 Q4_K_M 接受长度(5.39)反而比 BF16(5.28)还高,三是上上篇复测已经证明 8-bit 草稿在这台机器上不如 4-bit。排除 Q8_0(2.06GB)和 BF16(3.86GB):24G 机器上主模型加草稿已经贴着 Metal 工作集上限(后面 OOM 一节就是证据),没有理由用更大的。

对照臂:baseline / n-max 7(官方 README 推荐值)/ n-max 3(llama.cpp 默认值)/ n-max 12(探上限)。方法继续用净速度法:greedy(temperature 0)、固定 400 token、读 timings.predicted_per_second,散文和代码两种提示词各两轮。

挂载方式:本地文件用 -md 显式指定;如果用 -hf 从 HuggingFace 拉主模型,llama.cpp 新版能按 dflash- 前缀自动发现并下载 sidecar(common/download.cpp 里的 find_best_dflash),和 mtp-、eagle3- 是同一套机制。

# 草稿模型 1.14GB
curl -L -o ~/llm/mtp/models/Qwen3.8-27B-DFlash2-Q4_K_M.gguf \
  "https://huggingface.co/z-lab/Qwen3.8-27B-DFlash2-GGUF/resolve/main/Qwen3.8-27B-DFlash2-Q4_K_M.gguf"

# 官方 README 的推荐姿势(剧透:24G 机器上会 OOM)
./build/bin/llama-server -m Qwen3.8-27B-UD-Q4_K_S.gguf \
  -md Qwen3.8-27B-DFlash2-Q4_K_M.gguf \
  --spec-type draft-dflash --spec-draft-n-max 7

实测过程

benchmark 脚本沿用上篇的脚手架(含全部踩坑修正:curl -o 直写响应文件、pkill -INT 匹配进程名、/health 检查 body),四臂顺序执行,每臂独立起停 server、/usr/bin/time -l 记录峰值内存:

四臂对照:baseline 6.0 tok/s 复现;dflash7 与 dflash12 全部请求 500;dflash3 散文 2.98/3.01、代码 4.30/4.62

n-max 7 和 12 两臂的所有请求返回 {"error":{"code":500,"message":"Compute error."}}。翻 server stderr,是 Metal 显存分配失败:

server-dflash7 stderr:kIOGPUCommandBufferCallbackErrorOutOfMemory 连环报错,backend 进入 error state

加载阶段日志里其实已有预警:ggml_metal_log_allocated_size: warning: current allocated size is greater than the recommended max working set size——15.36GB 主模型 + 1.14GB 草稿 = 16.5GB 权重,已经越过 24GB 机器的 Metal 推荐工作集上限,n-max 越大验证批越宽、计算缓冲越多,7 和 12 直接压垮。

OOM 是不是环境问题? 首轮跑的时候系统只剩 314MB 空闲内存,有理由怀疑是环境压力。于是在空闲内存回升到 4.5GB 后做了受控重试:

  • n-max 7 + -c 8192:依然全部 OOM——这是配置固有的,不是环境背锅;
  • n-max 7 + -c 4096(砍半上下文换空间):第一轮跑通了(散文 1.73、代码 3.32 tok/s),第二轮又 OOM——贴着上限跑,Metal 堆撑不过连续请求。

重试实测:8192 上下文可复现 OOM;4096 上下文第一轮通过、第二轮再爆;下方为 dflash3 的接受率日志

实践效果

汇总所有跑通的配置(400 token greedy,净生成速度):

配置 散文 代码 接受率(散文/代码) 结果
baseline(无投机) 6.00 / 6.00 6.01 / 5.97 — 基准
dflash n-max 3 2.98 / 3.01(-50%) 4.30 / 4.62(-23~28%) 43.8% / 83.8% 净减速
dflash n-max 7(c=4096,仅存活 1 轮) 1.73(-71%) 3.32(-45%) 21.6% / 55.6% 净减速 + 随后 OOM
dflash n-max 7 / 12(c=8192) — — — Metal OOM,可复现

对照 MLX 栈同机数字:基线 6.43–6.51,DFlash 2 block-size 5 是 10.8–12.2 tok/s(1.66–1.88x),block-size 3 是 11.6(1.79x)。同一个算法、同一台机器、同一天气候,两条栈一个 1.8x、一个 0.5x。

一笔算死这条栈的账

代码提示词的接受率是 83.8%、平均一步接住 3.5 个 token(server 日志 mean len = 3.50)——按 MLX 的经验这足够 1.5x 以上了,为什么还亏 23%?把总时间除以验证步数:

  • 散文:133.9 秒 / 174 个投机步(400 − 226 个接受)= 每步 0.77 秒
  • 代码:86.3 秒 / 115 个投机步(400 − 285 个接受)= 每步 0.75 秒

每个投机步(草稿块生成 + 批量验证一次)稳定花 0.75–0.77 秒,而 baseline 单 token 只要 0.167 秒——一步的成本等于约 4.6 个普通 token,而 n-max 3 一步的收益上限是 4 个 token(3 个草稿全中 + 1 个验证 token)。也就是说:接受率就算 100%,这条栈也是亏的(0.77 ÷ 4 = 0.19 秒/token > 0.167)。n-max 7 同理:每步实测 1.44 秒,成本 8.6 个 token,收益上限 8 个,照样死。

这 0.77 秒里有两笔:验证批不摊薄(上篇量化过的 1.13x)+ 草稿块生成本身的开销(一个 1.1GB 的 block-diffusion 模型每步全量前向加候选路径选择,在 Metal 上没有做过任何优化)。两笔的精确拆分需要 profiler,但结论不需要:成本 ≥ 收益上限,是结构性死局,调参救不了。

顺带两个复现细节:

  • greedy 一致性:代码输出与 baseline 字节级一致;散文在第 1294 字符处分叉——和上篇 mtp4 分叉的位置一模一样。这坐实了它是"不同 Metal kernel 路径在 logits 近平局处的浮点分歧"这个系统性现象,与投机算法无关。
  • 官方数字没有说谎:z-lab README 的接受长度 5.1–5.4 是 CUDA 上 GSM8K + 高推理档的评测,README 通篇没有对 Metal 做任何速度承诺。是"合并了支持"和"在你的后端上跑得快"之间的差距,需要自己实测来填。

结论怎么用

  • 24G Mac 上跑 Qwen3.8 要投机加速:用 MLX 栈的 DFlash 2(block-size 3–5),这是三篇实测下来唯一正收益的路线;
  • llama.cpp 栈在 Metal 上目前不要开任何投机解码(draft-dflash 和 draft-mtp 都实测净亏),裸跑 6.0 tok/s 就是它的最优解;
  • 这个结论是 Metal 后端专属的:CUDA/ROCm 用户两条路线都有社区实测的正收益,别拿本文劝退;
  • 若未来 llama.cpp 的 Metal 批量解码摊薄改善(batched-bench 的 batch-8 比值明显超过 1.13x),本文结论需要重测——这是个可以量化跟踪的信号。

踩坑

  1. 官方 README 的推荐配置在 24G 机器上直接 OOM。--spec-draft-n-max 7 + 默认上下文在 16.5GB 权重的组合下越过 Metal 工作集上限,全部请求 500。加载日志里的 allocated size is greater than the recommended max working set size 警告不是废话,看到它就该预期高 n-max 会炸。
  2. **-c 4096 只能续命一轮。**砍上下文换来的空间撑过了第一对请求,第二轮又 OOM——贴着显存上限的配置在连续服务下不可用,别拿"跑通过一次"当"能用"。
  3. **内存受压时 /usr/bin/time -l 的峰值 RSS 不可比。**本次 baseline 显示峰值 10.9GB,而昨天同配置清爽环境下是 15.4GB——差的 4.5GB 是被系统驱逐的 mmap 权重页。要对比内存足迹,先清场再测,或者直接看 Metal 分配日志。
  4. **script -a(追加模式)在无 TTY 的后台环境下报 tcgetattr/ioctl: Operation not supported on socket。**新建会话文件的 script -q file cmd 可以在后台跑,追加模式不行;后台补测的输出改用 tee 收集。

三部曲到此闭环:这台机器上投机解码的胜负从来不在草稿质量(43.8% 和 83.8% 的接受率结局相同),在后端每个投机步的固定成本。下一个值得跟的信号有两个:llama.cpp Metal 批量解码的摊薄改善,和 MLX 侧原生 MTP 挂载路径的出现——任何一个落地,这张 2×2 表就要重画。

相关文章

LM Studio 速度慢怎么办:Mac 实测首字等 39 秒的真正原因是 prompt 预填充,不是 GPU 没开满

在 24GB M4 Mac mini 上用 LM Studio 跑 gemma-4-e4b(Q4_K_M)实测:生成速度约 29.5 tok/s,GPU offload 从全开调到全关只慢 11%;真正让人觉得慢的是长 prompt 的预填充——1.3 万 token 的 prompt 要等 39.4 秒才出第一个字(约 330 token/s),全 CPU 时还要再慢 3.4~3.7 倍。相同前缀的第二次请求首字只要 0.1 秒(前缀缓存)。--parallel 4 时同时来 4 个请求,每个只剩 9.7 tok/s。

mac-mini本地大模型+5
pitfalls2026年10月5日7 min
389

Ollama 显存不足怎么办:24GB Mac 实测 num_ctx、并发、KV 量化各吃多少内存,以及它不会拦你的那个坑

24GB 的 M4 Mac mini,Ollama 0.19 只认到 17.8 GiB 显存,默认上下文只给 4096。实测 qwen3:4b:上下文从 4k 开到 32k,占用从 3.73GB 涨到 9.89GB;OLLAMA_NUM_PARALLEL=4 时 8k 上下文占用等于单路 32k,但 ollama ps 只显示 8192;开 Flash Attention + q4_0 KV 量化后 32k 只要 4.31GB、64k 只要 5.77GB,速度不变。上下文开到远超内存时 Ollama 不会提前拒绝,而是直接开始分配,16 秒内系统可用内存掉到 23%。全部挪到 CPU 只慢 25%。

mac-mini本地大模型+5
pitfalls2026年10月5日7 min
392
llama.cpp 部署 llama-server 还是用 ollama?Mac mini 24GB 同一个 GGUF 实测:ollama 底下跑的就是 llama-server

llama.cpp 部署 llama-server 还是用 ollama?Mac mini 24GB 同一个 GGUF 实测:ollama 底下跑的就是 llama-server

在 24GB Mac mini 上让 llama-server(b11376)和 ollama(0.35.1)加载同一个 GGUF 文件对照:ollama 0.35 的推理进程就是它自带的 llama-server,同参数下速度一样(4B 解码 36.5 vs 36.3 tok/s,27B 都是 6.3)。差别全在默认参数:ollama 默认上下文 4096,官方库模型的超长 prompt 会被静默截到 2050 token,日志里只有一行 WARN;llama-server 默认按显存把上下文开到最大(4B 开到 101120,footprint 14GB 以上),超长时明确返回 400;并发上 llama-server 默认 4 槽并行,ollama 默认排队;ollama 设 32k 加 4 并发会按每路 32k 分配,footprint 约 21GB。文末给出 24GB 机器的推荐启动参数。

qwenmac-mini+6
hands-on2026年10月3日11 min
571

24GB Mac mini 能跑多大的本地大模型?内存账、实测速度与提速方案汇总

一台 24GB 统一内存的 Mac mini 到底能跑什么模型?答案是:27B 的 4-bit 量化就是天花板,且我们真的把 Qwen3.8-27B 在 M4 丐版上完整跑通了——峰值内存 19.4GB,投机解码后 11.7-12.2 tok/s。本文汇总这台机器上的全部实测:各参数量级的内存账、速度预期、三条提速路线的真实效果(DFlash 2 / 原生 MTP / MLX vs llama.cpp),以及量化档位怎么选。

qwendflash+6
ai-tutorials2026年9月4日5 min
1225