From ee9be85292f5918c98df273febe679b16c19bca9 Mon Sep 17 00:00:00 2001 From: master <727475183@qq.com> Date: Wed, 6 May 2026 23:30:49 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20ACL=20bytes=5Fto=5Fptr=20GC=20bug=20+=20?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20NPU=20=E6=A8=A1=E5=9E=8B=E8=BD=AC=E6=8D=A2?= =?UTF-8?q?=E6=8C=87=E5=8D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit python/infer_server.py: - 修复 acl.util.bytes_to_ptr() 后 bytes 被 GC 回收导致全零输出的 bug - 保持 bytes 引用直到 memcpy 完成 - 正确解析 FP16 模型输出 (half precision -> float32) docs/CONVERSION_GUIDE.md: - 完整的 YOLOv8 ONNX->OM 转换流程文档 - ATC 转换参数详解及低内存设备配置 - 已知问题排查表 (OOM/全零输出/算子不支持) - 其他 YOLOv8 尺寸模型参考 start.sh: 一键启动脚本 --- docs/CONVERSION_GUIDE.md | 165 +++++++++++++++++++++++++++++++++++++++ python/infer_server.py | 27 ++++++- start.sh | 49 ++++++++++++ 3 files changed, 238 insertions(+), 3 deletions(-) create mode 100644 docs/CONVERSION_GUIDE.md create mode 100644 start.sh diff --git a/docs/CONVERSION_GUIDE.md b/docs/CONVERSION_GUIDE.md new file mode 100644 index 0000000..0dd6401 --- /dev/null +++ b/docs/CONVERSION_GUIDE.md @@ -0,0 +1,165 @@ +# 昇腾 NPU 模型转换指南 + +> 硬件: Atlas 200I DK2 / 昇腾 310B4 | CANN 6.2.RC2 | YOLOv8n + +--- + +## 方案概要 + +``` +YOLOv8n.pt → YOLOv8n.onnx → yolov8n.om (ACL原生推理) +``` + +| 步骤 | 工具 | 耗时 | 资源需求 | +|------|------|------|----------| +| 1. 下载权重 | ultralytics | ~1min | 无 | +| 2. 导出 ONNX | ultralytics | ~85s | 需安装 onnx/onnxslim | +| 3. ATC 转换 OM | CANN atc | ~16min | 3GB RAM + swap, 单核编译 | + +## 环境要求 + +``` +Ubuntu 22.04 aarch64 +Python 3.9+ (miniconda) +CANN 6.2.RC2 (已安装于 /usr/local/Ascend/ascend-toolkit/latest) +Go 1.22+ (编译 edge-agent) +``` + +## 详细步骤 + +### 第 1 步:安装依赖 + +```bash +# Python 依赖 +pip3 install "numpy>=1.24.0,<2.0.0" +pip3 install ultralytics opencv-python-headless + +# Go (系统源版本太低, 需手动安装) +wget https://go.dev/dl/go1.22.5.linux-arm64.tar.gz +rm -rf /usr/local/go && tar -C /usr/local -xzf go1.22.5.linux-arm64.tar.gz +rm go1.22.5.linux-arm64.tar.gz +export PATH=/usr/local/go/bin:$PATH + +# 验证 +python3 -c "import ultralytics; print('OK')" +go version # 应输出 go1.22.5 +``` + +### 第 2 步:导出 ONNX + +```bash +cd /path/to/AI-tianyan + +python3 -c " +from ultralytics import YOLO +model = YOLO('yolov8n.pt') # 或自定义模型路径 +model.export(format='onnx', imgsz=640, dynamic=False, opset=11, simplify=True) +" +``` + +输出:`yolov8n.onnx` (约 13MB) + +**注意事项:** +- `dynamic=False`:静态输入尺寸,ATC 转换必须 +- `opset=11`:CANN 6.2 最稳定的 ONNX opset 版本 +- `simplify=True`:使用 onnxslim 简化模型图 + +### 第 3 步:ATC 转换 OM(关键步骤) + +```bash +# 设置环境变量(3GB RAM 设备必须) +export TBE_IMPL_MODE=AI_CORE # 使用 AI Core 模式 +export TE_PARALLEL_COMPILER=1 # 单线程编译,避免内存溢出 + +# 执行转换 +atc \ + --model=yolov8n.onnx \ + --framework=5 \ # 5 = ONNX + --output=yolov8n \ + --soc_version=Ascend310B4 \ # 昇腾 310B4 + --input_format=NCHW \ + --input_shape="images:1,3,640,640" \ + --output_type=FP16 # FP16 更快,精度够用 + +# 约 16 分钟后得到 yolov8n.om (约 7.1MB) +``` + +**常见失败原因:** +| 错误 | 原因 | 解决 | +|------|------|------| +| EOFError / 进程崩溃 | TBE 多线程编译 OOM | 设置 `TE_PARALLEL_COMPILER=1` | +| 输出全零 | `bytes_to_ptr()` 后 bytes 被 GC | 保持 bytes 引用见 infer_server.py | +| 算子不支持 | ONNX opset 版本过高 | 改用 `opset=11` | + +### 第 4 步:部署模型 + +```bash +# 复制模型到指定位置 +cp yolov8n.om /opt/tianyan-edge/model/model.om + +# 更新类别文件(COCO 80 类) +cp model/names.txt /opt/tianyan-edge/model/names.txt + +# 启动服务 +cd /opt/tianyan-edge +python3 python/infer_server.py # 推理服务 +./bin/edge-agent -config config/edge.yaml # 主控 Agent +``` + +--- + +## 其他模型 + +| 模型 | 导出命令 | 输出尺寸 | +|------|---------|---------| +| yolov8s | `YOLO('yolov8s.pt').export(...)` | 22.5MB onnx → 11MB om | +| yolov8m | `YOLO('yolov8m.pt').export(...)` | 51.9MB onnx → 25MB om | +| yolov8l | `YOLO('yolov8l.pt').export(...)` | 88.3MB onnx → 43MB om | +| yolov8n | `YOLO('yolov8n.pt').export(...)` | 12.3MB onnx → 7.1MB om | + +**注意:** 大模型转换需要更多内存,建议 8GB+ RAM。 + +--- + +## YOLOv10 注意事项 + +如果用 YOLOv10,需要: +1. `pip install ultralytics yolov10` +2. 导出:`YOLOv10('yolov10n.pt').export(format='onnx', ...)` +3. 修改 `OUTPUT_FORMAT=nms_free` (YOLOv10 输出格式不同) +4. ATC 命令完全相同 + +--- + +## 已知 Bug 修复 + +### ACL bytes_to_ptr 提前 GC 问题 + +**问题描述:** `acl.util.bytes_to_ptr(blob.tobytes())` 返回的指针, +由于 `blob.tobytes()` 创建的临时 bytes 对象被 Python GC 回收, +导致后续 `acl.rt.memcpy` 读到全零数据。 + +**修复方案(见 `python/infer_server.py`):** +```python +# 错误写法 - bytes 临时对象会被 GC +host_addr = acl.util.bytes_to_ptr(blob.tobytes()) +acl.rt.memcpy(in_buf, in_sz, host_addr, in_sz, ...) # 读到空数据 + +# 正确写法 - 保持引用 +host_bytes = blob.tobytes() # 保持引用 +host_addr = acl.util.bytes_to_ptr(host_bytes) +acl.rt.memcpy(in_buf, in_sz, host_addr, in_sz, ...) # 数据正确 +``` + +### FP16 输出解析 + +ATC 转换时指定 `--output_type=FP16` 后,模型输出为 half precision。 +需要按半精度读取并转为 float32: + +```python +host_bytes = host.tobytes() +acl.rt.memcpy(acl.util.bytes_to_ptr(host_bytes), sz, out_buf, sz, ...) +host_from_bytes = np.frombuffer(host_bytes, dtype=np.uint8) +out_fp16 = host_from_bytes.view(np.float16) +output = out_fp16.astype(np.float32).reshape(shape) +``` diff --git a/python/infer_server.py b/python/infer_server.py index 3dedb51..d0a56a0 100644 --- a/python/infer_server.py +++ b/python/infer_server.py @@ -112,10 +112,15 @@ class AclModel: def run(self, blob): """Push blob to NPU, execute, pull outputs back as numpy arrays.""" in_ds = acl.mdl.create_dataset() + # Host input buffer - MUST keep reference alive during memcpy + host_buf = np.ascontiguousarray(blob) + host_bytes = host_buf.tobytes() + host_addr = acl.util.bytes_to_ptr(host_bytes) + # Device input buffer in_buf, ret = acl.rt.malloc(self.input_size, ACL_MEM_MALLOC_NORMAL_ONLY) assert ret == 0 ret = acl.rt.memcpy(in_buf, self.input_size, - blob.tobytes(), self.input_size, + host_addr, self.input_size, ACL_MEMCPY_HOST_TO_DEVICE) assert ret == 0 db = acl.create_data_buffer(in_buf, self.input_size) @@ -135,11 +140,27 @@ class AclModel: for i in range(self.output_num): sz = self.out_sizes[i] host = np.zeros(sz, dtype=np.uint8) - ret = acl.rt.memcpy(host.ctypes.data, sz, + # Keep reference alive during memcpy + host_bytes = host.tobytes() + host_addr = acl.util.bytes_to_ptr(host_bytes) + ret = acl.rt.memcpy(host_addr, sz, self.out_bufs[i], sz, ACL_MEMCPY_DEVICE_TO_HOST) assert ret == 0 - outputs.append(host.view(np.float32).reshape(self.output_shapes[i])) + # Read back from host_bytes (memcpy modified it) + host_from_bytes = np.frombuffer(host_bytes, dtype=np.uint8) + # FP16 model outputs are half precision (2 bytes) + num_fp16 = sz // 2 + num_fp32 = sz // 4 + expected = self.output_shapes[i][0] * self.output_shapes[i][1] * self.output_shapes[i][2] + if num_fp16 == expected: + out_fp16 = host_from_bytes.view(np.float16) + outputs.append(out_fp16.astype(np.float32).reshape(self.output_shapes[i])) + elif num_fp32 == expected: + outputs.append(host_from_bytes.view(np.float32).reshape(self.output_shapes[i])) + else: + out_fp16 = host_from_bytes.view(np.float16) + outputs.append(out_fp16.astype(np.float32).reshape(self.output_shapes[i])) acl.rt.free(in_buf) acl.mdl.destroy_dataset(in_ds) diff --git a/start.sh b/start.sh new file mode 100644 index 0000000..2bcd74f --- /dev/null +++ b/start.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# 一键启动脚本 +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +cd "$SCRIPT_DIR" + +echo "==========================================" +echo " Tianyan Edge - 启动脚本" +echo "==========================================" + +# 检查模型 +if [ ! -f model/model.om ]; then + echo "[错误] model/model.om 不存在,请先转换模型" + echo "使用方法: atc --model=yolov8n.onnx --framework=5 --output=model/model.om --soc_version=Ascend310B4 --input_format=NCHW --input_shape=\"images:1,3,640,640\"" + exit 1 +fi + +# 创建必要的目录 +mkdir -p logs + +# 启动推理服务(后台) +echo "[1/2] 启动推理服务..." +if systemctl is-active --quiet edge-infer 2>/dev/null; then + echo " edge-infer 已在运行" +else + python3 python/infer_server.py > logs/infer.log 2>&1 & + INFER_PID=$! + echo " 推理服务 PID: $INFER_PID" + sleep 2 +fi + +# 启动 Go Agent +echo "[2/2] 启动 Go Agent..." +if systemctl is-active --quiet edge-agent 2>/dev/null; then + echo " edge-agent 已在运行" +else + ./build/edge-agent -config config/edge.yaml 2>&1 | tee logs/agent.log & + AGENT_PID=$! + echo " Agent PID: $AGENT_PID" +fi + +echo "" +echo "启动完成!按 Ctrl+C 停止" +echo "推理日志: logs/infer.log" +echo "Agent日志: logs/agent.log" + +# 等待 +wait