diff --git a/bench_npu.py b/bench_npu.py new file mode 100644 index 0000000..d0f7c59 --- /dev/null +++ b/bench_npu.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python3 +""" +昇腾 310B4 NPU 推理性能基准测试 +测试项目: +1. 确认 NPU 推理(非 CPU fallback) +2. 单次推理延迟 +3. 连续推理吞吐 (FPS) +4. 多 worker 并发 +5. 不同分辨率影响 +""" +import os +import sys +import time +import socket +import struct +import json +import base64 +import threading +import numpy as np +import cv2 +from concurrent.futures import ThreadPoolExecutor + +SOCK_PATH = "/tmp/edge-infer.sock" + +def make_test_image(w=640, h=640): + """生成随机测试图片""" + img = np.random.randint(0, 255, (h, w, 3), dtype=np.uint8) + _, buf = cv2.imencode('.jpg', img, [cv2.IMWRITE_JPEG_QUALITY, 90]) + return buf.tobytes() + +def infer_one(jpeg_bytes): + """发送一次推理请求,返回响应时间(ms)""" + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + sock.connect(SOCK_PATH) + + msg = { + "stream_id": 0, + "device_id": "bench", + "url": "bench://test", + "ts": 1234567890.0, + "jpeg_b64": base64.b64encode(jpeg_bytes).decode("utf-8") + } + + data = json.dumps(msg).encode("utf-8") + + t0 = time.time() + sock.sendall(struct.pack(">I", len(data)) + data) + hdr = sock.recv(4) + length = struct.unpack(">I", hdr)[0] + result = json.loads(sock.recv(length).decode("utf-8")) + elapsed = (time.time() - t0) * 1000 # ms + + sock.close() + return elapsed, result + +def test_single_infer(): + """单次推理延迟""" + print("\n=== 单次推理延迟测试 ===") + jpeg = make_test_image() + times = [] + for i in range(5): + t, _ = infer_one(jpeg) + times.append(t) + print(f" 第 {i+1} 次: {t:.1f} ms") + + # 跳过第一次预热 + times = times[1:] + avg = sum(times) / len(times) + print(f"\n 平均延迟 (排除预热): {avg:.1f} ms") + print(f" 理论 FPS: {1000/avg:.1f}") + return avg + +def test_throughput(): + """连续推理吞吐""" + print("\n=== 连续推理吞吐测试 ===") + jpeg = make_test_image() + count = 30 + t0 = time.time() + + for i in range(count): + _, _ = infer_one(jpeg) + if (i+1) % 10 == 0: + elapsed = time.time() - t0 + print(f" {i+1}/{count} 完成, 累计: {elapsed:.1f}s, FPS: {(i+1)/elapsed:.1f}") + + total = time.time() - t0 + fps = count / total + print(f"\n 总计 {count} 帧: {total:.2f}s") + print(f" 吞吐: {fps:.1f} FPS") + print(f" 每帧延迟: {total/count*1000:.1f} ms") + return fps + +def test_concurrent(workers=4): + """多 worker 并发推理""" + print(f"\n=== {workers} 路并发测试 ===") + jpeg = make_test_image() + results_per_worker = [] + + def worker_task(worker_id, n_frames=20): + times = [] + for _ in range(n_frames): + t, _ = infer_one(jpeg) + times.append(t) + return worker_id, times + + t0 = time.time() + with ThreadPoolExecutor(max_workers=workers) as pool: + futures = [pool.submit(worker_task, i, 15) for i in range(workers)] + for f in futures: + wid, times = f.result() + results_per_worker.append((wid, sum(times)/len(times), max(times), min(times))) + + total = time.time() - t0 + total_frames = workers * 15 + total_fps = total_frames / total + + for wid, avg, mx, mn in results_per_worker: + print(f" Worker {wid}: avg={avg:.1f}ms, max={mx:.1f}ms, min={mn:.1f}ms") + + print(f"\n 并发 {workers} 路: 总计 {total_frames} 帧, {total:.2f}s") + print(f" 总吞吐: {total_fps:.1f} FPS") + print(f" 单路等效 FPS: {total_fps/workers:.1f}") + return total_fps + +def test_power(): + """读取 NPU 功耗信息""" + import subprocess + try: + result = subprocess.run(['npu-smi', 'info'], capture_output=True, text=True, timeout=5) + print("\n=== NPU 状态 ===") + print(result.stdout) + except Exception as e: + print(f"无法读取 NPU 状态: {e}") + +if __name__ == "__main__": + print("=" * 60) + print(" 昇腾 310B4 + ACL 原生推理 性能基准测试") + print("=" * 60) + + test_power() + test_single_infer() + test_throughput() + test_concurrent(2) + test_concurrent(4) + test_concurrent(6) + + print("\n" + "=" * 60) + print("测试完成") + print("=" * 60) diff --git a/config/edge.yaml b/config/edge.yaml new file mode 100644 index 0000000..784978e --- /dev/null +++ b/config/edge.yaml @@ -0,0 +1,13 @@ +edge_id: edge-demo-001 +cloud_url: http://101.36.73.102:8004 +edge_token: "" + +# RTSP 流地址(留空使用演示模式) +rtsp_urls: [] + +infer_socket: /tmp/edge-infer.sock +infer_fps: 5 +infer_workers: 3 +conf_threshold: 0.5 +dedup_window_sec: 30 +version: 1.0.0 diff --git a/debug_model.py b/debug_model.py new file mode 100644 index 0000000..16b213a --- /dev/null +++ b/debug_model.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 +"""调试: 检查模型原始输出""" +import numpy as np +import cv2 +import acl + +ACL_MEMCPY_HOST_TO_DEVICE = 1 +ACL_MEMCPY_DEVICE_TO_HOST = 2 + +img = cv2.imread("/home/强光车灯误报.png") +print("Image shape:", img.shape) + +acl.init() +acl.rt.set_device(0) +ctx, _ = acl.rt.create_context(0) + +model_id, _ = acl.mdl.load_from_file("/root/AI-tianyan/model/model.om") +desc = acl.mdl.create_desc() +acl.mdl.get_desc(desc, model_id) + +in_sz = acl.mdl.get_input_size_by_index(desc, 0) +print("Input size:", in_sz, "bytes") + +# Preprocess +h0, w0 = img.shape[:2] +inp_h, inp_w = 640, 640 +scale = min(inp_h / h0, inp_w / w0) +nh, nw = int(h0 * scale), int(w0 * scale) +resized = cv2.resize(img, (nw, nh), interpolation=cv2.INTER_LINEAR) +canvas = np.full((inp_h, inp_w, 3), 114, dtype=np.uint8) +pad_top = (inp_h - nh) // 2 +pad_left = (inp_w - nw) // 2 +canvas[pad_top:pad_top + nh, pad_left:pad_left + nw] = resized +rgb = cv2.cvtColor(canvas, cv2.COLOR_BGR2RGB) +blob = rgb.astype(np.float32) / 255.0 +blob = np.ascontiguousarray(blob.transpose(2, 0, 1)[np.newaxis]) +print("Blob shape:", blob.shape, "dtype:", blob.dtype) + +# Allocate device input +in_ds = acl.mdl.create_dataset() +host_buf = np.ascontiguousarray(blob) +host_addr = acl.util.bytes_to_ptr(host_buf.tobytes()) +in_buf, _ = acl.rt.malloc(in_sz, 0) +acl.rt.memcpy(in_buf, in_sz, host_addr, in_sz, ACL_MEMCPY_HOST_TO_DEVICE) +acl.mdl.add_dataset_buffer(in_ds, acl.create_data_buffer(in_buf, in_sz)) + +# Allocate output +out_num = acl.mdl.get_num_outputs(desc) +out_sizes = [acl.mdl.get_output_size_by_index(desc, i) for i in range(out_num)] +out_ds = acl.mdl.create_dataset() +out_bufs = [] +for i in range(out_num): + buf, _ = acl.rt.malloc(out_sizes[i], 0) + out_bufs.append(buf) + acl.mdl.add_dataset_buffer(out_ds, acl.create_data_buffer(buf, out_sizes[i])) + print(f"Output[{i}] size: {out_sizes[i]} bytes") + +# Execute +ret = acl.mdl.execute(model_id, in_ds, out_ds) +print("Execute ret:", ret) + +# Get output +for i in range(out_num): + sz = out_sizes[i] + host = np.zeros(sz, dtype=np.uint8) + host_addr = acl.util.bytes_to_ptr(host.tobytes()) + acl.rt.memcpy(host_addr, sz, out_bufs[i], sz, ACL_MEMCPY_DEVICE_TO_HOST) + + dims_out, _ = acl.mdl.get_output_dims(desc, i) + d = dims_out['dims'] + print(f"\nOutput[{i}] dims: {d}") + print(f" Size: {sz} bytes") + + # Try FP16 + fp16 = host.view(np.float16) + print(f" FP16 shape: {fp16.shape}") + + reshaped = fp16.astype(np.float32).reshape(d) + print(f" Reshaped: {reshaped.shape}") + print(f" Min: {reshaped.min():.4f}, Max: {reshaped.max():.4f}, Mean: {reshaped.mean():.4f}") + + # Check if it's all zeros or NaN + nan_count = np.isnan(reshaped).sum() + zero_count = (reshaped == 0).sum() + print(f" NaN count: {nan_count}, Zero count: {zero_count}/{reshaped.size}") + + # For YOLOv8 output [1, 84, 8400], check class scores + # boxes: reshaped[:, :4, :] + # scores: reshaped[:, 4:, :] + scores = reshaped[0, 4:, :] + max_scores = scores.max(axis=0) # max class score per anchor + print(f"\n Max class scores per anchor:") + print(f" Min: {max_scores.min():.4f}, Max: {max_scores.max():.4f}, Mean: {max_scores.mean():.4f}") + + # Count anchors with score > 0.1 + high_conf = (max_scores > 0.1).sum() + print(f" Anchors with conf > 0.1: {high_conf}") + print(f" Anchors with conf > 0.01: {(max_scores > 0.01).sum()}") + + # Print top 5 + top5_idx = np.argsort(max_scores)[-5:][::-1] + for idx in top5_idx: + best_cls = scores[:, idx].argmax() + print(f" Anchor {idx}: class={best_cls} (score={max_scores[idx]:.4f})") + box = reshaped[0, :4, idx] + print(f" box: {box}") + +# Cleanup +for buf in out_bufs: + acl.rt.free(buf) +acl.rt.free(in_buf) +acl.mdl.destroy_dataset(in_ds) +acl.mdl.destroy_dataset(out_ds) +acl.mdl.unload(model_id) +acl.mdl.destroy_desc(desc) +acl.rt.destroy_context(ctx) +acl.rt.reset_device(0) +acl.finalize() +print("\nDone") diff --git a/detect_one.py b/detect_one.py new file mode 100644 index 0000000..81d1e85 --- /dev/null +++ b/detect_one.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +"""检测单张图片 - 通过 Unix socket 发送到推理服务""" +import sys +import socket +import struct +import json +import base64 +import cv2 + +SOCK_PATH = "/tmp/edge-infer.sock" + +def detect_image(image_path): + img = cv2.imread(image_path) + if img is None: + print("无法读取图片:", image_path) + return + + # 编码为 JPEG + _, buf = cv2.imencode('.jpg', img, [cv2.IMWRITE_JPEG_QUALITY, 90]) + jpeg_bytes = buf.tobytes() + + # 连接推理服务 + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + sock.connect(SOCK_PATH) + + # 构造请求 + msg = { + "stream_id": 0, + "device_id": "cli-test", + "url": "file://" + image_path, + "ts": 1234567890.0, + "jpeg_b64": base64.b64encode(jpeg_bytes).decode("utf-8") + } + + # 发送 + data = json.dumps(msg).encode("utf-8") + sock.sendall(struct.pack(">I", len(data)) + data) + + # 接收结果 + hdr = sock.recv(4) + if not hdr: + print("未收到响应") + sock.close() + return + + length = struct.unpack(">I", hdr)[0] + result = json.loads(sock.recv(length).decode("utf-8")) + sock.close() + + # 输出结果 + dets = result.get("detections", []) + print(f"\n图片: {image_path}") + print(f"尺寸: {img.shape[1]}x{img.shape[0]}") + print(f"检测到 {len(dets)} 个目标\n") + print(f"{'类别':<20} {'置信度':<10} {'边界框'}") + print("-" * 60) + + for d in dets: + bbox = d["bbox"] + print(f'{d["class"]:<20} {d["conf"]:<10.3f} [{bbox[0]:.0f}, {bbox[1]:.0f}, {bbox[2]:.0f}, {bbox[3]:.0f}]') + + # 保存带标注的图片 + if dets: + for d in dets: + x1, y1, x2, y2 = [int(x) for x in d["bbox"]] + cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) + label = f'{d["class"]} {d["conf"]:.2f}' + cv2.putText(img, label, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2) + + out_path = image_path.rsplit(".", 1)[0] + "_result.jpg" + cv2.imwrite(out_path, img) + print(f"\n已保存标注图片: {out_path}") + +if __name__ == "__main__": + path = sys.argv[1] if len(sys.argv) > 1 else "/home/强光车灯误报.png" + detect_image(path) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..33d593e --- /dev/null +++ b/go.sum @@ -0,0 +1,10 @@ +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/model/model.om b/model/model.om new file mode 100644 index 0000000..90a2456 Binary files /dev/null and b/model/model.om differ diff --git a/model/names.txt b/model/names.txt new file mode 100644 index 0000000..941cb4e --- /dev/null +++ b/model/names.txt @@ -0,0 +1,80 @@ +person +bicycle +car +motorcycle +airplane +bus +train +truck +boat +traffic light +fire hydrant +stop sign +parking meter +bench +bird +cat +dog +horse +sheep +cow +elephant +bear +zebra +giraffe +backpack +umbrella +handbag +tie +suitcase +frisbee +skis +snowboard +sports ball +kite +baseball bat +baseball glove +skateboard +surfboard +tennis racket +bottle +wine glass +cup +fork +knife +spoon +bowl +banana +apple +sandwich +orange +broccoli +carrot +hot dog +pizza +donut +cake +chair +couch +potted plant +bed +dining table +toilet +tv +laptop +mouse +remote +keyboard +cell phone +microwave +oven +toaster +sink +refrigerator +book +clock +vase +scissors +teddy bear +hair drier +toothbrush diff --git a/test_infer.py b/test_infer.py new file mode 100644 index 0000000..084e183 --- /dev/null +++ b/test_infer.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +"""测试推理服务 - 发送一张测试图片""" +import socket +import struct +import json +import base64 +import numpy as np +import cv2 +import os + +SOCK_PATH = "/tmp/edge-infer.sock" + +def create_test_image(): + """创建一张 640x480 的测试图片,画几个几何图形""" + img = np.zeros((480, 640, 3), dtype=np.uint8) + # 画一些简单的形状模拟检测目标 + cv2.rectangle(img, (50, 50), (200, 200), (255, 255, 255), -1) + cv2.circle(img, (400, 300), 80, (255, 255, 255), -1) + # 编码为 JPEG + _, buf = cv2.imencode('.jpg', img) + return buf.tobytes() + +def send_msg(conn, payload): + data = json.dumps(payload).encode("utf-8") + conn.sendall(struct.pack(">I", len(data)) + data) + +def recv_msg(conn): + hdr = conn.recv(4) + if not hdr: + return None + length = struct.unpack(">I", hdr)[0] + data = b"" + while len(data) < length: + chunk = conn.recv(length - len(data)) + if not chunk: + return None + data += chunk + return json.loads(data.decode("utf-8")) + +def main(): + if not os.path.exists(SOCK_PATH): + print(f"错误: socket {SOCK_PATH} 不存在") + return + + print("连接推理服务...") + conn = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + conn.connect(SOCK_PATH) + + # 创建测试图片 + jpeg_bytes = create_test_image() + msg = { + "stream_id": 0, + "device_id": "test-001", + "url": "test://local", + "ts": 1234567890.0, + "jpeg_b64": base64.b64encode(jpeg_bytes).decode("utf-8") + } + + print("发送测试图片...") + send_msg(conn, msg) + + print("等待推理结果...") + result = recv_msg(conn) + + if result: + print(f"\n推理结果:") + print(f" stream_id: {result['stream_id']}") + print(f" device_id: {result['device_id']}") + print(f" detections: {len(result['detections'])} 个目标") + for d in result['detections']: + print(f" - {d['class']}: {d['conf']:.3f} bbox={d['bbox']}") + else: + print("未收到结果") + + conn.close() + print("\n测试完成!") + +if __name__ == "__main__": + main() diff --git a/yolov8n.om b/yolov8n.om new file mode 100644 index 0000000..90a2456 Binary files /dev/null and b/yolov8n.om differ