
1. 为什么要在K230上折腾AI部署第一次拿到K230开发板的时候我脑子里想的其实很简单这玩意儿带NPU算力标称6TOPS功耗又低能不能把我自己训练的一个小模型塞进去跑起来结果从模型训练到真正在板子上跑通中间踩的坑比我想象的多得多。这篇文章就是把我整个流程复盘一遍从数据集准备、模型训练、量化转换到最终在K230上部署推理每一步都尽量写清楚为什么这么做、当时遇到了什么问题、怎么解决的。K230是嘉楠科技推出的一款边缘计算芯片核心卖点就是内置了NPU神经网络处理单元专门用来加速神经网络推理。它跟纯CPU跑推理完全不是一个概念——CPU跑一个ResNet34可能一帧要几百毫秒甚至更久而NPU可以把同样的模型压到几十毫秒甚至更低。这个差距在边缘设备上非常关键因为边缘设备通常功耗受限、散热受限不可能靠堆CPU核心来换性能。那什么人适合看这篇内容如果你手头有K230开发板想把自己训练的模型部署上去或者你在做端侧AI硬件部署的选型想了解从训练到部署的完整链路又或者你只是好奇NPU到底怎么用起来那这篇应该都能给你一些参考。我默认你有基本的Python和深度学习基础但不需要你之前接触过K230或者任何NPU相关的开发。整个流程我分成四大块模型训练、模型转换与量化、K230环境搭建与部署、以及实际运行中的问题排查。每一块我都会把关键决策点的理由讲清楚因为很多教程只告诉你“这么做”但不告诉你“为什么这么做”导致换个模型就不知道怎么改了。2. 模型训练从数据集到可用的ResNet342.1 为什么选ResNet34而不是更大的模型在边缘设备上部署模型第一原则是“模型够用就好不要贪大”。我一开始想的是用ResNet50甚至更大的模型但后来算了一下K230的NPU算力和内存限制果断退回到ResNet34。ResNet34的参数量大约是21M浮点模型大小在80MB左右经过量化后可以压到20MB以内这对K230的内存来说比较友好。另外ResNet34的结构比较规整没有太多特殊算子这对NPU的算子支持比较友好。如果你用一个包含大量自定义算子或者动态shape的模型NPU很可能不支持最后只能回退到CPU跑那就失去意义了。所以选模型的时候除了看精度还要看它的算子是否“常规”。我这次的任务是一个图像分类任务类别数不多大概10类左右。数据集是我自己采集的每类大概500张图片总共5000张左右。这个数据量不算大所以训练的时候用了比较强的数据增强包括随机裁剪、翻转、颜色抖动等。2.2 训练环境的搭建与关键参数训练我是在一台带GPU的机器上做的用的是PyTorch。这里有个坑要注意K230的NPU工具链对PyTorch版本和算子版本有一定要求不是随便什么版本都能顺利转换。我建议用PyTorch 1.12或者1.13太新的版本可能在转换时遇到不支持的算子。训练脚本本身没什么特别的标准的ResNet34训练流程。但有几个参数我调了比较久学习率用了余弦退火初始学习率0.01因为数据集不大太大会震荡。Batch size32再大显存不够再小训练不稳定。Epoch大概跑了80个epoch因为数据量小容易过拟合所以加了早停。权重衰减1e-4防止过拟合。训练完之后验证集精度大概在92%左右。这个精度对于实际应用来说够用了但我知道量化之后精度会掉一些所以留了一些余量。注意训练的时候最好把模型保存成ONNX格式因为K230的工具链通常是从ONNX开始转换的。PyTorch直接转ONNX的时候要注意opset版本我用的opset 11比较稳。2.3 导出ONNX时的常见坑导出ONNX这一步看起来简单但实际上很容易出问题。我遇到的主要有这几个第一个是动态shape的问题。训练的时候如果用了动态输入尺寸导出ONNX时可能会带上动态维度而K230的NPU通常要求固定输入尺寸。所以导出的时候要指定固定的输入尺寸比如1x3x224x224。第二个是算子不支持。有些PyTorch算子转ONNX之后会变成自定义算子NPU不认。我建议导出之后用ONNX Runtime跑一下确认推理结果和PyTorch一致然后再看ONNX的算子列表确认没有奇怪的算子。第三个是预处理和后处理的处理。很多人会把预处理比如归一化也放进模型里但这样会增加NPU的负担而且有些预处理算子NPU不一定支持。我的做法是把预处理放在CPU上做模型只负责纯推理部分。导出ONNX的代码大概长这样import torch import torch.onnx model ResNet34(num_classes10) model.load_state_dict(torch.load(best.pth)) model.eval() dummy_input torch.randn(1, 3, 224, 224) torch.onnx.export( model, dummy_input, resnet34.onnx, opset_version11, input_names[input], output_names[output], dynamic_axesNone )导出之后我建议用Netron打开ONNX文件看一眼确认输入输出和算子都正常。3. 模型转换与量化让模型适配NPU3.1 K230的工具链长什么样K230的模型转换工具链主要是基于嘉楠自己的NNCase编译器。NNCase负责把ONNX模型转换成K230 NPU能执行的kmodel格式。这个过程包括算子映射、量化、内存分配等步骤。NNCase的安装我就不细说了官方文档有。但要注意版本匹配NNCase的版本要和K230的固件版本对应不然可能出现转换成功但板子上跑不了的情况。我用的NNCase版本是1.x具体版本号建议去官方社区确认。转换的基本流程是ONNX - NNCase IR - 量化 - kmodel。其中量化是最关键的一步直接决定精度和性能。3.2 量化到底在做什么量化简单说就是把浮点权重和激活值用低比特整数表示通常是int8。这样做的好处是模型体积缩小4倍推理速度提升功耗降低。但代价是精度会掉。K230的NPU支持int8量化也支持混合量化部分层int8部分层int16。我这次用的是全int8量化因为ResNet34对这种量化比较鲁棒。量化需要一个校准数据集用来统计激活值的分布确定量化参数scale和zero point。校准数据集不需要标签只需要输入图片通常从训练集里随机抽几百张就够了。我用了大概200张。注意校准数据集的质量很重要。如果校准集和实际推理时的数据分布差异大量化后的精度会掉得很厉害。所以校准集最好能覆盖实际场景的各种情况。3.3 转换脚本与参数说明NNCase的转换脚本大概长这样import nncase # 加载ONNX模型 with open(resnet34.onnx, rb) as f: model_content f.read() # 编译配置 compile_options nncase.CompileOptions() compile_options.target k230 compile_options.input_shape [1, 3, 224, 224] compile_options.input_type uint8 compile_options.input_range [0, 255] compile_options.mean [0.485, 0.456, 0.406] compile_options.std [0.229, 0.224, 0.225] compile_options.quant_type uint8 # 量化配置 ptq_options nncase.PTQTensorOptions() ptq_options.samples_count 200 ptq_options.set_tensor_data(calib_data) # 编译 compiler nncase.Compiler(compile_options) compiler.import_onnx(model_content) compiler.use_ptq(ptq_options) compiler.compile() kmodel compiler.gencode_tobytes() with open(resnet34.kmodel, wb) as f: f.write(kmodel)这里有几个参数需要解释input_type和input_range指定输入数据的类型和范围。如果输入是uint8的图片范围就是0-255。mean和std归一化参数。这里有个坑如果你在训练时用了归一化这里也要对应设置但要注意NNCase的归一化是在NPU内部做的所以你的输入应该是原始图片数据不需要在CPU上再做归一化。quant_type量化类型uint8或者int8。K230通常用uint8。转换完成后你会得到一个kmodel文件这个就是最终要放到板子上跑的文件。3.4 量化精度掉了怎么办我第一次转换完之后在PC上模拟跑了一下发现精度从92%掉到了85%左右。这个掉得有点多所以我做了一些调整第一个是增加校准样本数量从200增加到500精度回升到88%左右。第二个是调整量化算法NNCase支持不同的量化校准方法比如KLD、MSE等。我试了MSE比默认的好一些。第三个是对某些敏感层使用混合量化。ResNet34的第一层和最后一层对量化比较敏感我把这两层设成int16其他层保持int8精度回到了90%左右。如果这些都不行那就只能考虑量化感知训练QAT在训练时就模拟量化过程让模型适应量化误差。但QAT比较麻烦我这次没用到。4. K230环境搭建与模型部署4.1 固件烧录与系统启动K230开发板拿到手之后第一件事是烧录固件。官方提供了固件包和烧录工具通常是通过USB线连接板子和PC然后用烧录工具把固件写到板子的存储里。烧录的时候要注意几点固件版本要和NNCase版本匹配不然kmodel可能跑不了。烧录前要确保板子进入烧录模式通常是按住某个按键再上电。烧录完成后要重新上电让系统正常启动。系统启动后你可以通过串口或者USB网络连接到板子。我习惯用串口因为最稳定。串口通信的波特率通常是115200用minicom或者PuTTY都可以。4.2 把kmodel传到板子上板子上的系统通常是一个精简的Linux有基本的文件系统。你可以通过scp或者U盘把kmodel文件传上去。我一般用scp因为方便scp resnet34.kmodel root192.168.1.100:/root/传上去之后你需要一个推理程序来加载kmodel并执行推理。K230的SDK里通常有示例代码你可以基于示例改。4.3 推理程序的编写推理程序的核心是调用K230的运行时API加载kmodel输入数据获取输出。大概流程是初始化运行时加载kmodel设置输入数据执行推理获取输出这里有个关键点输入数据的预处理。前面说了归一化是在NPU内部做的所以你在CPU上只需要把图片resize到模型输入尺寸然后转换成uint8数组就行。推理程序的代码大概长这样基于官方示例改的from k230_runtime import Runtime rt Runtime() rt.load_model(resnet34.kmodel) # 读取图片并resize img preprocess(test.jpg, (224, 224)) # 设置输入 rt.set_input(0, img) # 执行推理 rt.run() # 获取输出 output rt.get_output(0) pred output.argmax() print(Predicted class:, pred)实际代码会比这个复杂一些因为要处理内存分配、多线程等但核心逻辑就是这样。4.4 性能实测与对比部署完成之后我测了一下推理速度。单帧推理时间大概在15ms左右也就是大约60fps。这个速度对于很多实时应用来说已经够了。对比一下如果我用CPU跑同样的模型单帧大概要200ms以上差距非常明显。而且NPU的功耗比CPU低很多这对边缘设备来说很重要。不过要注意这个15ms是纯推理时间不包括预处理和后处理。如果预处理比较重整体延迟会增加。所以实际应用中要优化整个pipeline不能只看推理时间。5. 常见问题与排查技巧实录5.1 模型转换失败怎么办转换失败是最常见的问题原因通常有几个ONNX算子不支持用Netron看一下ONNX的算子列表如果有一些奇怪的算子尝试用ONNX Simplifier简化一下或者替换成支持的算子。输入shape不匹配确认ONNX的输入shape和转换配置里的input_shape一致。NNCase版本不匹配确认NNCase版本和固件版本对应。我遇到过一次转换失败最后发现是ONNX里有一个Resize算子NNCase不支持。解决办法是把Resize操作从模型里拿出来放到CPU上做。5.2 板子上跑推理报错板子上跑推理报错常见的有kmodel加载失败通常是kmodel文件损坏或者版本不匹配。重新转换一次确认版本对应。内存不足K230的内存有限如果模型太大或者输入尺寸太大可能内存不够。尝试减小输入尺寸或者用更小的模型。输入数据格式不对确认输入数据的类型和范围与转换配置一致。5.3 精度不达预期精度问题通常出在量化上。排查思路是先在PC上用NNCase的模拟器跑一下量化后的模型看精度掉了多少。如果掉得不多1-2%可能是正常的量化误差。如果掉得很多检查校准数据集是否覆盖了实际场景。尝试增加校准样本、调整量化算法、或者对敏感层用混合量化。5.4 常见问题速查表问题可能原因解决方法转换失败算子不支持简化ONNX或替换算子转换失败shape不匹配检查input_shape配置加载kmodel失败版本不匹配确认NNCase和固件版本推理报错内存不足减小模型或输入尺寸精度掉太多量化误差大增加校准样本或混合量化推理速度慢回退到CPU检查算子是否被NPU支持5.5 一些实操心得最后分享几个我在实际操作中总结的小技巧第一个是先用小模型跑通流程。不要一上来就用大模型先用一个简单的模型比如MobileNet把整个流程跑通确认环境没问题再换大模型。这样可以快速定位问题是出在流程上还是模型上。第二个是保留浮点模型作为对照。在PC上跑浮点模型的结果要保存下来部署到板子上之后用同样的输入对比输出确认精度差异。第三个是注意温度。K230在长时间高负载运行时会发热温度过高可能会降频。如果做长时间推理最好加个散热片。第四个是串口通信的稳定性。如果用串口调试注意波特率和流控设置不然可能丢数据。我一般用115200波特率不开流控。整个流程走下来从模型训练到K230上跑通大概花了我一周多的时间其中大部分时间花在量化调优和问题排查上。但跑通之后看到模型在板子上实时推理的效果还是很有成就感的。如果你也在做类似的事情希望这篇内容能帮你少踩一些坑。