针对M5 Max优化的Splash分支:速度提升约1.5倍(单请求1.25倍)

Reddit r/LocalLLaMA 工具

摘要

Splish是Splash的一个非官方分支,专为Apple M5 Max芯片优化Metal内核,能够在不降低质量的情况下,将AI推理速度提升高达1.5倍,适用于Qwen3.8-27B等模型。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/09/27 07:42

publicExcess/splish 源码:https://github.com/publicExcess/splish

Splish

许可证: Apache-2.0 发布版 (https://github.com/publicExcess/splish/releases/tag/splish-v1.0) Ko-fi (https://ko-fi.com/severalviolins)

速度比原版 Splash 最高快 1.5 倍,单请求约快 1.25 倍,且质量未变:在 40 核 M5 Max 上运行 Qwen3.8-27B 系列 4 位模型。Splish 是 Inco 公司的 Splash (https://github.com/incoai/splash) 的非官方分支,与 Inco 无关联。它为苹果 M5 系列 GPU 重新调整并扩展了 Splash 的 Metal 内核,并在 40 核 M5 Max(128 GB,MacBook Pro)、macOS 27.0、Xcode 27.0(Metal 工具链 27A266a)上开发和测试。

为何创建分支。 Splish 的大部分改动都针对单一芯片和操作系统(macOS 27 上的 40 核 M5 Max)进行了调优。在相同芯片上使用 macOS 26.6 进行的独立运行已经对一些内核的排名产生了影响。与其要求 Inco 审查和维护可能无法移植到其他 Mac 上的硬件特定工作,并分散对引擎整体的注意力,不如让它在这里独立存在。硬件无关的部分(从文件加载内核选择、复制规则、基准测试工具)如果 Inco 需要,可向其上游提供。

对比原版 Splash 1.1.0,在同一台 Mac 上使用相同模型。每个单元格是解码 tok/s,原版 → Splish(增益);在 2–4 个请求时,是所有请求的总和。模型均为 4 位仿射格式(MLX 风格,分组 64,Splash 的包格式),除非标记为 GGUF,并使用 Splash 默认的 int8 KV 缓存和每个模型的 DFlash2 草稿。方法参见与原版 Splash 对比。

工作负载1 个请求2 个请求3 个请求4 个请求
Inco 的 Qwen3.8-27B,长推理131 → 178 (+35%)223 → 292 (+31%)222 → 330 (+48%)299 → 392 (+31%)
Inco 的 Qwen3.8-27B,短回答,贪心78 → 99 (+26%)134 → 164 (+23%)138 → 171 (+24%)183 → 210 (+15%)
Inco 的 Qwen3.8-27B,短回答,采样74 → 91 (+23%)118 → 151 (+28%)125 → 171 (+37%)163 → 209 (+28%)
Inco 的 Qwen3.8-27B,TensorFold 客户端:代码142 → 176 (+24%)
Inco 的 Qwen3.8-27B,TensorFold 客户端:聊天75 → 92 (+22%)
Inco 的 Qwen3.8-27B,2K–128K 令牌文档(按上下文长度)+11% 到 +29%96 → 118 (+24%, 64K)
Swift-1.5(Qwen3.8-27B 的微调版),长推理141 → 179 (+27%)224 → 296 (+32%)224 → 342 (+52%)288 → 400 (+39%)
Qwen3.6-35B-A3B,长推理331 → 348 (+5%)486 → 573 (+18%)553 → 672 (+22%)642 → 754 (+18%)

此外,复制规则加速了整个文件的代码编辑(Swift-1.5,Splish 不使用 → 使用):144 → 180 tok/s (+24%) 和 136 → 194 tok/s (+42%)。质量未变:上述所有模型在使用 Splish 时,在我们 95 个任务的测试集上得分均为 95/95,与我们测量过的原版版本一致。

按上下文长度

单个请求,总结给定长度的文档(一篇不同的 WikiText 段落),输出 2,048 个令牌。提示在解码计时开始前已预填充到缓存中。解码是 2 轮的平均值;预填充是冷启动的第一轮。Inco 的 Qwen3.8-27B,tok/s,原版 → Splish:

上下文2K8K32K64K128K
解码92 → 109 (+18%)74 → 95 (+29%)72 → 80 (+11%)66 → 77 (+16%)54 → 63 (+16%)
预填充1,019 → 923829 → 895851 → 864794 → 803701 → 713

在每个长度下解码都更快。预填充速度未变:Splish 不涉及预填充内核,这些单次冷启动测量的差异在 −9% 到 +8% 之间。Splash 自身的基准工具也测量了 2K 和 32K 时的首个令牌时间。

按上下文长度解码 按上下文长度预填充

此分支在 Splash 1.1.0 基础上新增了:

  • 针对 M5 张量单元的新型验证内核。 Split-K 内核一次性计算每个投影的行和(SplitSums32),加上更轻量的屏障。它们在 2–4 个请求时带来了增益。
  • 针对该芯片测量的内核选择(tuning/ 目录),从文件(SPLASH_KERNEL_CHOICES)加载。它们带来了大部分单请求增益。原版 Splash 无法使用它们:其选择是编译时确定的,其调优器是开发者构建目标,不包含在 Homebrew 包中。
  • 面向编码代理的复制规则(来自 TensorFold (https://github.com/ashhart/TensorFold))。当最后 16 个或更多令牌重复早期上下文时,下一个草稿是逐字的延续。输出是精确的:贪心文本是字节相同的,采样接受使用单热草稿分布。
  • 更快的 GGUF 解码(G4a 分阶段内核),输出位相同:每个内核 +3–14%,每步约 2%。
  • 确保数据真实性的工具。 包括一个与 fp64 对照的内核基准测试、一个带置信区间的整步基准测试、一个位对位构建比较,以及一个带功耗读取的稳态服务基准测试。

未提速的地方。 长上下文注意力本身未改变;64K 时的领先来自其周围的所有优化。Qwen3.6-35B 单请求仅增益 5%,GGUF 模型约 2%。在 3–4 个请求时每令牌能耗更低,但在 1–2 个请求时情况不一。原版领先的场景列出了所有案例。

其他一切都是 Inco 的:引擎、包格式、模型和草稿模型,以及服务器。此分支有意识地跟踪上游发布(目前为 Splash 1.1.0;upstream 远程仓库)。

快速开始

你需要一台运行 macOS 26.4+、Xcode 26 或更新版本以及 Python 3.12–3.14 的 Apple Silicon Mac。

git clone https://github.com/publicExcess/splish.git && cd splish
make -j4
./splish serve --model mlx-community/Qwen3.8-27B-4bit

首次 serve 会下载模型及其 DFlash2 草稿并准备权重,与 Splash 相同。./splish 会为模型选择调优后的内核选项并启用复制规则;它会打印所选内容。然后连接一个代理(./splish opencode、claude、codex、hermes、pi)或任何兼容 OpenAI 或 Anthropic 的客户端,与 Splash 类似(上游 README)。

调优后的选项是针对 macOS 27 上的 40 核 M5 Max 的。在 macOS 26.6 上另一台 40 核 M5 Max 上进行的独立调优运行为某些形状选择了不同的最优解;增益方向相同但幅度减半。这就是为什么选择应该针对每台机器进行调优。在任何其他 Mac 上,./splish 会保留 Splash 自身的默认设置,但仍会启用复制规则。其他 M5 芯片需要自己的选择;自动调优器正在开发中。

高级选项。 ./splish 仅在以下变量未设置时应用:

变量效果
SPLASH_KERNEL_CHOICES=FILE要加载的内核选择(tuning/);未设置则使用 Splash 默认值
SPLASH_CHOICES=any在非 40 核 M5 Max 芯片上应用调优后的选择
SPLASH_M5_COPY_MIN_MATCH=N复制规则:当 N+ 个重复令牌时,草稿为逐字延续(默认 16;0 则关闭)
SPLASH_M5_ACCEPT_LOG、SPLASH_M5_TOKEN_LOG诊断:接受直方图和每步令牌

推荐设置

模型选择文件备注
Qwen3.8-27B 系列,Splash 包(Inco 的、Swift-1.5、其他微调版)tuning/m5max-40c-swift15-v8.choices在 40 核 M5 Max 上调优。其他 M5 芯片:运行调优器(dev/tuning)。
Qwen3.8-27B GGUF Q8_0tuning/m5max-40c-swift15-q80.choices约 2%
Qwen3.6-35B-A3Btuning/m5max-40c-qwen36-35b.choices1–4 个请求时 +5/+18/+22/+18%
Qwen3.8-27B GGUF Q4_K_M, Q6_Ktuning/m5max-40c-swift15-kquant.choices约 2%(G4a 内核起主要作用)

复制规则: 通过 ./splish 默认开启(SPLASH_M5_COPY_MIN_MATCH=16)。它在重写文件的代理上有效,在散文上从不触发,即使误触发最多也只增加约 3% 的开销。草稿长度:保持 Splash 的 7 个草稿令牌。在长推理负载下,50–62% 的验证步骤接受全部 7 个(在 Qwen3.6-35B 上为 45–48%)。将草稿截断为 5 只能保留每步约 81% 的令牌,这比缩短验证节省的更多。温度:在默认风扇行为下,持续 3–4 个请求的负载在两款引擎上 GPU 峰值温度达到 92–94 °C。对于长时间的代理会话,请设置风扇曲线,使其在 80–85 °C 时达到全速。

与原版 Splash 对比

方法。

  • 构建版本。 原版是原生的 Splash 1.1.0。分支是此仓库,使用了 v8 选择。两者在使用相同包的同一台 M5 Max 上,通过不同端口并行运行。
  • 服务负载。 C 个并发请求(C = 1–4),每个从长推理提示生成最多 4,096 个令牌,使用推荐的采样设置(温度 1.0,top_p 0.95,top_k 20)。
  • 指标。 聚合解码 tok/s,仅在所有 C 个请求解码时计数(dev/m5/serve_bench.py)。包功耗和 GPU 温度在同一时间窗口内通过 macmon 获取。
  • 重复次数。 每个值是两轮的平均值,原版和分支交替进行。运行差异最高可达约 7%(单个单元格最高 8%),因此 5% 以下的差异视为持平。
  • Splash 自身的基准工具。 dev/benchmarks/http_regression.py(ABBA 顺序,abba.py 的通过规则)测量了 2K 和 32K 上下文时的每令牌解码毫秒数和首个令牌时间。
  • 质量。 在两个构建版本上使用包含 95 个任务(数学、代码、推理、精确匹配评分)的测试集。

哪个构建版本。 长推理、质量和基准工具数据(2026-09-26 夜间运行)使用的是当前构建版本,包含 G4a 和 A4。512 令牌行和 Swift 的步进时间早于 G4a 和 A4。这两项更改都是位相同的,且仅影响 GGUF 模型和长上下文。

Splish 领先的地方

Inco 的 Qwen3.8-27B 包(incoai/Qwen3.8-27B-Splash),聚合 tok/s,分支与原版对比:

负载C=1C=2C=3C=4
贪心,512 令牌98.8 +26%163.9 +23%170.8 +24%209.7 +15%
采样,512 令牌90.5 +23%151.3 +28%171.2 +37%208.6 +28%
64K 令牌文档,总结,稳态73.4 +12%118.2 +24%
长推理,稳态,采样177.7 +35%292.0 +31%329.5 +48%391.8 +31%
每令牌能耗,J(原版 → 分支)0.65 → 0.360.24 → 0.300.25 → 0.220.21 → 0.19

单个请求,按模型 1-4 个并发请求 Inco 的 Qwen3.8-27B,原版 vs Splish,贪心 Inco 的 Qwen3.8-27B,原版 vs Splish,采样

原版参考值:贪心 78.2 / 133.7 / 137.8 / 182.6 tok/s,采样 73.8 / 118.2 / 124.9 / 163.4,长推理 131.3 / 223.2 / 222.2 / 299.0。长推理数学的草稿效果很好(每个验证步骤约 6.2 个令牌),这就是为什么它的 tok/s 比短回答更高。

Swift-1.5 在同一长推理负载下:140.5 / 224.2 / 224.1 / 287.7 → 179.0 / 295.8 / 341.5 / 400.2 tok/s (+27% / +32% / +52% / +39%)。4 个请求时能耗为 0.32 → 0.18 J 每令牌。

TensorFold 的客户端(tools/bench_openai.py 来自 ashhart/TensorFold (https://github.com/ashhart/TensorFold),通过 dev/m5/tensorfold_bench.py;64 个令牌回复,5 个种子,2 轮)。代码和聊天,采样与贪心,tok/s:

代码,采样聊天,采样代码,贪心聊天,贪心
原版 Splash 1.1.0156.677.8142.175.1
Splish202.688.6176.191.9
TensorFold 0.3.4,其为 M5 Max 发布的版本168.469.3154.773.5

TensorFold 行是其自身发布的测量值:不同的检查点和草稿器、一个原始补全的代码提示和不同的会话。将其视为参考点,而非竞赛。

Splash 自身的基准工具(http_regression.py,ABBA,Inco 的通过规则)在 Inco 的 27B 上:解码 5.93 → 4.60 ms 每令牌 (−22%),通过。在 32K 时首个令牌时间相同(39.5 s 对比 39.4 s)。在 2K 时为 +0.7%,但该运行的分散度为 6.9%,超过了规则的 5% 限制,因此规则判定为不确定而非回归。

Swift-1.5(Qwen3.8-27B 的微调版,相同形状),使用 2,048 令牌提示时,来自 Splash 的 decode_profile 的解码步进时间:

请求数原版 1.0.21.0.2 + 调优选择Splish (v8)
149.0 ms41.5 ms40.5 ms
259.1 ms59.4 ms44.9 ms
387.2 ms88.2 ms59.3 ms
487.0 ms85.8 ms62.9 ms

在单个请求时,分支比仅使用调优选择增益约 2.5%。来自分支自身内核的增益在 2–4 个请求时(1.3–1.5 倍)。

质量。 Swift-1.5 在两个引擎上都得了 95/95,四个副本并发运行时得分 380/380。在 Inco 的 27B 上:原版 95/95,分支 95/95。分支的贪心文本与原版在接近平局时不同,因为 Split-K 内核的加法顺序不同。两个引擎在运行间都是确定性的。

原版领先或持平的地方

场景结果
接受投机,Inco 的 27B分支 0.380 vs 原版 0.398(贪心,16 个提示)。略低;速度增益足以弥补。
长上下文解码(40K+)持平。注意力设定了限制,而唯一保留的更改(A4)约占注意力的 3%,每步不明显。
GGUF Q8_0 解码,2 个请求持平(−0.1%)。1、3 和 4 个请求:快约 2%。
Qwen3.6-35B-A3B(调优)未经调优。分支的注意力更改因其形状而被禁用,在该形状下慢了 3%。
首个令牌时间,2K / 32K持平(+0.7% / −0.2%)。2K 运行噪声太大,未通过 Inco 的规则。
Qwen3.6-35B-A3B,长推理,1–4 个请求持平:−2% / −1% / −4% / +2%,在其 5–7% 的运行分散度内。

有效的方法

  1. Split-K 验证内核,使用 SplitSums32 一次性计算行和,在 MPP 张量单元上运行。残差、普通和门控/上投影在 16–32 行时增益,在 2–4 个请求时将步进时间缩短了 23–32%。

  2. 更轻量的屏障 (H1)。 当一个瓦片只有一个 simdgroup 时,使用 simdgroup 屏障替代线程组屏障。

  3. 针对每个形状测量选择(v8),从文件加载。

  4. GGUF 输入预取 (G4a)。 分阶段的 GGUF 瓦片现在将输入行提前一步预取到从其权重阶段释放的空间中。它使用相同的线程组内存,产生位相同的输出,在 32 行时快 2–8%。

    GGUF Q8_0 在 32 行时

  5. 6 头组使用 4 个 simdgroup 的注意力 QK (A4)。 位相同;注意力快 2–3%。

  6. 使用相同内核调优第二个模型。 Splash 的 tune-kernels 在 Qwen3.6-35B-A3B 上为其稠密投影选择了 31 个 SplitSums32 选择。这些选择在其形状上经过了 fp64 和竞态检查。在 1–4 个请求时服务增益为 +5/+18/+22/+18%。

  7. 复制规则(来自 TensorFold)。在构建之前进行了测量:重放真实对话记录(SPLASH_M5_TOKEN_LOG、dev/m5/copy_rule.py)预测在两次整个文件编辑中增益 +26% / +42%;实际运行时为 +24% / +42%。第一个版本拒绝了所有复制。引擎首先发出每步的锚点令牌,因此复制提前了一个令牌;一个调试日志(SPLASH_M5_COPY_DEBUG)发现了这个问题。

    Swift-1.5,贪心,提示已缓存关闭开启
    整个文件编辑(类型提示),tok/s144.4179.7
    整个文件编辑(文档字符串),tok/s136.3194.2
    短重命名,tok/s197.8196.4
    散文 / 推理−0.4 到 −0.7%
    接受全部 7 个草稿的步数28%45%

一种相关且互补的方法: harryslimes 的上下文复制参考 (https://github.com/harryslimes/ninfer-fast/pull/1) 适用于 NInfer 和 Pi 代理的基准工具层。模型被指示发出简短的复制命令(``)而不是重新输入文本,基准工具展开它们。这在大规模重写时节省更多,但它改变了模型的输出格式并需要基准工具扩展。Splish 的复制规则保持模型的精确输出,并且不需要

相似文章

适用于 Apple Metal 的 Automatic1111,sd1.5 速度提升 40%

Hacker News Top

本文介绍了一个专为 Apple Silicon 优化的 Automatic1111 分支,加入了 Metal 优化(例如 Metal Flash Attention),以加速 Stable Diffusion 1.5 的生成速度,在 M3 Pro 上将时间从 8-10 秒缩短至 3-7 秒,在 M1 Mac Mini 上从 13-20 秒缩短至 8-10 秒。