让 Neural Engine 干活:视觉塔为什么不该跑在 GPU 上

2026-09-01

English · 中文

每台 Apple Silicon Mac 里都有两个加速器:GPU,和一颗 Neural Engine(ANE, M5 Pro 上 16 核、约 42 TOPS int8)。跑本地大模型时,几乎所有框架的选择是 一样的:所有东西都在 GPU 上,ANE 从头到尾闲着。

对纯文本模型,这个选择没什么可争的 —— LLM 解码是自回归的、访存主导、需要 灵活的 KV cache,不是 ANE 的形状。但视觉语言模型不同:它多出来的那块 计算 —— 视觉塔(ViT)—— 恰好是 ANE 最擅长的东西。

视觉塔是一次性的静态前向

一张图进来,patchify 之后过 27 层 transformer,输出一串视觉 embedding, 结束。没有自回归、没有 KV cache、没有逐步增长的序列 —— 就是一个形状固定的 大前向。这正是 Core ML 编译器能吃透、ANE 能全速跑的负载。

数字说话。ViT-B/16 规模的塔,M5 Pro 实测:

时延能量
GPU (MPS)15.9 ms612 mJ
ANE18.3 ms75 mJ

时延几乎相同,每张图的能量差 8 倍。ANE 跑视觉塔约 2W,GPU 干同样的事 约 8W。在插电的台式机上这是温度和风扇;在 MacBook 上这是电池。

但真正的收益不是省电,是并行

省电是单张图的账。serving 场景的账不一样:视觉塔在 ANE 上跑的时候, GPU 一毫秒都不用停。

Tempo9 的流水线是这样的:请求进来,聊天模板和 tokenizer 在 CPU 上跑,图像 在 ANE 上过塔 —— 而这整段时间,GPU 在解码别的请求。等视觉 embedding 就绪,它才进入引擎的连续批处理,和在飞的解码共享同一个 forward。

一个 agent 循环反复看同一张截图、多个会话各带各的图、视频逐帧进来 —— 这些 负载里,视觉预处理和语言解码是天然可重叠的两条流。用同一个 GPU 排队, 重叠就没了;分到两个处理器上,重叠是白拿的。

还有第三笔账,24GB 的机器上最要紧:塔的权重不占 GPU 的 wired 显存预算。 35B 模型贴着上限跑的时候,几百 MB 的塔权重放在哪一边,决定的是能不能再多 放一个并发的 KV。

动态分辨率怎么塞进静态编译器

麻烦在于:Qwen 系的塔是 NaViT 式动态分辨率 —— patch 数随输入图变,而 Core ML 要的是编译时定死的形状。

我们的解法是分桶:按 patch 预算导出固定尺寸的模型,输入图等比缩放到 最近的桶,不足的 pad 掉、用 additive mask 屏蔽。发布策略是「一大一小」:

像素预算典型适配对应 LLM token
b102426 万 px683×384 / 512×512≤ 256
b4096105 万 px1365×768 / 1024×1024≤ 1024

为什么只有两个桶、不是一个梯子?每个桶是一个独立编译的模型,磁盘上 各占几百 MB —— 桶越多,下载越肥,而中间档位的收益递减。小桶伺候缩略图和 agent 截图流,大桶伺候文档和照片,够了。

换处理器,不能换答案

把计算从 GPU 挪到 ANE,最大的风险不是慢,是悄悄变错:Core ML 默认 fp16,数值路径完全不同,而视觉塔的输出会直接决定模型"看到"什么。

所以每个导出的塔要过三道门才能发布:算子驻留检查(确认真的在 ANE 上跑, 而不是静默回退 CPU)、逐桶和 fp32 参考比对(当前全部桶 cosine ≥ 0.98, 实测 min = 1.000000)、端到端 VQA 冒烟。任何一道不过,这个塔就不发布 —— 一个更快但看错图的塔,不是优化,是缺陷。

收尾

芯片上的硅是已经付过钱的。同一台 Mac,视觉走 ANE、语言走 GPU、调度走 CPU —— 三块硅同时在干活,这才对得起 Apple Silicon 这个名字。