1. 为什么嵌入式老兵不该去卷大模型训练
1.1 一个被忽视的错位竞争逻辑
这两年大模型火得一塌糊涂,我身边不少做了五六年单片机和RTOS的朋友都动了心思,想去读个硕博或者报个培训班转行搞算法。每次聊到这个话题,我都会先泼一盆冷水:你一个写了五六年C语言、天天跟寄存器打交道的人,跑去跟科班硕博卷Transformer架构和分布式训练,这不是拿自己的短板去撞别人的长板吗?
先不说算法岗现在卷成什么样,光是那些数学推导、论文复现、大规模集群调参的经验门槛,就够你喝一壶的。更关键的是,你过去积累的那些东西——中断嵌套怎么优化、内存碎片怎么治理、低功耗怎么调——在纯算法岗里几乎用不上,等于把最值钱的资产清零了。
但反过来看,大模型要真正落地到设备上,中间有一道巨大的鸿沟:算法团队训练出来的模型,怎么塞进算力有限、内存有限、功耗敏感的嵌入式设备里跑起来?这道鸿沟,恰恰是嵌入式老兵的主场。模型量化、算子裁剪、内存复用、异构调度、实时性保障——这些活儿,纯算法背景的人干不了,纯嵌入式背景但不懂AI的人干着也吃力。而一个懂Linux、懂底层、又愿意花时间理解端侧推理框架的嵌入式老兵,正好卡在这个位置上。
所以我的核心观点很明确:3到8年的嵌入式老兵,与其去卷大模型训练,不如走“Linux + 端侧AI部署”这条路。这不是退而求其次,而是一次精准的错位竞争。
1.2 端侧AI部署到底在做什么
很多人对“端侧AI部署”这个词的理解很模糊,以为就是把模型下载到板子上跑一下。实际上,这个岗位要做的事情远比想象中复杂。
简单来说,端侧AI部署工程师的核心任务是:把一个在服务器上训练好的模型,经过一系列转换和优化,最终在嵌入式设备上以可接受的延迟、功耗和精度运行起来。这里面涉及的工作包括但不限于:模型格式转换(比如从训练框架导出到推理框架支持的格式)、量化(把FP32的权重压成INT8甚至INT4)、算子适配(有些算子端侧NPU不支持,需要替换或重写)、内存规划(推理时的峰值内存要控制在设备可用内存以内)、多线程调度(CPU、GPU、NPU怎么协同)、以及最终的精度和性能调优。
我举个具体的例子。假设有一个目标检测模型,在服务器上跑一张图要30毫秒,精度mAP是0.45。现在要把它部署到一个带NPU的开发板上,NPU算力只有1TOPS,内存只有512MB。你直接拿原始模型去跑,大概率是跑不起来的——要么算子不支持,要么内存爆了,要么延迟高到没法用。这时候就需要你做量化、做算子替换、做内存复用,最终可能把模型压到原来的四分之一大小,延迟控制在50毫秒以内,精度只掉1到2个百分点。这个过程,就是端侧AI部署工程师的日常。
1.3 为什么是Linux而不是RTOS
有人可能会问:嵌入式不是RTOS的天下吗?为什么标题里强调的是Linux?
这个问题问得好。确实,在MCU层面,RTOS是绝对主流。但端侧AI部署的场景,绝大多数发生在算力更强的应用处理器上,比如ARM Cortex-A系列、瑞芯微的RK系列、晶晨的A系列、英伟达的Jetson系列等等。这些芯片跑的都是Linux,因为AI推理框架(如TensorFlow Lite、ONNX Runtime、NCNN、MNN等)基本都依赖Linux的运行时环境,需要POSIX接口、需要动态内存管理、需要文件系统来加载模型。
而且,Linux带来的不只是跑模型的能力,还有一整套开发生态:你可以用Python做模型转换和精度验证,用C++写推理引擎,用shell脚本做自动化测试,用perf做性能分析。这些工具链在RTOS上要么没有,要么极其简陋。所以,如果你想做端侧AI部署,Linux是绕不过去的。好消息是,对于有嵌入式底子的老兵来说,Linux的学习曲线并不陡——你不需要成为内核专家,只需要掌握用户态开发、系统调用、内存管理、进程线程调度这些核心概念就够了。
2. 嵌入式老兵转端侧AI的独特优势
2.1 底层调试能力是稀缺资源
我见过不少算法背景的人转端侧部署,他们最大的短板不是不懂模型,而是不懂底层。模型跑不起来,他们第一反应是“是不是模型转换有问题”,然后反复折腾转换脚本。但实际问题可能出在内存对齐、DMA传输、cache一致性、或者NPU驱动版本不匹配上。这些问题,没有底层调试经验的人根本无从下手。
而嵌入式老兵在这方面有天然优势。你过去调I2C不通、SPI时序不对、中断丢失这些问题的经验,在端侧AI部署里全都能用上。比如NPU推理结果不对,你可能需要去看DMA描述符有没有正确配置;比如推理延迟波动很大,你可能需要去查CPU频率调节策略和中断亲和性设置。这些活儿,说白了就是嵌入式调试的延伸,只不过调试对象从外设变成了AI加速器。
我认识一个做了七年工业控制的工程师,转做端侧AI部署后,最让他得心应手的不是模型量化,而是用逻辑分析仪抓NPU和DDR之间的通信时序,发现了一个驱动层面的cache刷新问题。这种问题,纯算法背景的人可能查一个月都查不出来,但他三天就定位了。这就是底层能力的价值。
2.2 对资源约束的敏感度是算法团队没有的
算法团队在服务器上训练模型,内存随便用,算力随便堆,他们很少需要考虑“这个操作会不会多占1MB内存”或者“这个算子会不会多花5毫秒”。但嵌入式设备上,每一KB内存和每一毫秒延迟都是要抠出来的。
嵌入式老兵对资源约束的敏感度是刻在骨子里的。你过去写MCU代码时,RAM都是以KB为单位计算的,堆栈溢出是要死机的,所以你会本能地去优化每一个变量、每一个缓冲区。这种思维习惯在端侧AI部署里极其宝贵。比如做模型量化时,你会主动去考虑权重的内存布局对cache命中率的影响;做推理流水线设计时,你会自然地去想怎么用双缓冲来隐藏IO延迟。这些优化点,算法背景的人往往意识不到,因为他们从来没有在资源极度受限的环境下写过代码。
2.3 软硬协同的思维方式
端侧AI部署本质上是一个软硬协同的问题。你不能只盯着软件层面,还要理解硬件加速器的工作原理。比如NPU的算力峰值是1TOPS,但实际能跑出多少,取决于你的模型结构、数据复用率、内存带宽、以及驱动调度策略。这些因素里,有些是软件可以优化的,有些是硬件限制,你需要能判断哪些是能改的,哪些是改不了的。
嵌入式老兵天然具备这种软硬协同的思维。你过去调外设时,既要看datasheet里的硬件参数,又要写驱动代码,还要考虑实际信号完整性。这种“从硬件到软件再回到硬件”的思维方式,在端侧AI部署里同样适用。你会去查NPU的指令集手册,看哪些算子有硬件加速;你会去分析内存带宽瓶颈,判断是不是数据搬运拖慢了推理速度;你会去调CPU的DVFS策略,在功耗和性能之间找平衡点。这些能力,是纯软件背景的人很难具备的。
3. Linux端侧AI部署的核心技术栈
3.1 推理框架选型:没有最好只有最合适
端侧AI部署的第一步是选推理框架。市面上主流的框架有TensorFlow Lite、ONNX Runtime、NCNN、MNN、Tengine、RKNN等,每个都有自己的适用场景和坑。
TensorFlow Lite的优势是生态完善,Google官方支持,文档齐全,而且有XNNPACK后端可以在CPU上跑得比较快。但它的缺点是算子支持相对保守,一些自定义算子需要自己实现,而且对国产NPU的支持不如国产框架好。
ONNX Runtime的优势是模型格式统一,几乎所有训练框架都能导出ONNX,然后直接用ORT跑。它的Execution Provider机制也很灵活,可以挂载不同的硬件后端。但ORT在端侧的资源占用相对较大,启动时间也比较长,不太适合资源极度受限的场景。
NCNN和MNN是国产框架里的佼佼者,对ARM CPU的优化非常到位,内存占用小,启动快,而且支持Vulkan和OpenCL后端。NCNN的代码非常干净,适合学习推理框架的实现原理;MNN则在工程化上做得更好,支持模型压缩和多种异构调度。
RKNN是瑞芯微自家的框架,专门针对RK系列芯片的NPU做了深度优化。如果你用的是RK3588这类芯片,用RKNN能发挥出NPU的全部算力,但代价是绑定硬件平台,换芯片就要重新适配。
我的建议是:先确定目标硬件平台,然后选该平台官方支持最好的框架。如果是通用ARM CPU场景,NCNN或MNN是首选;如果是特定NPU平台,优先用厂商自家框架;如果需要跨平台兼容,ONNX Runtime更合适。
3.2 模型量化:精度和速度的平衡艺术
量化是端侧AI部署里最核心的优化手段之一。简单说,就是把模型权重和激活值从FP32压缩成INT8或INT4,从而减少内存占用、提高推理速度、降低功耗。但量化不是免费的午餐,它会带来精度损失,而且有些模型对量化非常敏感。
量化的方式主要分两种:训练后量化(PTQ)和量化感知训练(QAT)。PTQ是在模型训练完成后直接做量化,不需要重新训练,操作简单但精度损失可能较大。QAT是在训练过程中模拟量化误差,让模型适应量化后的数值分布,精度更好但需要算法团队配合重新训练。
对于嵌入式老兵来说,你大概率会先接触PTQ,因为不需要动训练流程。PTQ的核心是确定量化参数,也就是scale和zero_point。对于对称量化,scale等于权重绝对值的最大值除以127;对于非对称量化,还需要计算zero_point来对齐零点。这些参数的计算看似简单,但实际效果取决于校准数据集的选择。校准集要有代表性,不能只用几张图,否则量化后的模型在实际场景里精度会崩。
我踩过的一个坑是:用PTQ量化一个检测模型,校准集用了100张图,量化后mAP掉了5个点。后来把校准集增加到500张,并且覆盖了各种光照和角度,mAP只掉了1.5个点。所以校准集的代表性和数量,直接决定了PTQ的成败。
3.3 算子适配与图优化
推理框架支持的算子集是有限的,尤其是NPU后端,往往只支持常见的卷积、池化、全连接等算子。如果你的模型里有自定义算子或者冷门算子,就需要做算子适配。
算子适配有几种策略:一是用多个支持的算子组合来等价替换,比如用卷积加逐元素运算来模拟某些复杂激活函数;二是把不支持的算子放到CPU上跑,其他部分在NPU上跑,形成异构执行;三是自己写算子实现,这需要深入理解NPU的指令集和编程模型,门槛最高。
图优化则是另一个重要环节。推理框架通常会在加载模型时做一系列图优化,比如算子融合(把卷积、BN、ReLU融合成一个算子)、常量折叠(把编译期能算出来的常量提前算好)、内存复用(把生命周期不重叠的张量分配到同一块内存)。这些优化能显著减少推理时的计算量和内存占用。但有时候框架的自动优化会出问题,比如融合后的算子精度不对,或者内存复用导致数据被覆盖。这时候就需要你手动干预,关闭某些优化或者调整优化顺序。
3.4 内存管理与性能调优
端侧设备的内存通常很紧张,尤其是当模型较大或者需要同时跑多个模型时。内存管理的核心目标是:在保证推理正确性的前提下,尽可能降低峰值内存占用。
常用的手段包括:内存池化(预分配一大块内存,推理时从中切分,避免频繁malloc/free)、张量生命周期分析(找出哪些张量的生命周期不重叠,让它们共享内存)、以及就地操作(in-place operation,把输出直接写到输入的内存里)。这些手段在推理框架里通常有内置支持,但你需要理解它们的原理,才能在出问题时快速定位。
性能调优则涉及更多维度:CPU频率调节、线程亲和性设置、NPU频率和电压调节、DMA传输优化、以及推理流水线设计。比如你可以把预处理(图像缩放、归一化)放到一个线程,推理放到另一个线程,后处理再放到第三个线程,通过流水线并行来隐藏各阶段的延迟。这种优化思路,和嵌入式里常用的生产者-消费者模型是一脉相承的。
4. 从零搭建一个端侧AI部署环境
4.1 硬件选型:别一上来就买最贵的
很多新手一上来就想买Jetson Orin或者RK3588,觉得算力越强越好。但我的建议是:先想清楚你要部署什么模型、对延迟和功耗有什么要求,再选硬件。
如果你只是学习端侧AI部署的基本流程,一块树莓派或者香橙派就够了。ARM Cortex-A72或A76的CPU,配上NCNN或MNN,跑一些轻量级分类和检测模型完全没问题。而且树莓派的社区资源丰富,遇到问题容易找到答案。
如果你要做NPU相关的开发,RK3588是一个性价比很高的选择。它自带6TOPS算力的NPU,支持INT8和INT16推理,而且瑞芯微的RKNN工具链相对成熟。但要注意,RK3588的NPU对算子支持有限,一些复杂的注意力机制可能需要拆解或回退到CPU。
如果你要做更高级的异构计算,比如同时用CPU、GPU和NPU,Jetson系列是更好的选择。Jetson的CUDA生态和TensorRT支持非常完善,但价格也更高,而且功耗更大,不太适合电池供电的场景。
我的建议是:学习阶段用树莓派,项目阶段根据实际需求选RK3588或Jetson,不要盲目追求高算力。
4.2 开发环境搭建:交叉编译是绕不过去的坎
端侧AI部署的开发环境通常涉及交叉编译。你的开发机是x86的Linux,目标设备是ARM的Linux,所以需要配置交叉编译工具链。
以RK3588为例,你需要从芯片厂商获取对应的SDK,里面包含了交叉编译工具链、内核源码、以及NPU驱动和推理库。然后设置环境变量,比如CC和CXX指向交叉编译器,SYSROOT指向目标设备的根文件系统。这些配置看起来简单,但实际搭建时经常会遇到库版本不匹配、头文件路径不对、链接时找不到符号等问题。
我踩过的一个坑是:交叉编译时用了开发机上的OpenCV头文件,但目标设备上的OpenCV版本不同,导致运行时崩溃。后来我把目标设备的根文件系统挂载到开发机上,用--sysroot指定,确保头文件和库都来自目标设备,问题才解决。所以交叉编译的核心原则是:所有依赖都必须来自目标设备,不能用开发机上的替代。
4.3 模型转换与验证:精度对齐是关键
模型转换是把训练框架的模型转成推理框架支持的格式。比如把PyTorch的.pth转成ONNX,再转成RKNN或MNN。这个过程看似机械,但坑非常多。
最常见的问题是算子不支持。比如PyTorch里的某些操作在ONNX里没有对应算子,或者ONNX里的算子RKNN不支持。这时候你需要修改模型结构,用支持的算子来等价替换。另一个常见问题是精度对齐:转换后的模型在推理框架里的输出,和原始模型在训练框架里的输出对不上。这可能是数值精度问题(FP32到FP16的舍入误差),也可能是算子实现差异(比如padding方式不同)。
我的做法是:转换后先用一张固定的输入图片,分别在训练框架和推理框架里跑一遍,逐层对比输出。如果某一层开始出现较大偏差,就重点排查那一层对应的算子。这个逐层对比的方法虽然笨,但非常有效,能快速定位问题所在。
4.4 性能测试与瓶颈分析
模型跑起来之后,下一步是测性能。你需要关注几个指标:推理延迟(单帧耗时)、吞吐量(每秒处理帧数)、内存占用(峰值和均值)、以及功耗。
测试延迟时要注意预热。第一次推理通常比较慢,因为要加载模型、分配内存、初始化硬件。所以正式测试前要先跑几十次预热,等延迟稳定后再取平均值。测试时还要控制变量,比如固定CPU频率、关闭其他占用资源的进程,否则数据波动会很大。
瓶颈分析则需要用性能分析工具。Linux下可以用perf看CPU热点,用ftrace看内核调度,用厂商提供的NPU profiling工具看NPU利用率。如果发现NPU利用率很低,可能是数据供给跟不上,需要优化预处理或内存拷贝;如果CPU占用很高,可能是后处理太重,需要优化算法或移到NPU上跑。
5. 实操案例:在RK3588上部署一个轻量级检测模型
5.1 模型选择与转换
假设我们要部署一个轻量级目标检测模型,比如YOLOv5s或YOLOv8n。这两个模型在服务器上mAP不错,参数量也不大,适合端侧部署。
首先从训练框架导出ONNX模型。导出时要注意opset版本,RKNN对opset 11到13支持比较好,太新或太旧都可能出问题。导出后可以用ONNX Simplifier做一下图简化,去掉冗余算子。
然后用RKNN Toolkit把ONNX转成RKNN格式。转换时需要指定目标平台(RK3588)、量化方式(INT8)、以及校准数据集。校准数据集我一般准备200到500张图,覆盖不同场景,这样量化后的精度更稳。
转换完成后,RKNN Toolkit会输出量化精度分析报告,告诉你每一层的量化误差。如果某些层误差特别大,可以考虑把这些层保留为FP16,其他层用INT8,形成混合量化。
5.2 推理代码编写与优化
推理代码的核心流程是:加载RKNN模型、初始化运行时、读取输入图片、预处理、推理、后处理、输出结果。
预处理包括resize、归一化、以及NHWC到NCHW的转换。这些操作可以用OpenCV做,也可以用RGA(Rockchip Graphics Accelerator)做。RGA是硬件加速器,做resize和格式转换比CPU快很多,而且不占CPU资源。如果你的场景对延迟敏感,强烈建议用RGA做预处理。
后处理主要是解码检测框和做NMS。这部分计算量不大,但逻辑比较复杂,容易出bug。我建议先用Python写一版后处理,和训练框架的输出对齐,确认无误后再移植到C++。
优化方面,可以用多线程流水线:一个线程做预处理,一个线程做推理,一个线程做后处理。三个线程通过队列传递数据,形成流水线。这样能把各阶段的延迟隐藏起来,整体吞吐量能提升不少。
5.3 实测数据与调优记录
我在RK3588上实测YOLOv8n,输入640x640,INT8量化,单线程推理延迟大约25毫秒,三线程流水线后吞吐量能到每秒50帧左右。NPU利用率在70%到80%之间,CPU占用率大约30%。
调优过程中发现两个瓶颈:一是预处理耗时较长,用OpenCV做resize要8毫秒左右,换成RGA后降到2毫秒;二是后处理的NMS在检测框较多时耗时波动大,后来把NMS的阈值调高了一些,减少了候选框数量,延迟更稳定了。
功耗方面,RK3588在跑这个模型时整板功耗大约5瓦,如果加上散热风扇会更高。如果是电池供电的场景,可能需要进一步降低推理频率或者用更小的模型。
6. 常见问题与排查技巧实录
6.1 模型转换失败怎么办
模型转换失败是最常见的问题,表现五花八门:有的报算子不支持,有的报维度不匹配,有的转换成功但推理结果全错。
排查思路是:先看错误信息,定位到具体是哪个算子或哪一层出了问题。如果是算子不支持,查一下推理框架的算子支持列表,看有没有替代方案。如果是维度问题,用Netron打开ONNX模型,逐层检查输入输出维度,找出哪一层的维度对不上。如果是推理结果全错,大概率是量化问题或者输入预处理不一致,先用FP16跑一遍,确认模型本身没问题,再逐步排查量化环节。
6.2 推理结果和训练框架对不上
这个问题也很常见,原因通常有三类:预处理不一致、量化误差、算子实现差异。
预处理不一致是最容易犯的错。比如训练时用的是RGB通道,推理时用了BGR;训练时归一化是除以255,推理时忘了除;训练时resize用的是双线性插值,推理时用了最近邻。这些细节一定要和算法团队确认清楚,最好让他们提供一份预处理参考代码。
量化误差则可以通过混合量化来缓解,把敏感层保留为FP16。算子实现差异比较难搞,只能逐层对比输出,找到差异大的层,然后看能不能用其他算子替换。
6.3 推理延迟波动大怎么排查
延迟波动大通常和系统调度有关。可能的原因包括:CPU频率调节策略导致频率波动、其他进程抢占CPU资源、内存带宽被其他任务占用、或者NPU驱动有bug。
排查时先用cpufreq-set把CPU频率固定到最高,看波动是否减小。如果还波动,用top或htop看有没有其他进程在抢资源。如果都正常,可能是NPU驱动的问题,尝试更新驱动版本或者调整NPU的工作模式。
6.4 内存不足导致推理失败
内存不足的表现通常是推理到一半程序崩溃,或者malloc返回NULL。排查时先用free看系统可用内存,再用推理框架的内存统计接口看模型本身占了多少内存。
如果模型本身内存占用就很大,可以考虑用更小的模型、更激进的量化、或者内存复用。如果是系统内存被其他进程占了,可以尝试杀掉不必要的进程,或者调整内存分配策略。
| 问题类型 | 典型表现 | 排查方向 | 解决手段 |
|---|---|---|---|
| 转换失败 | 报算子不支持 | 查算子支持列表 | 替换算子或回退CPU |
| 精度对不上 | 输出偏差大 | 逐层对比输出 | 混合量化或修正预处理 |
| 延迟波动 | 耗时忽高忽低 | 查CPU频率和进程 | 固定频率或隔离核心 |
| 内存不足 | 推理中途崩溃 | 查内存占用 | 量化或内存复用 |
| NPU利用率低 | 推理慢但NPU闲 | 查数据供给 | 优化预处理或DMA |
7. 学习路径与资源推荐
7.1 第一阶段:补齐Linux用户态开发能力
如果你之前主要做MCU开发,第一步是熟悉Linux用户态编程。重点掌握:文件IO、进程线程、内存管理、网络编程、以及Makefile和CMake。这些是端侧AI部署的基础,不要求精通内核,但用户态的东西要能熟练写。
推荐的学习方式是:找一个小项目练手,比如写一个基于socket的图片传输程序,或者写一个多线程的日志系统。通过实际项目来熟悉Linux的开发流程和调试工具。
7.2 第二阶段:理解推理框架的工作原理
选一个推理框架(推荐NCNN或MNN),读它的源码,理解模型加载、图优化、算子实现、内存管理这几个核心模块。不需要全部读懂,但关键流程要能说清楚。
同时动手跑几个模型,从分类模型开始,再到检测和分割模型。每跑一个模型,都记录下遇到的问题和解决方法,积累经验。
7.3 第三阶段:深入NPU和异构计算
如果你用的是带NPU的平台,下一步是深入NPU的编程模型。了解NPU的指令集、内存层次、以及驱动接口。尝试写一些自定义算子,或者做CPU-NPU的异构调度。
这个阶段最好的学习资料是芯片厂商的官方文档和SDK里的示例代码。虽然文档质量参差不齐,但这是最权威的信息来源。
7.4 第四阶段:做完整的端到端项目
最后,找一个完整的项目来做,从模型训练(可以找开源模型)到部署到设备上,全流程走一遍。项目不需要很复杂,但每个环节都要亲手做,遇到问题自己排查。做完一个完整项目,你对端侧AI部署的理解会有一个质的飞跃。
我个人在实际操作中的体会是:端侧AI部署这个方向,门槛不在算法,而在工程。你需要的是对底层系统的理解、对资源约束的敏感、以及软硬协同的思维方式。这些恰恰是嵌入式老兵多年积累下来的核心能力。所以别去跟算法硕博卷大模型训练了,把你的底层功底用在端侧部署上,这才是真正的扬长避短。