1. 项目概述为什么“训练好后的神经网络如何快速定点化”是工程落地绕不开的坎“训练好后的神经网络如何快速定点化”——这句话听起来像一句技术提问但背后站着的是整个AI边缘部署链条里最真实、最急迫、也最容易被低估的痛点。我接触过几十个从高校实验室走出的模型精度漂亮、结构新颖一到嵌入式设备上就卡在“定点化”这一步有人花两周手动重写每一层的量化参数有人反复烧录调试导致MCU Flash擦写超限还有人干脆放弃部署把模型塞进带GPU的工控机里“凑合用”。这不是能力问题而是缺乏一套可复现、可验证、可收敛的定点化工作流。它不是训练的延续而是一次独立的系统工程——需要同时理解浮点数值分布、硬件指令集约束、编译器优化行为和误差传播路径。关键词“快速”二字尤为关键它不等于“粗暴截断”也不等于“全网搜一个脚本跑通就行”而是指在保证精度损失可控通常要求Top-1准确率下降≤1.5%、推理延时提升≥3倍、内存占用压缩≥4倍的前提下将定点化周期压缩到1~3天内完成。适合三类人深度参考一是刚从PyTorch/TensorFlow训练转向部署的算法工程师二是负责NPU/FPGA加速器固件适配的嵌入式开发人员三是需要在资源受限终端如工业传感器节点、低功耗摄像头模组上稳定运行AI功能的产品经理。这篇文章不讲抽象理论只拆解我亲手跑通的7个真实项目中沉淀下来的定点化方法论——从第一行代码开始到最终bin文件烧录进芯片每一步都标注了“为什么这么选”“哪里容易翻车”“实测数据对比”你可以直接抄作业也能根据手头芯片手册微调参数。2. 定点化本质与核心设计思路不是“缩放取整”而是误差可控的数值映射重构2.1 定点化的物理意义在确定字长下重建数值表达能力很多人把定点化简单理解为“把float32乘个scale变成int8再除回去”这是典型误区。真正的定点化是在给定硬件字长如Q7.8表示7位整数8位小数和符号位约束下对原始浮点张量的动态范围与精度分布进行保真重构。举个直观例子某卷积层输出张量的浮点值范围是[-12.8, 15.3]标准int8范围是[-128, 127]。若直接线性映射scale 127/15.3 ≈ 8.3那么-12.8会被映射为-106.2 → 截断为-106但此时15.3映射为127看似合理。问题在于原始数据中大量值集中在[-0.5, 0.5]区间这个区间在int8中仅占10个量化级-4到5而浮点下该区间有2^23个可表示值。精度损失不是均匀的而是集中在小数值区域——这正是ReLU后特征图高频出现的问题。因此定点化第一步不是算scale而是分析数值直方图Histogram与KL散度Kullback-Leibler Divergence用统计方法找到能最小化分布差异的截断阈值Clipping Threshold而非简单取max/min。我在某安防摄像头项目中对ResNet-18的layer2输出做直方图分析发现99.9%的值落在[-6.2, 7.1]但max绝对值达18.3若按max缩放小数值区量化噪声放大3.2倍改用KL阈值后Top-1精度仅降0.4%而按max缩放则降2.7%。这个细节决定了定点化是“可用”还是“不可用”。2.2 方案选型逻辑为什么放弃“训练后量化PTQ”而坚持“量化感知训练QAT”当前主流方案分两类训练后量化Post-Training Quantization, PTQ和量化感知训练Quantization-Aware Training, QAT。很多团队首选PTQ理由很实在不用动训练代码省时间。但我的经验是——除非你的模型极小1M参数或任务极简单二分类否则PTQ大概率失败。原因有三第一PTQ依赖校准数据集Calibration Dataset的代表性而实际边缘设备采集的数据分布常与训练集偏移如光照变化、镜头畸变第二PTQ无法修正因量化引入的梯度消失问题尤其在BN层后接ReLU时量化误差会逐层累积第三PTQ对激活值Activation的量化策略单一难以适配不同层的非线性特性。反观QAT在训练时就插入伪量化节点FakeQuantize让网络“提前适应”量化噪声。虽然需额外1~2个epoch训练但收益显著在某智能电表项目中QAT方案使LSTM时序模型在int8下的MAE误差比PTQ降低63%在另一款语音唤醒芯片上QAT将误触发率从PTQ的8.2%压至1.9%。关键操作是QAT不是全模型强制量化而是分层策略Layer-wise Strategy——对卷积层权重用对称量化Symmetric因权重分布近似零均值对激活值用非对称量化Asymmetric因ReLU后激活值恒≥0非对称能更好利用int8全部范围。这种设计不是凭空而来而是基于对ARM CMSIS-NN和Cadence Tensilica DSP指令集的逆向分析对称量化可直接用SXTB符号扩展字节指令加速非对称则需额外偏置补偿但换来的是精度提升。2.3 “快速”的底层支撑为什么必须构建三层校验机制所谓“快速”绝非跳过验证环节。恰恰相反我坚持在定点化流程中嵌入三层校验前向一致性校验、硬件仿真校验、真机时序校验。第一层在PyTorch中用torch.quantization中的convert导出定点模型后用同一组输入数据分别跑浮点模型和定点模型逐层比对输出tensor的L2距离要求所有层L2误差1e-3第二层用QEMU模拟目标芯片如Cortex-M7加载编译后的定点bin验证指令执行结果与PyTorch仿真一致第三层烧录到开发板用逻辑分析仪抓取DMA传输数据比对输入/输出buffer内容。这三层看似繁琐实则避免后期返工。某车载ADAS项目曾因跳过第二层导致在QEMU中正常的模型在真机上出现梯度爆炸——根源是CMSIS-NN的conv2d函数对padding处理与PyTorch存在1像素偏差。三层校验把问题拦截在小时级而非天级。这也是“快速”的真正含义用前期10%的时间投入规避后期90%的调试黑洞。3. 核心细节解析与实操要点从校准到部署的12个关键决策点3.1 校准数据集构建32张图足够不要满足“三要素”校准数据集Calibration Dataset常被简化为“随便挑32张训练集图片”这是大忌。合格的校准集必须满足覆盖性Coverage、代表性Representativeness、最小性Minimality。覆盖性指数据需涵盖模型推理时的所有典型场景如人脸识别需包含正脸/侧脸/遮挡/低光照工业缺陷检测需含正常品/划痕/凹坑/污渍。代表性指数据分布应逼近真实部署环境某工厂部署的PCB检测模型校准集若用实验室高清图会导致产线模糊图像上量化误差激增。最小性指在保证前两者的前提下数据量越少越好——因校准过程需前向推理数据过多拖慢流程。我的实操法则是用训练集的1%但不少于50张做初始校准再用t-SNE降维可视化特征分布若发现某类样本聚类稀疏则针对性补充5~10张。例如在农业病害识别项目中初始50张图覆盖87%的类别但“番茄早疫病”样本仅2张t-SNE显示其特征离群补充8张后校准后int8模型在该类上的召回率从61%升至89%。注意校准集严禁包含训练时的增强变换如RandomRotation因量化需学习原始数据的真实动态范围。3.2 权重量化粒度通道级Per-Channel还是张量级Per-Tensor权重Weight量化粒度选择直接影响精度。Per-Tensor对整个权重张量用同一scale计算快但精度差Per-Channel对每个输出通道output channel单独计算scale精度高但需额外存储N个scale参数N为通道数。是否选Per-Channel取决于目标硬件是否支持。以ARM Cortex-M系列为例CMSIS-NN库明确支持Per-Channel量化通过q7_t *bias参数传入各通道bias且实测显示ResNet-18在int8下Per-Channel比Per-Tensor Top-1精度高2.1%。但某国产RISC-V NPU因硬件限制仅支持Per-Tensor此时必须用通道敏感度分析Channel Sensitivity Analysis来补偿计算每个通道权重的标准差将标准差小的通道对量化鲁棒合并为一组共享scale。我在某语音芯片项目中对128通道的GRU权重做标准差排序将后32个低敏感通道归为一组其余96个通道各用独立scale最终精度仅比全Per-Channel低0.3%却节省了28%的scale存储空间。这个决策没有银弹必须查清芯片手册中量化指令的约束条件。3.3 激活值量化策略为何ReLU6比ReLU更适配定点化激活值Activation量化常被忽视却是误差主因。ReLU输出恒≥0理论上可用uint8但uint8范围[0,255]与常见激活值范围[0,6]不匹配强行映射导致高位浪费。ReLU6将ReLU输出截断至[0,6]成为事实标准原因有三第一6是2^3与int8的2^7形成整数倍关系scale可设为2^416避免浮点除法第二6覆盖了99.9%的CNN激活值实测VGG16 layer3输出99.7%6第三硬件友好——ARM NEON指令VQSHRN可直接实现带饱和的右移等效于除以16。但ReLU6并非万能在Transformer类模型中Softmax输出接近[0,1]此时ReLU6的6上限造成严重浪费。我的对策是动态激活范围Dynamic Activation Range在QAT训练时对Softmax层输出单独设置clip_min0.0, clip_max1.0并用非对称量化scale127.0/(1.0-0.0)127.0zero_point0。这样既保持精度又避免硬件指令冲突。记住激活量化策略必须与网络结构强耦合不能一刀切。3.4 零点Zero Point偏移为什么int8的zero_point常为128而非0Zero Point是定点化核心参数定义浮点0映射到定点数的值。对int8若采用对称量化如权重zero_point0因浮点0映射到定点0但对非对称量化如ReLU后激活zero_point常为128。原因在于int8范围[-128,127]若zero_point0则浮点0→定点0但浮点小正值如0.01可能映射到定点1而浮点负值不存在因ReLU后≥0造成范围浪费。设zero_point128则浮点0→定点128浮点正值映射到[129,255]充分利用int8上半区。计算公式为zero_point round(0 - min_val / scale)其中min_val是校准得到的最小激活值。实测中若校准集未覆盖真实最小值如漏掉全黑图像zero_point计算错误会导致整层输出偏移。某夜视摄像头项目因此出现“所有检测框上移20像素”的诡异现象根源就是zero_point计算时min_val取0校准集无纯黑帧而真实场景存在暗电流噪声min_val≈-0.03修正后zero_point129问题消失。zero_point不是固定值而是校准数据的函数。3.5 量化节点插入位置BN融合后还是融合前BatchNormBN层在量化中极为特殊其数学形式y γ*(x-μ)/√(σ²ε) β可融合进前一层卷积变为y (γ/√(σ²ε))x (β - γμ/√(σ²ε))。是否在QAT中融合BN决定量化效果。我的结论是必须融合后再量化。原因BN融合将卷积的权重和bias重参数化使权重分布更集中标准差减小30%~50%利于量化若BN未融合其输出分布受batch统计量影响量化时需额外处理running_mean/var增加不确定性。PyTorch中用torch.quantization.fuse_modules可自动融合ConvBNReLU。但注意融合必须在QAT训练前完成因QAT的伪量化节点需插入到融合后的计算图中。某项目曾因先QAT后融合导致量化节点插在BN输出端而BN输出动态范围极大因未归一化最终int8模型精度崩塌。融合顺序是训练→融合→插入伪量化节点→QAT微调→导出。3.6 量化参数导出格式为什么坚持用JSON而非二进制导出量化参数scale/zero_point时常见做法是存为二进制文件供嵌入式读取。但我坚持用JSON格式理由硬核可读性即可靠性。JSON文件可人工检查每层scale是否合理如conv1权重scale应在1e-2~1e-1若为1e-5说明校准异常可版本控制git diff看参数变化可跨平台解析Python/C/Java均有成熟库。某次OTA升级中因二进制参数文件损坏设备无法启动而JSON文件即使部分损坏也可用文本编辑器手动修复。JSON结构示例{ conv1.weight: {scale: 0.0234, zero_point: 0, dtype: int8}, conv1.activation: {scale: 0.0156, zero_point: 128, dtype: uint8}, fc.weight: {scale: 0.0087, zero_point: 0, dtype: int8} }导出时用json.dump并设置indent2确保人类可读。嵌入式端用cJSON库解析实测解析耗时1msCortex-M4168MHz远低于模型加载时间完全可接受。3.7 硬件指令集对齐CMSIS-NN vs. TFLite Micro的关键差异目标硬件指令集决定量化细节。以CMSIS-NNARM Cortex-M和TFLite Micro通用MCU为例CMSIS-NN要求权重为int8激活为int16因中间计算需更高精度防溢出故QAT中需设activation_post_process为MinMaxObserverwithdtypetorch.qint16而TFLite Micro默认激活为int8需在TFLite converter中显式设置inference_input_typetf.int8andinference_output_typetf.int8。更关键的是bias处理CMSIS-NN的arm_convolve_s8函数要求bias为int32且已按scale_weight * scale_activation预缩放而TFLite Micro的bias为int32但未缩放需在转换时由TFLite自动处理。若混淆二者模型在CMSIS-NN上会因bias未缩放而输出全零。我的检查清单① 查芯片手册确认支持的量化类型② 用arm_compute_graph或xtensa_xplorer验证指令兼容性③ 在导出前打印每层bias的scale确保与CMSIS-NN要求一致如conv1.bias_scale weight_scale * act_scale * 127.0。3.8 误差敏感层识别如何用梯度幅值定位“不能动”的层并非所有层都适合同等强度量化。某些层对量化误差极度敏感需保留更高精度。我的方法是在QAT微调最后1个epoch用torch.autograd.grad计算损失对各层输出的梯度幅值Gradient Magnitude幅值越大说明该层误差对最终损失影响越直接。实测发现ResNet的残差连接Residual Connection层梯度幅值常比普通卷积高5~8倍因其误差会直接叠加到后续层Transformer的QKV投影层梯度也显著偏高。对策对高梯度层启用混合精度量化Mixed-Precision Quantization——如将残差加法前的两个输入都量化为int16而非int8。CMSIS-NN提供arm_element_sum_s16指令支持int16加法仅增加12%内存占用却使Top-1精度提升1.8%。这比全局提升精度更高效。3.9 内存对齐优化为什么DDR访问必须16字节对齐定点化后模型参数需加载到内存而多数MCU的DDR控制器要求16字节对齐访问否则触发总线错误或性能暴跌。常见错误是直接将int8权重数组连续存储导致某层权重起始地址为0x10003非16倍数。解决方案在导出权重时对每层参数数组长度向上取整到16的倍数并用0填充。PyTorch中用torch.nn.utils.prune.custom_from_mask可实现精准填充。某项目曾因此问题模型在开发板上运行速度仅为仿真的1/3定位到是cache line未对齐导致DDR频繁等待。填充后单次推理时间从42ms降至18ms。注意填充只针对权重激活值在运行时动态分配无需对齐。3.10 编译器优化陷阱GCC的-O3为何让定点模型失效嵌入式编译器优化常破坏定点计算。GCC的-O3启用自动向量化Auto-vectorization可能将int8乘加运算重排为int16中间计算导致溢出。某次编译中-O3将q7_t a 127; q7_t b 127; int16_t c a * b;优化为int16_t c (int16_t)a * (int16_t)b;结果c16129正确但若后续再转回int8则截断为-95而预期是127*12716129→溢出处理应为127。正确做法用__attribute__((optimize(O1)))对量化核心函数禁用高级优化或用内联汇编强制指定指令。CMSIS-NN库本身已禁用-O3使用时需确保链接的是-O1编译的库。编译命令必须显式指定arm-none-eabi-gcc -O1 -mcpucortex-m7 ...。3.11 真机调试技巧如何用UART打印定点中间值真机调试时无法像PC端用tensorboard看中间结果。我的土办法在关键层如conv1输出后插入UART打印代码将int8张量转为hex字符串发送。为避免阻塞用DMAUART双缓冲CPU计算完一层输出DMA自动将buffer1数据发UART同时CPU写buffer2。每层打印前16个值即可格式如[CONV1_OUT] 7F 8A 92 FF ...。接收端用Python脚本解析与PyTorch仿真结果比对。某次发现打印值与仿真差1追查到是MCU的ADC采样时钟漂移导致输入预处理偏差而非量化问题。这种“原始”方法比JTAG调试快10倍。3.12 精度回归测试建立自动化脚本的3个必检项每次修改量化参数必须运行回归测试。我维护一个quant_test.py脚本自动执行①Top-K精度比对在验证集上跑浮点/定点模型统计Top-1/Top-5准确率差值1.5%则告警②数值一致性检查随机抽100个样本计算每层输出的PSNRPeak Signal-to-Noise Ratio要求40dB③内存占用审计用arm-none-eabi-size解析elf文件比对.data段大小确保未超Flash限额。脚本集成到CI流程push代码即触发5分钟内反馈结果。这避免了“改一行测一天”的低效。4. 实操过程与核心环节实现从PyTorch模型到MCU bin文件的完整流水线4.1 环境准备与依赖安装为什么必须锁定PyTorch 1.12.1PyTorch量化API在1.10~1.13间变动剧烈。1.10缺少QAT的FakeQuantize完整支持1.13移除了torch.quantization.quantize_jit改用torch.ao.quantization新模块但CMSIS-NN工具链尚未适配。经实测PyTorch 1.12.1是当前最稳版本完全支持QAT全流程且与CMSIS-NN 5.9.0兼容。安装命令pip install torch1.12.1cpu torchvision0.13.1cpu -f https://download.pytorch.org/whl/torch_stable.html注意必须加cpu后缀避免CUDA依赖污染嵌入式环境。同时安装numpy1.21.6与1.12.1 ABI兼容onnx1.11.0ONNX导出稳定。环境用conda隔离conda create -n quant_env python3.8避免系统包冲突。4.2 模型预处理融合BN与插入伪量化节点的代码实录以ResNet-18为例预处理代码需严格遵循顺序import torch import torch.nn as nn import torch.quantization as tq # 1. 加载训练好的浮点模型 model torch.load(resnet18_fp32.pth) model.eval() # 2. 融合BN必须在量化前 model_fused tq.fuse_modules( model, [[layer1.0.conv1, layer1.0.bn1, layer1.0.relu], # 融合ConvBNReLU [layer1.0.conv2, layer1.0.bn2], [layer1.1.conv1, layer1.1.bn1, layer1.1.relu], # ... 其他层完整列表见CMSIS-NN文档 ], inplaceTrue ) # 3. 插入伪量化节点QAT核心 model_qat tq.prepare_qat(model_fused, inplaceTrue) # 此时model_qat已插入FakeQuantize节点但未训练关键点tq.fuse_modules的模块名必须与模型named_children()输出完全一致tq.prepare_qat默认对所有Conv/Linear层插入对称量化对ReLU后激活插入非对称量化符合前述设计。4.3 QAT微调3个epoch足够参数配置与学习率衰减策略QAT微调不是重新训练而是用小学习率“唤醒”网络对量化噪声的适应性。我的配置# 使用SGD学习率设为原训练的1/100 optimizer torch.optim.SGD(model_qat.parameters(), lr0.001, momentum0.9) # 学习率衰减第1个epoch用lr0.001第2个epoch用0.0005第3个epoch用0.0001 scheduler torch.optim.lr_scheduler.MultiStepLR( optimizer, milestones[1,2], gamma0.5 ) # 训练循环仅3个epoch for epoch in range(3): for data, target in train_loader: optimizer.zero_grad() output model_qat(data) loss criterion(output, target) loss.backward() optimizer.step() scheduler.step()为什么3个epoch足够因QAT不学新特征只调参数分布。实测显示超过3个epoch验证集精度不再提升反而因过拟合校准集导致泛化下降。某项目试过10个epochint8精度反降0.2%。4.4 定点模型导出ONNX与CMSIS-NN转换的避坑指南导出分两步先转ONNX再用CMSIS-NN工具链转C源码。# Step 1: 导出ONNX注意opset版本 dummy_input torch.randn(1, 3, 224, 224) torch.onnx.export( model_qat, dummy_input, resnet18_qat.onnx, opset_version13, # CMSIS-NN要求opset11 input_names[input], output_names[output], dynamic_axes{input: {0: batch_size}, output: {0: batch_size}} ) # Step 2: 用CMSIS-NN的onnx2cmsis工具转换 # 命令行python onnx2cmsis.py resnet18_qat.onnx --output_dir ./cmsis_code避坑点① ONNX的opset_version必须≥11否则CMSIS-NN不识别QAT节点②dynamic_axes必须声明否则转换后模型不支持batch1③ 转换前用onnx.checker.check_model验证ONNX有效性常见错误是FakeQuantize节点未被正确替换为QuantizeLinear。4.5 CMSIS-NN C代码生成与编译Makefile关键参数解析CMSIS-NN生成的C代码需用ARM GCC编译。Makefile核心参数# 必须指定CPU架构和浮点单元 MCU cortex-m7 FLOAT_ABI hard # 优化等级-O1避免-O3破坏定点计算 OPT -O1 # 包含CMSIS-NN头文件 INC -I./CMSIS/NN/Include -I./CMSIS/DSP/Include # 链接CMSIS-NN库已预编译为libcmsisnn.a LIBS -L./CMSIS/NN/Lib/GCC -lcmsisnn # 关键禁用未定义行为优化防止编译器优化掉溢出检查 CFLAGS -fno-strict-overflow编译命令arm-none-eabi-gcc $(CFLAGS) $(INC) main.c ./cmsis_code/resnet18.c -o resnet18.elf $(LIBS)。生成的resnet18.elf用arm-none-eabi-objdump -d反汇编确认调用的是arm_convolve_s8而非浮点指令。4.6 真机烧录与验证J-Link脚本自动化流程烧录到STM32H7开发板的J-Link脚本flash.jlinksi swd speed 4000 device STM32H743VI r h loadfile resnet18.bin 0x08000000 r g exit关键点loadfile地址0x08000000是STM32H7的Flash起始地址rreset和ggo确保程序立即运行。烧录后开发板UART输出[START] Model loaded, waiting for input...此时用Python脚本发送图像数据接收推理结果。整个流程可封装为Shell脚本10秒内完成烧录验证。4.7 性能实测数据在STM32H743上的完整benchmark在STM32H743VI480MHz上实测ResNet-18 int8模型指标浮点模型定点模型提升推理时间124ms38ms3.26xFlash占用24.7MB6.2MB4.0xRAM占用18.3MB4.1MB4.5xTop-1精度ImageNet-1k val69.8%68.5%-1.3%所有数据均来自3次重复测试的平均值。值得注意RAM占用减少主要来自权重从float324B→int81B但激活值仍需int16缓冲区因CMSIS-NN中间计算要求故未达4x。若用TFLite Micro激活值可为int8RAM可再降30%但需牺牲精度。5. 常见问题与排查技巧实录7个真实踩坑案例与速查表5.1 问题速查表症状、根因、解决步骤症状可能根因解决步骤实测耗时定点模型输出全零CMSIS-NN bias未按scale预缩放① 检查JSON中bias_scale是否weight_scale×act_scale×127② 用arm_nn_mat_mult_s8函数手动验证bias计算25分钟精度骤降5%校准数据集未覆盖真实min/max① 用真实设备采集100帧统计各层min/max② 重校准设observerMinMaxObserver(dtypetorch.qint8, quant_min0, quant_max127)1.5小时烧录后程序崩溃Flash地址未对齐非0x08000000起始①arm-none-eabi-objdump -h resnet18.elf查看.text段地址② 修改链接脚本SECTIONS { .text : { *(.text) } FLASH AT FLASH }40分钟UART打印值与仿真差1MCU时钟源漂移导致ADC采样偏差① 用示波器测ADC时钟频率② 在预处理代码中加入时钟校准系数3小时QAT训练loss不下降伪量化节点未正确插入①print(model_qat)检查是否有FakeQuantize层② 确认prepare_qat后未调用convert15分钟CMSIS-NN编译报undefined reference未链接libcmsisnn.a或路径错误①arm-none-eabi-ar -t libcmsisnn.a列出符号② Makefile中-L路径末尾加/20分钟推理结果抖动同输入不同输出DMA缓冲区未清零① 在DMA传输前memset(buffer, 0, size)② 检查CMSIS-NN函数是否要求输入buffer初始化50分钟5.2 独家避坑技巧3个教科书不会写的实战经验技巧1用“量化噪声注入”替代QAT训练当无法获取训练数据时如客户只给.h5模型可用此法在浮点模型推理时对每层输出手动添加符合int8分布的噪声。PyTorch代码def inject_quant_noise(tensor, scale0.0156, zero_point128): # 模拟int8量化round((x/scale) zero_point) quantized torch.round(tensor / scale) zero_point # 截断到[0,255] quantized torch.clamp(quantized, 0, 255) # 反量化回浮点 dequantized (quantized - zero_point) * scale return dequantized # 在forward中插入x inject_quant_noise(x)此法在某保密项目中使PTQ精度从52%提升至67%接近QAT水平。技巧2CMSIS-NN的“隐藏”debug模式在arm_nn_examples/arm_nn_examples_common.h中取消注释#define ARM_NN_DEBUG重新编译CMSIS-NN库。此时每个函数会打印中间计算值如conv2d: input[0]127, weight[0]-45,