端侧AI这个词这两年出现的频率越来越高,但很多人对它的理解还停留在"把模型塞进手机里跑"这个层面。真正做过端侧部署的人都知道,模型能不能跑起来只是第一步,跑得快不快、功耗低不低、内存够不够,才是决定产品能不能落地的关键。而这些问题,最终都会收敛到一个核心命题上:张量这种数据结构,是怎么在NPU这种专用硬件上被高效执行的。
我自己是从移动端推理框架一路做过来的,早期用CPU跑卷积网络,后来转到GPU,再到现在主力做NPU算子适配和端侧部署。这个过程中踩过的坑、绕过的弯路,远比看几篇论文要多得多。这篇文章不打算讲太多理论推导,而是想从一个实际做端侧部署的工程师视角,把张量在NPU上的执行链路拆开来看——从数据排布、算子映射、内存调度到量化策略,每一层都有它存在的理由,也有它容易出问题的地方。
如果你正在做端侧AI硬件部署,或者想搞清楚CPU、GPU、NPU在跑大模型时到底有什么区别,又或者你只是好奇"张量"这个东西在硬件层面到底长什么样,那这篇内容应该能给你一些实在的参考。我会尽量用生活化的类比来解释底层机制,同时给出可以直接对照的实操思路和参数配置建议。
1. 张量在端侧硬件上到底长什么样
1.1 从数学概念到内存布局的转换
在数学课本里,张量就是一个多维数组,听起来很抽象。但在端侧硬件上,张量首先是一块连续的内存区域,然后才是一个多维数组。这个区别非常关键,因为硬件不关心你的数学定义,它只关心数据在内存里怎么排、怎么取。
举个具体的例子。一个形状为[1, 3, 224, 224]的输入张量,在PyTorch里是NCHW格式,意思是批次、通道、高度、宽度。但在很多NPU上,这个张量会被转换成NHWC格式,也就是批次、高度、宽度、通道。为什么要换?因为NPU的卷积计算单元通常按照通道维度做向量化,NHWC格式下,通道维度的数据在内存里是连续的,取数效率更高。
这个转换过程叫布局变换(Layout Transform),听起来只是换个顺序,但实际部署中,它可能是性能杀手。我遇到过不少情况,模型在CPU上跑得好好的,转到NPU上反而慢了,排查到最后就是布局变换引入了额外的内存拷贝。所以你在做端侧部署时,第一件事就是确认目标硬件偏好的张量布局,然后在模型转换阶段就把布局固定下来,避免运行时反复转换。
1.2 端侧硬件的内存层级与张量驻留策略
端侧设备和服务器最大的区别在于内存层级极其复杂。服务器上你基本只需要关心DDR和显存,但端侧SoC里通常有:
- 寄存器文件:最快,但容量极小,通常只有几十KB
- 片上SRAM:速度接近寄存器,容量在几百KB到几MB之间
- L2/L3缓存:共享缓存,容量稍大但延迟增加
- DDR/LPDDR:主存,容量大但带宽有限、功耗高
张量在NPU上执行时,理想情况是数据从DDR加载到SRAM,计算单元从SRAM取数,算完再写回DDR。但现实是,SRAM容量有限,大张量必须分块(Tiling)处理。分块策略直接决定了你的算子性能。
我实测过一个典型的卷积层,输入特征图是[1, 64, 56, 56],权重是[128, 64, 3, 3]。如果不做分块,整个特征图需要约800KB的SRAM,很多端侧NPU根本放不下。分成4块之后,每块只需要200KB左右,但代价是边界数据需要重复加载。这个权衡就是端侧部署的核心艺术——用计算换内存,或者用内存换计算,没有免费午餐。
提示:在做NPU算子开发时,一定要先查清楚目标硬件的SRAM容量和DMA带宽。这两个参数决定了你的分块策略上限,也决定了你能跑多大的模型。
1.3 张量形状对NPU执行效率的隐性影响
很多人以为张量形状只影响计算量,其实它还深刻影响NPU的流水线效率。NPU的计算单元通常是脉动阵列(Systolic Array)或者类似的矩阵乘加结构,这种结构对张量的维度有对齐要求。
比如某个NPU的矩阵乘法单元是16x16的,那么你的张量维度最好是16的倍数。如果通道数是17,硬件会把它padding到32,多出来的计算全是浪费。我见过一个模型,因为把通道数从64改成了65,端侧推理速度直接掉了30%。这不是理论推导,是实测数据。
所以在端侧模型设计阶段,就要有意识地把通道数、特征图尺寸对齐到硬件的友好数值。常见的友好数值是8、16、32、64。如果你用的是现成的模型,那在转换阶段也要检查是否有维度不对齐导致的padding浪费。
2. NPU执行张量运算的底层流水线
2.1 指令下发与数据搬运的并行机制
NPU和CPU最大的区别在于,CPU是通用计算,一条指令做一件事;NPU是专用计算,一条指令可能触发一整套数据搬运和计算流程。理解这一点,是理解NPU执行逻辑的关键。
典型的NPU执行一条卷积指令,背后会发生这些事:
- DMA引擎从DDR读取输入张量和权重到SRAM
- 计算单元从SRAM读取数据,执行矩阵乘加
- 累加器暂存中间结果
- 激活函数单元对结果做非线性变换
- DMA引擎把输出写回DDR
这些步骤在硬件上是流水线并行的。也就是说,当计算单元在处理第N块数据时,DMA引擎已经在搬运第N+1块数据了。这种并行机制决定了NPU的性能上限,但也带来了一个麻烦:如果数据搬运和计算不匹配,流水线就会停顿。
我遇到过最典型的情况是,权重数据太大,DMA搬运时间超过了计算时间,导致计算单元经常空转。解决办法是把权重量化到INT8甚至INT4,减少搬运量。这也是为什么端侧部署几乎必然要做量化——不只是为了省内存,更是为了让流水线跑满。
2.2 算子融合如何减少张量的往返搬运
在CPU上,每个算子独立执行,中间结果写回内存,下一个算子再读出来。这在端侧是灾难性的,因为每次往返DDR都意味着带宽消耗和功耗增加。
NPU通常支持算子融合(Operator Fusion),把多个算子合并成一个执行单元。最常见的融合模式是Conv+BN+ReLU,这三个算子融合后,中间结果不需要写回DDR,直接在SRAM里传递。
但融合不是无条件的。我踩过的一个坑是,某些NPU对融合算子的输入输出张量形状有要求,如果形状不匹配,融合会失败,退化成独立执行。更隐蔽的是,有些框架在转换时会"假装"融合成功,但实际运行时还是分开执行的。所以你在部署后一定要用性能分析工具确认融合是否真的生效,不能只看转换日志。
下面是一个典型的融合前后对比,以某端侧NPU为例:
| 配置 | 执行时间 | DDR访问次数 | 功耗 |
|---|---|---|---|
| 独立执行Conv+BN+ReLU | 4.2ms | 6次 | 基准 |
| 融合执行Conv+BN+ReLU | 2.8ms | 2次 | 降低约35% |
这个数据是我在实际项目中测出来的,不同硬件会有差异,但趋势是一致的:融合能显著减少内存往返,从而提升性能和能效。
2.3 多核NPU的任务划分与张量切分
现在很多端侧NPU是多核架构,比如4核NPU或者大小核NPU。多核意味着张量可以被切分到不同核心上并行计算,但切分方式很有讲究。
最简单的切分是按批次切分,比如批次为4,每个核心处理一个样本。但端侧推理通常批次为1,这时候就要按通道或者空间维度切分。按通道切分的问题是,每个核心都需要完整的输入特征图,输入数据被重复读取。按空间切分的问题是,边界区域需要halo区域,增加了计算量。
我实际用下来,按通道切分在大多数端侧NPU上表现更好,因为通道维度的数据局部性更好,DMA搬运效率更高。但这也取决于具体硬件的DMA设计,有些NPU的DMA对空间连续访问更友好,那就适合按空间切分。
注意:多核切分不是越多越好。核心之间的同步开销、DMA通道竞争都会抵消并行收益。我一般会从2核开始试,逐步增加,找到性能拐点。
3. 量化:让张量在NPU上跑得更快的关键手段
3.1 从FP32到INT8:量化到底损失了什么
量化是端侧部署绕不开的话题。FP32的权重和激活值占4字节,INT8只占1字节,内存占用直接降到四分之一,DMA搬运时间也大幅缩短。但量化不是免费的,它引入了精度损失。
量化的本质是把浮点数映射到整数区间。以INT8为例,把[-127, 127]映射到浮点范围[min, max]。这个映射需要一个缩放因子(Scale)和一个零点(Zero Point)。缩放因子决定了量化精度,零点决定了量化区间的偏移。
问题在于,神经网络中不同层的激活值分布差异很大。有些层输出范围是[-1, 1],有些是[-100, 100]。如果统一用一套量化参数,小范围的层精度损失会很大。所以实际部署中,通常采用逐层量化(Per-Layer Quantization)甚至逐通道量化(Per-Channel Quantization)。
我做过一个对比实验,同一个MobileNetV2模型,在端侧NPU上:
| 量化策略 | Top-1精度 | 推理延迟 | 模型大小 |
|---|---|---|---|
| FP32 | 71.8% | 12.5ms | 14MB |
| 逐层INT8 | 70.2% | 4.1ms | 3.6MB |
| 逐通道INT8 | 71.1% | 4.3ms | 3.6MB |
逐通道量化精度更高,但推理稍慢,因为每个通道需要独立的缩放因子,增加了计算开销。实际选型时,如果精度敏感就选逐通道,如果速度敏感就选逐层。
3.2 量化感知训练与训练后量化的选择逻辑
量化有两种主要方式:训练后量化(PTQ)和量化感知训练(QAT)。PTQ直接对训练好的FP32模型做量化,简单快捷,但精度损失可能较大。QAT在训练过程中模拟量化误差,让模型适应量化,精度更好,但需要重新训练。
我的经验是,对于分类、检测这类对精度要求不是极端苛刻的任务,PTQ通常够用。但对于分割、超分辨率这类像素级任务,PTQ的精度损失可能无法接受,这时候就需要QAT。
QAT的实现也有讲究。不是所有层都适合量化,第一层和最后一层通常保持FP32,因为输入输出对精度更敏感。另外,某些激活函数(如Sigmoid、Tanh)的量化误差较大,可以考虑用ReLU替代或者做特殊处理。
3.3 混合精度:在NPU上平衡速度与精度
混合精度是量化的进阶玩法。不是所有层都用INT8,而是根据敏感度分析,对精度敏感的层保持FP16,其他层用INT8。这样可以在精度和速度之间找到更好的平衡点。
实现混合精度的关键是敏感度分析。具体做法是逐层量化,观察每一层量化后对最终精度的影响,然后选择影响最大的几层保持高精度。这个过程可以用工具自动化,但需要人工确认结果是否合理。
我在一个端侧人脸识别项目里用了混合精度,最终方案是:主干网络用INT8,最后的特征嵌入层用FP16。结果是模型大小只增加了8%,但识别准确率提升了2.3个百分点,推理延迟只增加了0.4ms。这个投入产出比是非常划算的。
4. 端侧部署中张量相关的典型问题与排查思路
4.1 张量形状不匹配导致的推理失败
这是端侧部署最常见的问题,没有之一。模型在训练框架里跑得好好的,转成端侧格式就报错,十有八九是张量形状问题。
常见原因包括:
- 动态形状不支持:很多NPU只支持静态形状,如果你的模型有动态维度(比如NLP模型中的序列长度),转换时就会失败。解决办法是固定形状,或者用多个静态形状的模型覆盖不同输入长度。
- 布局不一致:训练框架用NCHW,NPU期望NHWC,转换工具没有正确处理,导致形状对不上。
- 算子版本差异:不同版本的转换工具对同一算子的形状推导规则可能不同,升级工具后模型跑不了的情况我也遇到过。
排查这类问题的思路是:先确认输入张量的形状和布局,再逐层检查中间张量的形状,找到第一个不匹配的层。大多数转换工具都支持导出中间层的形状信息,善用这个功能能省很多时间。
4.2 内存溢出与张量分块策略调整
端侧设备内存有限,大模型或者高分辨率输入很容易导致内存溢出。表现可能是推理直接失败,也可能是推理过程中被系统杀掉。
解决内存问题的核心是张量分块。但分块策略需要根据具体硬件调整。我的一般流程是:
- 先确认NPU的SRAM容量和DDR可用内存
- 计算最大张量的内存占用
- 如果超过SRAM容量,设计分块方案
- 在分块基础上,考虑是否可以把部分权重常驻SRAM
有一个容易被忽略的点是内存对齐。很多NPU要求张量地址按特定字节对齐(比如16字节或64字节),如果不对齐,要么报错,要么性能下降。在手动分配内存时,一定要检查对齐要求。
4.3 性能不达预期的逐层分析方法
模型跑起来了,但速度不达预期,这时候需要逐层分析。我常用的方法是:
- 逐层计时:用NPU的性能分析工具,导出每一层的执行时间
- 找瓶颈层:通常瓶颈集中在卷积层和全连接层,但某些激活函数层也可能成为瓶颈
- 分析瓶颈原因:是计算量大,还是内存访问多,还是流水线停顿
我遇到过一个案例,某个深度可分离卷积层耗时异常。排查后发现,是因为通道数太少(只有8),NPU的向量化单元利用率极低。解决办法是把相邻的深度可分离卷积合并,或者调整通道数到16的倍数。
另一个常见问题是首层和末层的开销。首层通常要做布局变换和量化,末层要做反量化和后处理,这两层的开销在整体延迟中占比可能很高。优化方法是把部分预处理和后处理移到CPU上,或者用NPU的专用指令加速。
5. 从CPU到NPU:端侧AI硬件部署的选型逻辑
5.1 CPU、GPU、NPU在端侧的分工与边界
端侧AI硬件部署不是非此即彼的选择,而是要根据任务特点做分工。我的经验是:
- CPU:适合小模型、低频率任务、控制逻辑、后处理。优势是灵活,劣势是能效低。
- GPU:适合中等规模模型、图形相关任务、需要浮点精度的场景。优势是通用性强,劣势是功耗高。
- NPU:适合大规模卷积、矩阵运算、量化模型。优势是能效高,劣势是灵活性差。
实际产品中,通常是CPU+NPU或者CPU+GPU+NPU的组合。比如手机上的拍照场景,预处理用CPU,主体检测用NPU,后处理用GPU。这种异构计算的关键是任务划分和数据同步,划分不好反而会拖慢整体速度。
5.2 模型转换工具链的坑与应对
从训练框架到端侧NPU,中间要经过模型转换。这个环节的坑非常多,我列几个最常见的:
- 算子不支持:训练框架里的某些算子,NPU没有对应实现。解决办法是用NPU支持的算子重新表达,或者把该层放到CPU上执行。
- 转换工具版本不匹配:训练框架版本、转换工具版本、NPU驱动版本,三者之间可能有兼容性问题。我的建议是锁定版本,不要轻易升级。
- 量化校准数据问题:PTQ需要校准数据,如果校准数据分布和实际数据差异大,量化精度会很差。校准数据一定要有代表性。
提示:模型转换后,一定要用测试集验证精度。我见过太多转换后精度掉点但没被发现的情况,上线后问题才暴露出来。
5.3 端侧大模型部署的现实挑战
现在端侧大模型很热,但实际部署挑战很大。大模型的参数量动辄几十亿,即使量化到INT4,也要几GB内存,很多端侧设备根本放不下。
目前的现实方案是:
- 模型裁剪:去掉冗余层,或者用蒸馏得到小模型
- 参数共享:不同层共享权重,减少参数量
- 条件计算:根据输入动态选择激活的层,减少计算量
- 内存映射:把权重放在闪存里,按需加载,但延迟会增加
我实测过一个70亿参数的模型,INT4量化后约3.5GB,在高端手机上可以跑,但延迟在秒级,离实时还有距离。所以端侧大模型目前更适合对延迟不敏感的场景,比如离线翻译、文档摘要。
6. 实操建议与个人经验总结
6.1 端侧NPU算子开发的入门路径
如果你想做NPU算子开发,我的建议是从这几个方向入手:
- 先跑通官方Demo:每个NPU厂商都会提供算子开发示例,先把这些示例跑通,理解工具链的基本流程。
- 从简单算子开始:不要一上来就写卷积,先从Element-wise算子(如Add、Mul)开始,理解数据搬运和计算的基本模式。
- 学会看性能分析报告:NPU厂商通常提供性能分析工具,能告诉你每个算子的执行时间、内存访问量、流水线利用率。这是优化的基础。
- 理解硬件架构:不需要懂电路设计,但要理解计算单元的组织方式、内存层级、DMA通道数量这些关键参数。
6.2 性能调优的优先级排序
端侧性能调优,我的优先级排序是:
- 量化:收益最大,风险也最大,需要仔细验证精度
- 算子融合:收益中等,实现难度中等
- 内存布局优化:收益中等,但需要深入理解硬件
- 多核并行:收益取决于任务,实现难度较高
- 指令级优化:收益较小,但需要深厚的硬件知识
这个排序不是绝对的,具体项目要根据瓶颈所在调整。但一般来说,先做量化,再做融合,最后考虑并行和指令优化,是投入产出比最高的路径。
6.3 常见问题的快速排查清单
最后分享一个我常用的排查清单,遇到端侧部署问题可以按这个顺序检查:
- 模型转换是否成功?有没有警告信息?
- 输入张量的形状和布局是否正确?
- 量化校准数据是否有代表性?
- 内存是否足够?有没有溢出?
- 算子融合是否生效?
- 多核切分是否合理?
- 首层和末层的开销是否过大?
- 是否有算子回退到CPU执行?
这个清单能覆盖80%以上的常见问题。剩下的20%通常需要具体分析,但有了这个基础,排查方向不会跑偏。
我在实际项目中最大的体会是,端侧AI部署没有银弹,每个硬件平台都有自己的脾气。同样的模型,在A平台上跑得好,在B平台上可能完全不行。所以不要迷信任何"通用方案",一定要针对目标硬件做实测和调优。另外,精度和速度的权衡贯穿始终,没有既快又准的完美方案,只有适合当前场景的最优解。