feat: 边缘侧服务代码初始化与配置同步准备
This commit is contained in:
149
bench_npu.py
Normal file
149
bench_npu.py
Normal file
@@ -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)
|
||||
13
config/edge.yaml
Normal file
13
config/edge.yaml
Normal file
@@ -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
|
||||
119
debug_model.py
Normal file
119
debug_model.py
Normal file
@@ -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")
|
||||
76
detect_one.py
Normal file
76
detect_one.py
Normal file
@@ -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)
|
||||
10
go.sum
Normal file
10
go.sum
Normal file
@@ -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=
|
||||
BIN
model/model.om
Normal file
BIN
model/model.om
Normal file
Binary file not shown.
80
model/names.txt
Normal file
80
model/names.txt
Normal file
@@ -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
|
||||
79
test_infer.py
Normal file
79
test_infer.py
Normal file
@@ -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()
|
||||
BIN
yolov8n.om
Normal file
BIN
yolov8n.om
Normal file
Binary file not shown.
Reference in New Issue
Block a user