我是赵一帆,搞了8年DevOps,手里管着K8s集群和上百条CI流水线。去年苹果发布M3的时候,我们为了编译速度,把一部分macOS构建节点换成了M3 Mac mini,效果不错。今年M4出来,Geekbench单核直接飙到4000以上,我凌晨三点看到跑分,当晚就批了采购单——把剩下的x86构建机全换成M4 MacBook Air。但没想到,无风扇设计在持续压力测试下,差点导致我们的端侧AI推理服务SLA翻车。这篇文章记录了我从跑分数据到实际部署的整个过程,包括如何用Core ML把质检模型跑在NPU上,以及必须加哪些监控才能避免半夜被叫醒。
30秒速览
- - M4单核4032分,直接促使我们替换所有x86编译节点,但无风扇Air在持续满载下必然降频,温度撞墙后性能损失30%。必须监控温度和频率,否则CI流水线会堵塞。
- - 38 TOPS的NPU用Core ML跑MobileNetV3,推理延迟从40ms降到5ms,但NPU是串行处理,并发高时尾延迟飙升。需要调度器+ANE利用率告警。
- - 模型转换时compute_units选CPU_AND_NEURAL_ENGINE,若有不支持的op会静默回退CPU,必须用Xcode Performance Report确认NPU实际工作。
- - 与骁龙X Elite对比,M4单核碾压,生态优势巨大(原生Docker、Xcode),骁龙NPU移植成本高。端侧AI开发选M4,编译节点用M4但有风扇版本更稳。
M4的跑分让我把x86编译节点全砍了——但我忘了一件事
Geekbench单核4000+背后:大核、小核和NPU是怎么分工的
M4的单核跑分Geekbench 6达到了4032分,而我们还在用的Intel Mac mini(2018款,i7-8700B)单核只有1200多分。编译一个中型Go项目,在Intel上需要4分钟,M4上只用了1分20秒。于是我们连夜替换了5台构建节点,全部换上M4 MacBook Air 16GB内存版。但M4的架构不只是大核强悍,它包含4个性能核和6个能效核,还有16核的Neural Engine,也就是NPU,标称38 TOPS算力。在编译任务中,Xcode能够把轻量级任务调度到能效核上,这样即使全量编译,风扇也不会转——等等,MacBook Air压根没有风扇。这为后来的热降频埋下了伏笔。
我写了一个简单的监控脚本,用powermetrics抓取CPU频率和功耗,发现在单核爆发时,性能核可以瞬间冲到4.0GHz以上,功耗大约15W,然后迅速回落。但当6个编译任务并行时,所有核心一起上,功耗达到25W,持续不到2分钟,系统就开始限制频率。这就是无风扇设计的代价。
#!/bin/bash
# 用于监控M4 MacBook Air的功耗和频率,每秒输出一次
# 配合prometheus node_exporter textfile collector使用
output_dir="/var/tmp/m4_metrics"
mkdir -p $output_dir
# 启动powermetrics,采样间隔1000ms,输出到文件
sudo powermetrics -i 1000 --samplers cpu_power,gpu_power,thermal -o $output_dir/power.log &
PID=$!
# 等待30秒,生成第一个数据点
sleep 30
# 解析日志,提取关键指标写入prom文件
PROM_FILE="${output_dir}/m4.prom"
> $PROM_FILE
# 从powermetrics日志中提取最近10秒的平均CPU功耗
cpu_watts=$(tail -20 $output_dir/power.log | grep 'CPU Power:' | tail -1 | awk '{print $3}' | sed 's/W//')
if [ ! -z "$cpu_watts" ]; then
echo "m4_cpu_power_watts ${cpu_watts}" >> $PROM_FILE
fi
# 提取温度(取最后一个die温度)
temp=$(tail -20 $output_dir/power.log | grep 'CPU die temperature' | tail -1 | awk '{print $4}' | sed 's/C//')
if [ ! -z "$temp" ]; then
echo "m4_cpu_temp_celsius ${temp}" >> $PROM_FILE
fi
# 提取CPU频率(取性能核平均频率)
freq=$(tail -20 $output_dir/power.log | grep 'CPU Performance Core Frequency' | tail -1 | awk '{print $5}' | sed 's/MHz//')
if [ ! -z "$freq" ]; then
echo "m4_cpu_freq_mhz ${freq}" >> $PROM_FILE
fi
# 上传到node_exporter textfile目录
if [ -f $PROM_FILE ]; then
mv $PROM_FILE /opt/homebrew/var/lib/node_exporter/textfile_collector/m4.prom
fi
# 清理(生产环境需保留日志)
# kill $PID
这个脚本我们最初没有写,后来差点出事。持续编译时CPU降频到2.2GHz,编译时间拉长,导致CI任务堆积,半夜报警。加上监控后,我们设定了温度超过95°C持续2分钟的告警,一旦触发就把编译任务转移到有风扇的M4 Mac mini上。(延伸阅读:我把工厂三个月的缺陷数据喂给Claude Artifacts,午饭前就出了一版可交互看板,但上线那晚监控停了4个小时)
用powermetrics抓到的真实功耗:从峰值爆发到稳态的30分钟曲线
我们实际跑了30分钟的编译负载(用make -j10编译LLVM),开始3分钟内,CPU功耗维持在25W,温度从40°C迅速升到95°C,然后系统开始降频,功耗掉到15W,温度保持在95°C左右。这就是无风扇Air的散热策略:用外壳散热,撞温度墙后降频,但不会让温度继续上升。我们监控发现,频率在3.2GHz到2.2GHz之间波动,编译时间比标称性能下降约30%。所以,如果你准备用无风扇Air做持续高负载的编译节点,一定要监控温度和频率,否则你的CI SLA会崩。
无风扇MacBook Air的散热策略,比我之前设想的要聪明多了
我们用asitop和thermal pressure监控了一场编译马拉松
asitop是专门用来监控Apple Silicon芯片状态的命令行工具,可以实时显示CPU/GPU/NPU功耗、温度和thermal pressure。我在一台M4 MacBook Air上跑了12小时的编译压力测试,用asitop记录日志。thermal pressure初始为0,一旦撞墙会变成moderate、heavy、critical等级别。我发现,虽然降频了,但系统很少进入critical状态,说明苹果的调度策略会主动把一些线程迁移到能效核,避免性能核过热。这比Intel时代的暴力降频要智能。(延伸阅读:我让Copilot里三个模型轮番写SQL,结果Gemini差点让我半夜被客户电话轰炸,现在我把默认锁死在Claude 3.7 Sonnet)
但是,这种智能调度对于延迟敏感型应用(比如端侧AI推理)来说,可能是个坑。当你期望一个推理任务固定在性能核上跑,系统却把它挪到能效核,推理延迟可能从5ms跳到20ms。我们后面在Core ML推理中遇到了这个问题。
当温度撞墙时,M4是怎么保护自己的——我调了调度策略
在macOS上,你可以通过设置quality of service(QoS)来影响线程调度。在跑AI推理服务时,我们故意把进程的QoS设置为user-interactive,这样系统会尽可能把它固定在性能核上。但即使这样,在高温下还是会被降频。最终我们在压力测试中,采用了主动限制并发数量的方法,不让CPU跑到25W以上,维持性能稳定。(延伸阅读:Copilot多模型切换评测:我拿三个模型轮番干了6件事,差点删库跑路,最后我选了它)
# 在启动Core ML推理服务时,将进程的QoS设置为user-interactive
taskpolicy -c user-interactive python3 inference_server.py
# 或者在代码中设置(Objective-C/Swift),但我们用Python,所以用subprocess调用
# 也可以用pthread_set_qos_class_self_np,但更简单的方法是wrapper
端侧AI在M4上跑起来:我把一个质检模型移植到Core ML的全过程
Core ML模型转换:从PyTorch到.mlpackage,绕过那些坑
我们有一个基于MobileNetV3的产品缺陷检测模型,原本跑在Intel CPU上,延迟约40ms。我想把它移植到M4的NPU上。使用coremltools最新版进行转换,但第一次转换出来的模型在NPU上跑失败,回退到CPU。查了文档才发现某些op不支持NPU,必须把模型中的部分操作替换(比如Gather操作)。我们用coremltools的convert功能,指定minimum_deployment_target为iOS18(对应M4),并设置compute_units=CPU_AND_NEURAL_ENGINE,但实际运行时,如果模型图中有不支持的层,会自动回退到CPU,没有警告。这导致我们一开始以为用上了NPU,结果延迟还是40ms。后来我加了监控,用Xcode的Core ML Performance Report查看每个层的执行单元,才发现问题。
import coremltools as ct
import torch
import torchvision
# 加载训练好的PyTorch模型
model = torchvision.models.mobilenet_v3_small(pretrained=False)
model.load_state_dict(torch.load('defect_detector.pth')))
model.eval()
# 跟踪示例输入
example_input = torch.rand(1, 3, 224, 224)
traced_model = torch.jit.trace(model, example_input)
# 转换为Core ML
scale = 1/(0.226*255.0)
bias = [-0.485/(0.229), -0.456/(0.224), -0.406/(0.225)]
image_input = ct.ImageType(name='input', shape=example_input.shape, scale=scale, bias=bias)
# 转换,指定部署目标和计算单元
mlmodel = ct.convert(
traced_model,
inputs=[image_input],
minimum_deployment_target=ct.target.iOS18,
compute_units=ct.ComputeUnit.CPU_AND_NEURAL_ENGINE,
)
# 保存
mlmodel.save('defect_detector.mlpackage')
转换后,我们用coremltools的预测函数测试,并利用macOS的powermetrics观察ANE(Apple Neural Engine)功耗。如果ANE有功耗,说明NPU在用。否则就是CPU在跑。(延伸阅读:开发服务器启动2.1秒,生产构建却卡了我26秒——Next.js 15升级的72小时硬件实测)
在NPU上跑推理,延迟从40ms降到5ms,但前提是你得避开这些内存陷阱
成功让模型运行在NPU上后,单次推理延迟降到5ms,功耗只有2W,比CPU的15W好太多。但是,当我们并发多个推理请求时,发现NPU是顺序处理的,不能并行。多个请求会在ANE队列里排队,导致尾延迟升高。我们不得不引入一个请求调度器,控制并发数,并监控ANE队列深度。这部分监控我们用了自定义的Metric,通过Core ML的performance报告导出到Prometheus,一旦ANE利用率超过80%就触发告警,自动扩容到另一台M4节点。
| 测试项目 | 苹果M4 (MacBook Air 无风扇) | 苹果M3 (MacBook Pro 有风扇) | 骁龙X Elite (X1E-80-100) |
|---|---|---|---|
| Geekbench 6 单核 | 4032 | 3150 | 2950 |
| Geekbench 6 多核 | 15200 | 12050 | 15800 |
| LLVM编译 (make -j8, 时间越短越好) | 12分30秒 (降频后15分) | 15分 | 18分 (Windows ARM模拟) |
| Core ML NPU推理延迟 (MobileNetV3) | 5ms | 5.5ms | N/A (Snapdragon NPU需单独适配) |
| 功耗 (满载CPU, W) | 25W (降频后15W) | 30W (风扇压制) | 23W (参考) |
| 生态支持 (CI/CD) | 原生macOS, Docker支持良好 | 同M4 | Windows on ARM, 部分工具不兼容 |
从对比可以看出,M4的单核性能碾压,多核与骁龙X Elite相当,但骁龙在持续负载下可能因为散热更好而维持高频。但是,骁龙的生态在开发端仍不及macOS,我们的很多CI工具在x86模拟下性能损失严重。最终我们保留了M4作为主力编译节点,同时配合有风扇的M3 Mac mini做重负载任务。(延伸阅读:我解剖了Figure 02灵巧手的控制栈,才发现工业精密装配的瓶颈不在电机)
横评M4、M3和骁龙X Elite:功耗、性能和生态,你该选哪个做编译和AI节点
用同一套基准跑编译和推理:M4对M3的提升和骁龙的差距
上面表格已经展示,这里补充一些细节。骁龙X Elite的NPU有45 TOPS,但Core ML无法直接使用,需要通过QNN或DirectML,开发复杂度高。我们在Snapdragon X Elite上跑YOLOv8,用QNN推理延迟可以达到4.8ms,功耗6.1W,但移植工作花了我们一个工程师3天。而在M4上,用Core ML只需几个小时。对DevOps团队来说,时间就是金钱。
生态决定了80%的体验:Xcode、Docker和CI/CD支持对比
我们的CI流水线重度依赖GitHub Actions和自托管macOS runner。M4 MacBook Air可以直接作为runner,安装Xcode 16,运行iOS构建和测试。Docker Desktop在M4上已经原生支持,可以运行ARM容器,我们用podman也可以,一切顺畅。相比之下,骁龙X Elite只能运行Windows on ARM,虽然有了原生的ARM64 Docker和WSL,但很多镜像还是x86,需要QEMU模拟,性能打折扣。特别是我们用的监控agent(Datadog agent)在ARM64 Windows上还有bug,导致频繁重启。所以我果断把AI推理节点全押在M4上。
最后,我强调一点,如果没有监控,你永远不知道NPU是否在工作,以及温度是否撞墙。我们搭建了Prometheus + Grafana监控M4节点的功耗、温度、ANE利用率和推理延迟,设定多个告警。这些报警曾两次在凌晨把我叫醒,但至少避免了服务中断。