深入解析 M4 Apple Neural Engine(第 1 部分):逆向工程 我们如何绕过 CoreML,直接与硬件对话

深入解析 M4 Apple Neural Engine(第 1 部分):逆向工程 我们如何绕过 CoreML,直接与硬件对话

关于“我们”的说明:

在本系列中,“我们” 指的是 maderix(人类)和 Claude Opus 4.6(由 Anthropic 提供)作为一对搭档共同工作。逆向工程、基准测试和训练代码是协作开发的——人类直觉引导探索,AI 对数据进行推理并撰写分析。我们认为这种人机协作是一种进行系统研究的新颖且自然的方式:一方作为具有直觉的架构师 ,另一方作为编写代码并设计实验的工程师 。

这一切始于一个简单的问题: 能否在 Apple 的神经引擎上训练模型?

Apple 不想让你知道答案。他们不公开 ANE 的指令集架构(ISA)。他们不记录其内部架构。他们甚至不给你直接编程的方式——所有东西都要通过 CoreML,这增加了抽象层、优化通道和开销,使得几乎不可能理解硬件实际在做什么。

所以我们对它进行了逆向工程。

在几天时间里,我们将整个软件栈从 CoreML 向下映射到 IOKit 内核驱动,发现了如何在不借助 CoreML 的情况下编译并在 ANE 上执行程序,攻破了二进制格式,测量了真实的峰值性能(剧透:Apple 的“38 TOPS”数字具有误导性),并最终在一块专为推理设计的芯片上实现了神经网络的训练 

这是一个三部分系列的第 1 部分。我们在此介绍逆向工程——我们如何剥开层层结构以了解 M4 Neural Engine 的真实面貌以及如何直接与其对话。

什么是神经引擎?

ANE 不是 GPU,也不是 CPU。它是一个图执行引擎 ——一个定功能加速器,接受已编译的神经网络图并将整个图作为一个原子操作来执行。你不会下发单独的乘加指令。你提交描述整个计算图的已编译程序,硬件会端到端地执行它。

Apple 在 A11(2017 年)中引入了神经引擎,最初为双核设计。每一代都在扩展规模:

深入解析 M4 Apple Neural Engine(第 1 部分):逆向工程 我们如何绕过 CoreML,直接与硬件对话

 

M4 的 ANE(代号 H16G)就是我们正在研究的对象。16 核,127 个评估请求的队列深度,独立的 DVFS(动态电压/频率调节),以及在空闲时能将其功耗硬性门控降至精确的 0 毫瓦。

先前研究

我们并不是第一个探究 ANE 内部结构的人。

  • hollance/neural-engine — Matthijs Hollemans 对 ANE 行为、性能特征和支持操作的全面社区文档。现存关于 ANE 的最佳单一资源。

  • mdaiter/ane — 早期的逆向工程,提供可运行的 Python 和 Objective-C 示例,记录了 ANECompiler 框架和 IOKit 分发。

  • eiln/ane — 一个为 ANE 逆向工程的 Linux 驱动(Asahi Linux 项目),提供对内核级接口的洞见。

  • apple/ml-ane-transformers — Apple 为 ANE 优化的变换器参考实现,确认了诸如通道优先布局和偏好 1×1 卷积等设计模式。

但据我们所知,之前还无人:(a)在 M4 上无需 CoreML 即实现直接 _ANEClient API 访问,(b)破解内存中 MIL 编译路径,(c)测量绕过 CoreML 开销的真实峰值吞吐量,或(d)在 ANE 上训练模型。

方法论

我们的方法结合了多种技术:

  1. 类发现 :通过在 AppleNeuralEngine.framework 上运行 dyld_info -objc——这会导出所有 Objective-C 类和方法

  2. 方法交换(Method swizzling):拦截 CoreML 对私有 ANE 框架的调用

  3. 二进制分析 :分析已编译的 E5 包以理解神经程序格式

  4. 扩展性分析 — 通过改变矩阵尺寸、图深度和通道数以推断硬件拓扑

我们在 AppleNeuralEngine.framework 中发现了 40 多个私有类,包括 _ANEClient 、 _ANEModel 、 _ANERequest 、 _ANEIOSurfaceObject 、 _ANEInMemoryModel 以及更多。

软件栈

下面是完整的 ANE 软件栈,从公共 CoreML API 到硬件:

深入解析 M4 Apple Neural Engine(第 1 部分):逆向工程 我们如何绕过 CoreML,直接与硬件对话

 

关键见解:CoreML 并非唯一的入口。位于 AppleNeuralEngine.framework 中的 _ANEClient 类提供了直接访问编译 → 加载 → 评估流水线的能力。CoreML 只是之上的一个便捷层。

_ANEClient:直接访问神经引擎

以下是在不使用 CoreML 的情况下,在 ANE 上编译并运行程序的完整步骤:

// 1. Get shared client connection
id client = [_ANEClient sharedConnection];

// 2. Create model reference
id model = [_ANEModel modelAtURL:compiledURL key:@"mykey"];

// 3. Compile (MIL text → E5 binary, result is cached)
[client compileModel:model options:@{
    @"kANEFModelType": @"kANEFModelMIL",
    @"kANEFNetPlistFilenameKey": @"model.mil"
} qos:21 error:&err];

// 4. Load program onto ANE hardware
[client loadModel:model options:@{} qos:21 error:&err];
// → programHandle assigned, queueDepth = 127

// 5. Create IOSurface I/O buffers
IOSurfaceRef surface = IOSurfaceCreate(props);
id wrapped = [_ANEIOSurfaceObject objectWithIOSurface:surface];

// 6. Build evaluation request
id req = [_ANERequest requestWithInputs:@[wA, wB]
    inputIndices:@[@0, @1]
    outputs:@[wOut]
    outputIndices:@[@0]
    weightsBuffer:nil
    perfStats:nil
    procedureIndex:@0];

// 7. Execute on ANE
[client evaluateWithModel:model options:@{}
    request:req qos:21 error:&err];

// 8. Read results from output IOSurface
IOSurfaceLock(outSurface, kIOSurfaceLockReadOnly, NULL);
float *data = IOSurfaceGetBaseAddress(outSurface);
// ... read results ...
IOSurfaceUnlock(outSurface, kIOSurfaceLockReadOnly, NULL);

I/O 使用 IOSurfaces——与 GPU 纹理使用的共享内存机制相同。这意味着如果共享相同的 IOSurfaceRef,理论上 GPU 与 ANE 之间可以实现零拷贝传输。

主要发现: ANE 支持 127 的队列深度——最多可以同时有 127 个评估请求在处理之中。这个深度远超大多数加速器队列,表明硬件是为高吞吐量的流式推理而设计的。

MIL:输入语言

CoreML 不会以 ONNX 或 protobuf 格式将神经网络发送到 ANE。它使用 MIL —— 机器学习中间语言(Machine Learning Intermediate Language)—— 一种带类型的 SSA(静态单赋值)表示,看起来像这样:

program(1.3)
[buildInfo = dict<string, string>({
    {"coremltools-version", "9.0"}
})]
{
    func main<ios18>(
        tensor<fp16, [1, 1024, 1, 1024]> x,
        tensor<fp16, [1, 1024, 1, 1024]> w
    ) {
        bool tx = const()[val = bool(false)];
        bool ty = const()[val = bool(false)];
        tensor<fp16, [1, 1024, 1, 1024]> out =
            matmul(transpose_x = tx, transpose_y = ty,
                   x = x, y = w);
    } -> (out);
}

MIL 意外地可读性很强。每个数值都有精度和形状的类型。操作有名称并采用关键字参数。函数签名用显式维度声明输入张量。

张量布局遵循 ANE 的原生 NCDHW + Interleave 格式: [Batch, Channels, Depth, Height, Width]。对于一个 1024×1024 的矩阵,在 4 维中表示为 [1, 1024, 1, 1024]

E5 二进制:已编译格式

当 ANECompiler 处理 MIL 程序时,它会生成一个 E5 二进制 — 一个采用 FlatBuffer 结构的文件,包含以下部分:

深入解析 M4 Apple Neural Engine(第 1 部分):逆向工程 我们如何绕过 CoreML,直接与硬件对话

有趣的是:一个 1024×1024 的矩阵乘法编译后为 2,688 字节 。一个 128×128 的矩阵乘法编译后为 2,680 字节 。几乎相同。E5 二进制并没有编码矩阵乘法算法——它编码的是一个参数化程序 ,其行为在运行时由张量描述符控制。这个“微码”更像是一种配置,而非传统的机器代码。

含义: ANE 硬件很可能只有一小套固定的计算基本操作(卷积、矩阵乘法、按元素运算),这些操作由张量形状描述符参数化。E5 二进制描述了应当如何串联这些基本操作以及如何连接它们,而不是描述具体的计算本身。

内存路径:圣杯

基于文件的编译路径可行,但有一个问题:它需要将 MIL 文本写入磁盘,创建目录结构,并将编译器指向该目录。对于训练——我们需要在每几步就用更新的权重重新编译——这种文件系统往返是不可接受的。

我们发现了 _ANEInMemoryModelDescriptor,它直接在内存中接受 MIL 文本:

id desc = [_ANEInMemoryModelDescriptor
    modelWithMILText:milData      // NSData*, not NSString*!
    weights:weightDict            // NSDictionary*, not NSData*!
    optionsPlist:nil];

id model = [_ANEInMemoryModel
    inMemoryModelWithDescriptor:desc];

[model compileWithQoS:21 options:@{} error:&err];
[model loadWithQoS:21 options:@{} error:&err];
[model evaluateWithQoS:21 options:@{}
    request:req error:&err];

使其正常工作我们解决了三个耗费数日调试的难点:

  1. NSData,而不是 NSString:参数 milText 需要一个包含 UTF-8 字节的 NSData*,而不是 NSString*。传入字符串会静默失败。

  2. NSDictionary,而不是 NSData:参数 weights 是一个将权重名称映射到 NSData 二进制块的字典,而不是单个数据缓冲区。

  3. 临时目录变通 :即使是“内存中”路径,内部也会写入临时目录。如果你没有对默认位置的写入权限,编译会以不明错误失败。我们必须确保有一个可写的临时路径可用。

还有一个有趣的发现:Apple 的内部代码在某个类名中引用了一个 Desctiptor(原文如此)。即便是 Apple 的工程师在私有 API 中也会出现拼写错误。:)

硬件概况:我们已知的信息

通过对 IOKit 的探测、频率/电压缩放分析和功耗测量,我们构建出了关于 M4 ANE 的以下概要:

深入解析 M4 Apple Neural Engine(第 1 部分):逆向工程 我们如何绕过 CoreML,直接与硬件对话

DVFS 通道

IOKit 的 IOReportLegend 显示 ANE 具有独立的电源管理,支持自适应时钟、抖动以及多种硬件/软件触发机制:

ANE_ADCLK_TRIG    — adaptive clock trigger
ANE_ADHWTRG       — hardware adaptive trigger
ANE_ADSWTRG       — software adaptive trigger
ANE_DITHR_TRIG    — dithering trigger
ANE_PPT_TRIG      — power/performance tuning
ANE_PPT_SWTRG     — software PPT trigger
ANE_PPT_HWTRG     — hardware PPT trigger
ANE_EXT_TRIG0-3   — external triggers

这种级别的 DVFS 复杂性表明 ANE 能够根据工作负载特性独立调整其频率和电压,独立于 CPU 和 GPU 的电源域。

支持的操作

从 ANECompiler.framework 导出的内容来看,ANE 本机支持:

深入解析 M4 Apple Neural Engine(第 1 部分):逆向工程 我们如何绕过 CoreML,直接与硬件对话

 

值得注意的是,Conv 似乎是 ANE 的主要计算原语。正如我们将在第 2 部分展示的那样,将 matmul 表示为 1×1 卷积可以显著提升吞吐量。

The IOSurface 协议

所有与 ANE 的数据传输都使用 IOSurfaces。协议很简单:

// Create an IOSurface for a 1024x1024 float tensor
NSDictionary *props = @{
    @"IOSurfaceWidth":           @(1024),
    @"IOSurfaceHeight":          @(1024),
    @"IOSurfaceBytesPerElement": @(4),     // float32
    @"IOSurfaceBytesPerRow":     @(4096),  // 1024 * 4
    @"IOSurfaceAllocSize":       @(4194304),
    @"IOSurfacePixelFormat":     @(0)
};
IOSurfaceRef surface = IOSurfaceCreate(props);

// Write data
IOSurfaceLock(surface, 0, NULL);
float *ptr = IOSurfaceGetBaseAddress(surface);
memcpy(ptr, data, 4194304);
IOSurfaceUnlock(surface, 0, NULL);

// Wrap for ANE
id wrapped = [_ANEIOSurfaceObject objectWithIOSurface:surface];

由于 IOSurface 与 GPU 纹理共享使用相同机制,这就打开了零拷贝 GPU↔ANE 管道的可能性,两者可以在相同内存上运行。

编译缓存

ANE 编译器将 E5 二进制文件缓存到磁盘以避免重新编译:

~/Library/Caches/<app>/com.apple.e5rt.e5bundlecache/<build>/<hash>/
  <hash>.bundle/
  └── H16G.bundle/           ← H16G = M4 ANE target
      ├── H16G.e5            ← FlatBuffer binary (2-3 KB)
      └── main/
          └── main_ane/
              └── model.anehash

首次编译大约需要 20–40 毫秒。命中缓存几乎无需开销。这对推理很重要(编译一次,长期运行),但给训练带来了挑战,因为权重在每一步都在变化。

未被探索的领域

发现的若干类尚未探索,暗示着我们尚未测试的能力:

  • _ANEChainingRequest — 可能允许在一次调度中串联多个已编译模型

  • _ANESharedEvents / _ANESharedSignalEvent / _ANESharedWaitEvent — 类似 Metal 的栅栏/信号原语,用于 GPU↔ANE 同步

  • _ANEPerformanceStats — 可能是硬件性能计数器

  • _ANEVirtualClient — 虚拟化的 ANE 访问,可能用于多进程共享

还有一些我们确实不知道的事情:

  • ANE 核心的具体微架构和指令集 ISA

  • 图(graph)中各操作如何分配到核心上

  • ANE 时钟频率(DVFS 使其动态可变)

  • 是否可以访问硬件性能计数器

  • 确切的 SRAM 拓扑结构(分成多个 bank?统一?每核独立?)

接下来是什么

既然我们已能直接访问 ANE,就可以实际测量它的能力。 第 2 部分 , 我们将对所有内容进行基准测试:矩阵乘法的扩展性、SRAM 性能陡降点、为何卷积比矩阵乘法快 3 倍、为何 Apple 的“38 TOPS”说法具有误导性,以及绕过 CoreML 如何为你带来 2–4 倍的吞吐量提升。

第 3 部分中,我们将做苹果声称你不能做的事:在神经引擎上训练神经网络。


所有代码可在 github.com/maderix/ANE 的 ane/ 目录中获取。已在 M4 Mac Mini,macOS 15.x 上测试。

了解 RecodeX 的更多信息

立即订阅以继续阅读并访问完整档案。

继续阅读