
端侧AI这个词这两年出现的频率越来越高但很多人对它的理解还停留在把模型塞进手机里跑这个层面。真正做过端侧部署的人会告诉你事情远没有这么简单。一个模型从训练框架里导出到最终在设备上以可接受的延迟和功耗跑起来中间要经过图优化、算子融合、量化、内存布局调整、硬件指令映射等一长串环节。而这条链路的最底层就是张量如何在NPU上被真正执行的问题。我自己是从移动端推理优化开始接触这个领域的先后在几个不同架构的NPU上做过模型移植和性能调优。踩过的坑包括但不限于量化后精度崩了、算子不支持导致大量回退到CPU、内存带宽成为瓶颈导致算力利用率不到30%、不同厂商的NPU对同一张图的理解完全不同。这些问题追到根上都指向同一个东西——你得理解张量在NPU上的执行逻辑而不是把它当成一个黑盒。这篇文章适合谁看如果你正在做端侧AI的模型部署或者准备从纯算法转向推理优化又或者你只是好奇NPU到底是怎么算一个卷积的那接下来的内容应该能给你一些实在的参考。我会从张量的内存表示讲起一路拆到NPU的指令调度和常见性能陷阱尽量把这条链路讲透。1. 张量在端侧的真实形态不只是多维数组1.1 从框架张量到硬件张量的三次转换在PyTorch里一个张量就是一个多维数组加上一些元信息。但到了端侧这个数组要经历至少三次形态转换每一次都可能引入性能损耗。第一次转换发生在模型导出阶段。以ONNX为例PyTorch的NCHW布局会被保留但某些算子会被拆解或重组。比如一个普通的卷积在ONNX图里可能变成Conv加BN加ReLU三个节点也可能被融合成一个节点取决于导出时的配置。这一步的关键是你看到的图结构和硬件实际执行的图结构往往不是一回事。第二次转换发生在推理引擎的图优化阶段。以TensorRT、NCNN、MNN这类引擎为例它们会做算子融合、常量折叠、死代码消除等优化。这时候张量的形状可能会被改写比如把NCHW转成NHWC因为很多NPU的卷积加速器对NHWC更友好。这个转换不是免费的它涉及一次完整的内存重排。第三次转换发生在硬件层面。NPU通常有自己的张量描述格式比如某些架构要求张量按特定的tile大小对齐或者要求通道数补齐到某个倍数。如果你的张量形状不满足这些约束运行时就会插入额外的padding或slice操作。注意很多性能问题不是出在算力不够而是出在这三次转换中的某一次引入了额外的内存搬运。搬运一次张量的能耗可能比算一次卷积还高。1.2 内存布局为什么比算力更影响性能我做过一个实测同一个MobileNetV2模型在同一个NPU上只改变张量的内存布局推理延迟差了将近一倍。这不是个例而是端侧推理的常态。原因在于NPU的算力单元通常很快但内存带宽是有限的。如果张量的布局导致算力单元需要频繁地从DRAM里取数据那算力单元大部分时间都在等数据利用率自然上不去。这就是所谓的内存墙问题。具体来说NCHW布局下卷积核在通道维度上是连续的这对某些NPU的向量化指令很友好。但NHWC布局下空间维度连续对另一些NPU的DMA搬运更高效。选哪种布局取决于你的NPU架构。没有一个放之四海而皆准的答案。更麻烦的是一个模型里不同算子可能偏好不同的布局。卷积喜欢NHWC全连接喜欢NCHW矩阵乘又可能有自己的要求。推理引擎会在图优化阶段插入transpose节点来满足不同算子的需求但每一次transpose都是一次完整的内存读写。如果transpose太多性能就被吃掉了。我的经验是在模型设计阶段就尽量保持布局一致性避免频繁的维度变换。如果必须变换尽量让变换发生在通道数较小的张量上因为搬运成本和张量大小成正比。1.3 量化对张量表示的颠覆性改变量化是端侧部署绕不开的话题。从FP32到INT8张量的数值表示变了但更重要的是张量的内存组织和计算方式也变了。INT8量化后一个张量的每个元素只占1个字节理论上内存占用降到FP32的四分之一。但实际收益往往没有这么大因为量化会引入scale和zero_point这两个参数它们需要额外的存储和计算。而且很多NPU的INT8计算要求张量按通道或按组进行量化这意味着scale和zero_point的数量可能和通道数一样多。更关键的是量化改变了算子的执行方式。以卷积为例FP32卷积是乘加运算INT8卷积是整数乘加再乘scale。某些NPU有专门的INT8乘加指令能在一个周期内完成更多运算。但如果你的量化方案和NPU的指令不匹配比如用了per-channel量化但NPU只支持per-tensor那运行时就得插入额外的反量化操作性能反而下降。我踩过的一个坑是在一个NPU上做per-channel量化精度很好但推理速度比per-tensor量化慢了40%。后来查下来是因为这个NPU的INT8卷积指令只支持per-tensor的scaleper-channel的scale需要在软件层面处理引入了大量额外计算。所以量化方案的选择不能只看精度还要看硬件支持。2. NPU的算力从哪来架构差异决定执行逻辑2.1 三种主流NPU架构的算力组织方式市面上的NPU架构差异很大但大致可以归为三类 systolic array脉动阵列、SIMD向量机、以及混合架构。脉动阵列的代表是Google的TPU系列它的核心思想是让数据在计算单元之间流动每个计算单元只做简单的乘加但数据复用率极高。这种架构对矩阵乘和卷积非常友好因为这两类算子的数据复用模式很规整。但它的缺点是灵活性差遇到非规则算子比如某些注意力机制里的gather操作就无能为力。SIMD向量机的代表是高通的Hexagon DSP和很多移动端NPU。它的核心是一组向量寄存器和一个向量ALU能同时对多个数据做相同的操作。这种架构的灵活性比脉动阵列好能处理更多类型的算子但数据复用率不如脉动阵列对内存带宽更敏感。混合架构则是把两者结合起来比如用脉动阵列处理卷积和矩阵乘用向量机处理element-wise操作和激活函数。这种架构的编程模型更复杂但能覆盖更多场景。理解你的NPU属于哪一类直接决定了你应该怎么优化模型。脉动阵列上你要尽量把算子转成矩阵乘SIMD上你要尽量让数据在向量寄存器里复用混合架构上你要注意算子在不同计算单元之间的调度开销。2.2 数据流NPU执行一个卷积的完整链路让我用一个具体的例子来说明NPU执行一个卷积时发生了什么。假设有一个3x3的卷积输入是1x3x224x224输出是1x64x224x224INT8量化。第一步DMA把输入张量从DRAM搬到NPU的片上内存。这一步的耗时取决于张量大小和内存带宽。224x224x3大约是150KB如果内存带宽是10GB/s搬运时间大约是15微秒。第二步权重张量从DRAM搬到权重缓存。3x3x3x64大约是1.7KB搬运时间可以忽略。但如果是大模型权重搬运可能是主要开销。第三步计算单元开始执行乘加。脉动阵列会按行和列加载输入和权重每个周期完成一批乘加。3x3x3x64x224x224大约是2.7亿次乘加如果NPU有1024个乘加单元每个周期完成1024次那需要大约26万个周期。按1GHz主频算大约是260微秒。第四步结果写回DRAM。输出是1x64x224x224大约是3.2MB写回时间大约是320微秒。注意写回时间比计算时间还长。这就是为什么我说内存带宽往往是瓶颈。在这个例子里计算只占了不到一半的时间剩下的都是数据搬运。提示优化端侧推理时先看内存搬运时间再看计算时间。如果搬运时间占比超过50%那优化计算单元是没用的得先优化数据流。2.3 算子融合如何减少数据搬运算子融合是端侧推理优化最有效的手段之一它的核心目的就是减少数据搬运。以ConvBNReLU为例如果不融合执行流程是卷积输出写到DRAMBN从DRAM读卷积输出计算后写到DRAMReLU再从DRAM读BN输出计算后写到DRAM。三次读写每次都是完整的张量大小。如果融合成一个算子卷积的输出直接留在片上内存BN和ReLU在片上完成最后只写一次DRAM。数据搬运量降到原来的三分之一。但算子融合不是没有代价的。融合后的算子需要更多的片上内存来保存中间结果如果片上内存不够融合就会失败或者需要分块处理。分块处理又会引入额外的边界处理开销。我在一个NPU上做过测试把ResNet50的所有ConvBNReLU都融合推理延迟降低了35%。但把融合后的模型放到另一个片上内存较小的NPU上延迟反而增加了10%因为分块处理的开销超过了融合的收益。所以融合策略要针对具体硬件来定。3. 从张量到指令NPU的调度逻辑3.1 图编译NPU如何理解你的模型当你把一个模型交给NPU运行时第一件事是图编译。这个过程把框架层面的计算图转换成NPU能执行的指令序列。图编译的第一步是算子映射。NPU通常只支持有限的一组算子比如卷积、全连接、池化、激活等。如果你的模型里有NPU不支持的算子比如某些自定义的注意力机制编译器会把它标记为不支持然后回退到CPU执行。回退到CPU的算子会成为整个推理链路的瓶颈因为CPU和NPU之间的数据同步开销很大。第二步是内存分配。编译器会为每个张量分配片上内存或DRAM地址。片上内存有限通常只有几百KB到几MB所以编译器需要决定哪些张量放在片上哪些放在DRAM。这个决策直接影响性能。第三步是指令生成。编译器把每个算子转换成NPU的指令序列包括DMA指令、计算指令、同步指令等。指令的调度顺序会影响数据依赖和流水线效率。我遇到过一个典型问题模型里有一个reshape操作在框架层面是零成本的只是改变元信息但在NPU上reshape可能触发一次完整的内存重排。如果这个reshape恰好发生在两个大张量之间性能就会明显下降。解决办法是调整模型结构让reshape发生在通道数较小的张量上或者用view操作替代reshape。3.2 算子不支持的代价回退与重写算子不支持是端侧部署最常见的坑之一。不同NPU的支持列表差异很大同一个算子在这个NPU上支持在另一个上可能就不支持。回退到CPU的代价有多大我做过一个实测一个模型有20个算子其中19个在NPU上执行1个回退到CPU。NPU部分的执行时间是10毫秒CPU部分的执行时间是2毫秒但总推理时间是18毫秒。多出来的6毫秒是数据在NPU和CPU之间同步的开销。所以一个算子回退可能让整个推理链路的性能下降30%以上。解决办法有两个一是重写算子用NPU支持的算子组合来等价实现二是调整模型结构避免使用不支持的算子。重写算子的例子某些NPU不支持GELU激活函数但支持tanh和乘法。GELU可以近似为x * sigmoid(1.702 * x)而sigmoid可以用tanh表示。这样就能用NPU支持的算子组合出GELU的近似实现。精度损失通常在可接受范围内。调整模型结构的例子某些NPU不支持动态shape的算子但你的模型里有动态reshape。可以把动态reshape改成固定shape的reshape或者在模型设计阶段就避免动态shape。3.3 多核NPU的任务划分策略高端NPU通常有多个计算核心比如4核或8核。如何把模型划分到多个核心上是一个直接影响性能的问题。最简单的策略是按层划分前几层在一个核心上后几层在另一个核心上。但这种策略的问题是核心之间的数据同步开销很大而且负载可能不均衡。更好的策略是按通道或按空间划分把同一个算子的不同通道或不同空间区域分配到不同核心上。这样每个核心处理的数据量更均衡同步开销也更小。但这对算子的实现有要求不是所有NPU都支持。还有一种策略是流水线划分把模型分成多个阶段每个阶段在一个核心上执行阶段之间用流水线方式重叠。这种策略能提高核心利用率但需要编译器有较强的调度能力。我在一个4核NPU上做过测试按层划分的推理延迟是15毫秒按通道划分是11毫秒流水线划分是9毫秒。但流水线划分的编程复杂度最高而且对模型结构有要求。所以选择哪种策略要看你的模型和硬件特性。4. 端侧部署的实战陷阱与调优经验4.1 量化精度崩塌的排查链路量化精度崩塌是端侧部署最常见的问题。模型在FP32下精度正常量化后精度大幅下降。排查这个问题需要一套系统的方法。第一步定位是哪一层导致的精度下降。方法是逐层量化观察每一层输出的误差。如果某一层的误差突然变大那问题就出在这一层。第二步分析这一层为什么对量化敏感。常见原因有这一层的权重分布不均匀存在极端值这一层的输入动态范围很大这一层使用了对量化敏感的算子比如softmax或layer norm。第三步针对性处理。如果是权重分布问题可以用KL散度或最小化量化误差的方法来选择scale如果是输入动态范围问题可以用per-channel量化如果是对量化敏感的算子可以保留FP16精度或者用查表法替代。我遇到过一个案例一个模型的最后一层全连接对量化特别敏感量化后精度掉了5个百分点。后来发现是因为这一层的权重有一个很大的偏置项导致量化后的zero_point偏移。解决办法是把偏置项单独用FP16处理精度就恢复了。4.2 内存带宽瓶颈的识别与缓解内存带宽瓶颈的典型表现是NPU的算力利用率很低但推理延迟很高。用性能分析工具看会发现DMA的占用率很高而计算单元的占用率很低。识别内存带宽瓶颈的方法是计算模型的算术强度arithmetic intensity即每字节内存访问对应的计算量。如果算术强度低于NPU的算力带宽比那就是内存瓶颈。缓解内存带宽瓶颈的方法有几种。一是算子融合减少中间张量的读写。二是分块处理把大张量切成小块让小块能放在片上内存里减少DRAM访问。三是改变数据布局让连续访问更高效。四是降低精度比如从FP16降到INT8直接减少一半的内存流量。我在一个模型上做过对比不做任何优化时算力利用率是25%算子融合后提升到40%再加上分块处理提升到60%最后用INT8量化提升到75%。每一步的收益都很明显但每一步都需要针对具体硬件来调。4.3 不同NPU平台的移植经验对比我在几个不同平台上做过模型移植每个平台都有自己的特点。平台A的NPU对卷积支持很好但对全连接支持一般。移植时要把全连接尽量转成卷积比如用1x1卷积替代全连接。这个转换在数学上是等价的但需要调整权重布局。平台B的NPU对INT8支持很好但对FP16支持一般。移植时要尽量用量化感知训练让模型在量化后精度损失最小。这个平台上的调优重点是量化方案的选择。平台C的NPU对动态shape支持很好但对静态shape的优化不够。移植时要尽量保持动态shape让编译器有更多优化空间。但这个平台的编程模型比较复杂需要更多的手动调优。跨平台移植的通用经验是不要假设一个平台上的优化策略在另一个平台上有效。每个平台都要重新做性能分析重新找瓶颈重新调优。移植的成本往往比预期的高。4.4 端侧AI硬件的选型考量选端侧AI硬件时不能只看算力参数。算力只是理论峰值实际能用到多少取决于内存带宽、算子支持、编译器质量等多个因素。我的选型框架是先看算子支持列表确保模型的主要算子都被支持再看内存带宽和片上内存大小评估是否能避免内存瓶颈然后看编译器的优化能力比如是否支持算子融合、是否支持自动分块最后看工具链的成熟度比如是否有性能分析工具、是否有量化工具。算力参数可以作为参考但不能作为唯一依据。我见过算力很高但实际性能很差的NPU也见过算力一般但实际性能很好的NPU。差距就在软件栈上。还有一个容易被忽略的因素是功耗。端侧设备的功耗预算通常很紧NPU的峰值功耗可能很高但持续功耗才是关键。有些NPU在短时间爆发时性能很好但持续运行时会因为散热问题降频。选型时要看持续性能而不是峰值性能。5. 端侧AI执行逻辑的演进方向5.1 从固定算子到可编程数据流当前的NPU大多采用固定算子架构编译器把模型映射到一组预定义的算子上。这种架构的优点是效率高缺点是灵活性差遇到不支持的算子就得回退。未来的趋势是可编程数据流架构NPU不再有固定的算子概念而是由编译器生成数据流图硬件按数据流执行。这种架构能支持任意算子但编译器的复杂度会大幅增加。已经有公司在做这方面的探索比如用CGRA粗粒度可重构阵列来实现可编程数据流。这种架构在灵活性和效率之间找到了一个平衡点但编程模型和工具链还需要完善。5.2 动态shape与自适应推理端侧AI的一个趋势是动态shape和自适应推理。比如在视频处理中每一帧的感兴趣区域可能不同需要动态调整计算量。又比如在语音处理中不同长度的音频需要不同的计算图。当前的NPU对动态shape的支持普遍不好很多编译器要求静态shape。未来的NPU需要更好地支持动态shape包括动态内存分配、动态指令调度等。自适应推理是另一个方向模型根据输入难度动态调整计算量。比如简单样本走浅层网络复杂样本走深层网络。这需要NPU支持条件执行和动态控制流。5.3 端侧训练与推理的融合当前端侧AI主要是推理训练还是在云端。但有些场景需要在端侧做微调比如个性化推荐、隐私保护等。这要求NPU不仅支持推理还支持训练。端侧训练的挑战更大因为训练需要反向传播、梯度更新等操作计算量和内存需求都更高。当前的NPU大多不支持训练或者只支持很有限的训练。未来的NPU需要更好地支持训练包括混合精度训练、梯度压缩等。我在实际项目中的体会是端侧AI的底层执行逻辑本质上是一个软硬件协同设计的问题。你不能只关注算法也不能只关注硬件必须两边都懂才能做出真正高效的方案。这个领域还在快速演进新的架构、新的编译器、新的优化技术不断出现保持学习是必须的。