在这里插入图片描述

title: “三台昇腾 310B4 下位机的分布式推理实践:上位机调度、内网穿透与 8K 目标检测”
date: 2026-05-11
categories:

  • AI 工程实践
    tags:
  • 昇腾 310B4
  • 分布式推理
  • YOLOv26
  • 8K 检测
  • Tailscale
  • SSH
  • SFTP

三台昇腾 310B4 下位机的分布式推理实践:上位机调度、内网穿透与 8K 目标检测

这篇记录的是我最近整理的一套分布式目标检测推理方案。硬件主角是三台昇腾 310B4 下位机,上位机是一台 Windows 机器。上位机不跑模型,主要负责选图、切图、调度、传输、回收结果和统计;三台 310B4 下位机只负责加载 OM 模型并执行推理。

这套方案的重点不是“模型并行”,而是“任务级分布式推理”:把一批图片,或者一张 8K 大图切出来的多个 tile,拆成独立任务分发给多台 310B4。每台下位机各跑各的,最后由上位机统一合并结果。

本文只放配置、流程、伪代码和关键设计,不展开完整源码。

1. 系统定位

这个系统解决的是一个很实际的问题:单台下位机能跑,但是批量图片和 8K 大图跑起来比较慢;三台下位机都在手边,如果每台都单独手工上传、执行、下载,效率又太低。

所以我的处理方式是:

  • Windows 上位机作为总控端;
  • 三台昇腾 310B4 下位机作为推理节点;
  • 上位机通过 SSH 执行远程命令;
  • 上位机通过 SFTP 上传图片、下载标签;
  • 普通图片按图片粒度分发;
  • 8K 图片先切成大 tile,再按 tile 粒度分发;
  • 每台下位机推理结束后,上位机统一回收 YOLO 标签并做汇总。

最终得到的是一个比较清晰的“上位机调度 + 多下位机推理”的结构,后续要增加第四台、第五台下位机时,只需要扩展设备配置和分发策略。

2. 四阶段流程图

这张图是上位机调用下位机检测算法的四阶段流程。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如果博客平台不方便显示 SVG,可以用下面的 Mermaid 版本:

阶段 1:上位机任务准备
选图 / 8K 填充裁剪 / 任务分组

阶段 2:SSH/SFTP 分发调度
检查连接 / 创建目录 / 上传输入

阶段 3:昇腾 310B4 下位机推理
加载 OM 模型 / NPU 推理 / 输出 YOLO 标签

阶段 4:结果回收与汇总
下载标签 / 8K 合并 / AP50 验证 / 日志记录

四个阶段里,最关键的是第二和第三阶段:上位机要把任务真正分散到多台 310B4 上,同时保证每台下位机的输入输出目录相互独立、状态可控、失败可定位。

3. 文件结构

主要文件放在 distributed/ 目录里:

distributed/
├── gui_client_parallel.py      # 上位机 GUI 和分布式调度入口
├── preprocess_8k.py            # 8K 填充、切块、tile 标签合并
├── config.py                   # 配置读取
├── config.yaml                 # 设备、远程目录、推理命令、8K 参数
├── requirements.txt            # 上位机依赖
├── SSH与SFTP调度推理流程说明.md
├── 8K裁剪流程详解.md
└── 上位机调用下位机检测算法-四阶段流程草图.svg

项目根目录/
├── inference_yolov26_full.py   # 下位机侧 OM 推理脚本
└── best.om                     # 昇腾 OM 模型

这里有一个原则:上位机代码不直接处理 NPU 推理,下位机代码也不掺杂 GUI 调度逻辑。两边通过“输入目录、输出目录、远程命令、标签文件”这几个约定连接起来。

4. 网络结构与内网穿透

三台下位机在实验室内网里,IP 固定为:

下位机 1:192.168.137.100
下位机 2:192.168.137.101
下位机 3:192.168.137.102

Windows 上位机有两张网络意义上的接口:

Windows 上位机
├── 内网网关地址:192.168.137.1
└── Tailscale 地址:100.x.y.z

内网里,上位机可以直接访问三台下位机:

ping 192.168.137.100
ping 192.168.137.101
ping 192.168.137.102

如果同门或另一台电脑不在实验室内网,就通过 Tailscale 做子网路由。这里不是把三台下位机都安装 Tailscale,而是让 Windows 上位机作为子网网关,把三台下位机的 /32 路由广播出去。

网络关系可以理解成:

远程电脑
  |
  | Tailscale 私有网络
  |
Windows 上位机 / 子网网关
  |
  | 192.168.137.0/24 实验室内网
  |
三台昇腾 310B4 下位机

上位机侧需要做三件事。

第一,开启 Windows IPv4 转发:

Set-NetIPInterface -InterfaceAlias "Tailscale" -AddressFamily IPv4 -Forwarding Enabled
Set-NetIPInterface -InterfaceAlias "<内网网卡名称>" -AddressFamily IPv4 -Forwarding Enabled
New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" `
  -Name IPEnableRouter -Value 1 -PropertyType DWord -Force

第二,广播三台下位机的子网路由:

tailscale set --advertise-routes=192.168.137.100/32,192.168.137.101/32,192.168.137.102/32

第三,在 Tailscale 管理后台批准这些路由。批准后,其他加入同一个 tailnet 的电脑就能像在局域网一样访问三台下位机。

远程电脑侧测试:

tailscale status
ping 192.168.137.100
ping 192.168.137.101
ping 192.168.137.102
Test-NetConnection 192.168.137.100 -Port 22

Linux 客户端如果没有自动接受路由,需要执行:

sudo tailscale set --accept-routes

跑通以后,远程电脑也可以直接 SSH 到 310B4:

ssh root@192.168.137.100
ssh root@192.168.137.101
ssh root@192.168.137.102

这个方案的好处是:分布式推理程序本身不用改。无论人在内网还是外网,只要能通过 Tailscale 访问这三个 192.168.137.x 地址,上位机仍然按原来的 SSH/SFTP 方式调度下位机。

5. 上下位机部署

上位机环境比较轻:

cd E:\project\shijiazhuang\distributed
python -m pip install -r requirements.txt
python gui_client_parallel.py

依赖主要是:

paramiko
Pillow
pyyaml

每台 310B4 下位机需要准备:

/shijiazhuang/
├── best.om
├── inference_yolov26_full.py
├── incoming/
└── outgoing/
    └── labels/

下位机侧主要检查 CANN、Python、ais_bench 和 SSH:

source /usr/local/Ascend/ascend-toolkit/set_env.sh
/usr/local/miniconda3/bin/python --version
/usr/local/miniconda3/bin/python -c "from ais_bench.infer.interface import InferSession; print('ais_bench OK')"
npu-smi info

6. 配置文件

核心配置放在 config.yaml。下面是公开版配置,密码和私钥路径都用占位符,实际发布博客时不要写真实凭据。

HOST: "192.168.137.100"
USER: "root"
PASSWORD: "<SSH_PASSWORD>"
KEY_FILE: "<SSH_PRIVATE_KEY_PATH>"
REMOTE_IN: "/shijiazhuang/incoming"
REMOTE_OUT: "/shijiazhuang/outgoing"

ENABLED_HOSTS:
  - "192.168.137.100"
  - "192.168.137.101"
  - "192.168.137.102"

EIGHTK_OUTPUT_CLASS_ID: 2
EIGHTK_NMS_CLASS_AGNOSTIC: true
EIGHTK_NMS_CONTAINMENT: 0.80
EIGHTK_BOX_SCALE: 1.5
EIGHTK_TILE_SIZE: 7168
EIGHTK_OVERLAP: 0.10
EIGHTK_CONF: 0.05
EIGHTK_POST_CONF: 0.05

INFER_CMD: >-
  source /usr/local/Ascend/ascend-toolkit/set_env.sh &&
  /usr/local/miniconda3/bin/python /shijiazhuang/inference_yolov26_full.py
  --source {remote_in} --save-dir {remote_out}
  --model /shijiazhuang/best.om
  --device 0
  --labels ""
  --no-save-vis

INFER_TIMEOUT: 600
SSH_TIMEOUT: 120
SCP_TIMEOUT: 60
STATS_INTERVAL: 2
UPLOAD_WORKERS: 4
RETRY_COUNT: 3
RETRY_DELAY: 3

几个字段比较重要:

  • ENABLED_HOSTS:真正参与推理的 310B4 下位机列表。
  • REMOTE_IN:上位机上传图片或 tile 的远程输入目录。
  • REMOTE_OUT:下位机写出预测标签和摘要文件的目录。
  • INFER_CMD:下位机实际执行的检测命令模板。
  • UPLOAD_WORKERS:每台下位机上传文件时的并发路数。
  • EIGHTK_TILE_SIZEEIGHTK_OVERLAP:8K 切块大小和重叠比例。

如果某台下位机临时关机,只需要从 ENABLED_HOSTS 里移除对应 IP,GUI 就不会再给它分配任务。

7. 分布式执行是怎么跑起来的

这一节是整套系统的核心。

我这里做的是任务级并行。每张普通图片是一个任务;在 8K 模式下,每个 tile 是一个任务。任务准备好以后,上位机根据启用设备列表做分组,然后为每台下位机启动一个独立执行单元。

公开版伪代码如下:

读取启用的 310B4 下位机列表
读取待推理图片列表

如果是 8K 模式:
    把每张大图填充到 8192 x 8192
    按大窗口切成多个 tile
    把 tile 列表作为待分发任务
否则:
    把原始图片列表作为待分发任务

按照设备数量对任务做轮询分组

对每台下位机并行启动一个执行单元:
    检查 SSH 连接
    创建远程输入/输出目录
    清理上一次残留文件
    通过 SFTP 并发上传本组任务
    通过 SSH 执行远程推理命令
    下载 labels 和 summary
    清理远程输入目录

等待所有下位机完成

如果是 8K 模式:
    把 tile 标签映射回完整大图坐标
    执行全图去重和合并

如果是验证模式:
    计算 AP50、TP、FP、FN

写出日志、标签和摘要文件

这里有两个设计点比较关键。

第一,每台下位机是独立执行单元。某台设备上传慢、推理慢或出现错误,不会直接阻塞其他设备继续完成自己的任务。上位机最终根据各 worker 的返回状态统一汇总。

第二,任务分发粒度可以变化。小图模式按图片分发,8K 模式按 tile 分发。这样三台 310B4 不需要知道输入是原图还是大图切片,下位机只看到一批普通图片,处理方式保持一致。

实际分发时,10 张 8K 大图会先生成 40 个 tile,然后分给三台设备,例如:

下位机 1:14 个 tile
下位机 2:13 个 tile
下位机 3:13 个 tile

这种分发方式简单,但很稳。后面如果要继续优化,可以把静态轮询换成动态队列,让空闲下位机主动领取下一批任务。

8. SSH/SFTP 调度链路

上位机和下位机之间只依赖 SSH/SFTP,不需要额外部署服务端程序。这样做的好处是部署简单,排错也直接。

单台下位机的调度顺序可以概括成:

SSH: echo connected
SSH: mkdir -p incoming/outgoing/labels
SSH: rm -f old inputs and old labels
SFTP: upload images or tiles
SSH: run remote inference command
SSH: read predict_summary.json
SFTP: download labels
SSH: cleanup remote inputs

推理命令由配置文件统一管理。运行前,上位机会把 {remote_in}{remote_out} 替换成对应目录。如果启用 8K 或 AP50 验证,还会追加必要的置信度输出参数。

远程命令形态大致如下:

source /usr/local/Ascend/ascend-toolkit/set_env.sh &&
/usr/local/miniconda3/bin/python /shijiazhuang/inference_yolov26_full.py \
  --source /shijiazhuang/incoming \
  --save-dir /shijiazhuang/outgoing \
  --model /shijiazhuang/best.om \
  --device 0 \
  --labels "" \
  --no-save-vis

这条命令是公开文章里可以说明的,因为它只是部署和调用方式。真正涉及调度、异常处理、性能采集、标签合并的细节,源码里保留即可。

9. 8K 大图推理

8K 模式的处理思路是:大图在上位机切,下位机仍然只推理普通图片。

参数如下:

目标画布:8192 x 8192
切块大小:7168 x 7168
重叠比例:0.10
步长:6451

处理流程:

原始大图
  -> 等比例缩放
  -> 居中填充到 8192 x 8192
  -> 生成 2 x 2 大窗口 tile
  -> 记录每个 tile 在全图里的 left/top/right/bottom
  -> 分发 tile 到三台 310B4 推理
  -> 下载每个 tile 的 YOLO 标签
  -> 将 tile 坐标还原到 8192 全图坐标
  -> 全图 NMS 去重
  -> 输出每张大图的最终标签

一张 8K 图切成 4 个 tile 后,三台下位机可以同时跑不同 tile。下位机完全不需要知道这些 tile 来自哪张大图;上位机在本地保存了 tile 与原图之间的映射关系,合并时再用。

最终标签仍然是 YOLO 格式:

class_id center_x center_y width height confidence

8K 模式的 FPS 按完整大图统计,不按 tile 统计。也就是说,系统显示的 fps_8k 表示每秒处理多少张完整 8K 图,而不是每秒处理多少个裁剪块。

10. 下位机侧推理

下位机脚本负责加载 OM 模型、读取输入目录图片、执行预处理、调用 NPU 推理、后处理并写出标签。

这里不贴完整源码,只保留执行逻辑:

加载 best.om
遍历输入目录图片
对图片做 letterbox 和归一化
调用 ais_bench InferSession 推理
把输出还原成原图坐标
执行置信度过滤和 NMS
写出 labels/*.txt
写出 predict_summary.json

predict_summary.json 里会记录图片数量、检测数量、纯推理耗时和平均 FPS。上位机读取这个文件后,可以区分“模型实际推理耗时”和“上传下载等端到端耗时”。

11. AP50 验证

验证模式要求输入目录包含:

dataset/
├── images/
└── labels/

普通图片验证时,上位机可以把同名标签同步到远程临时目录;8K 验证时,GT 标签需要和大图填充规则同步变换到 8192 坐标系,再与合并后的预测框计算 AP50。

匹配规则如下:

按类别分组
预测框按置信度从高到低排序
同类别预测框与未匹配 GT 计算 IoU
IoU >= 0.50 记为 TP
没有匹配成功的预测记为 FP
没有被匹配的 GT 记为 FN
根据 precision-recall 曲线计算 AP50

最终输出:

validation_metrics.json

里面包含整体 AP50、每类 AP50、TP/FP/FN,以及每张图片的 best IoU。

12. 一次 8K + AP50 测试结果

这是一组 10 张 8K 大图的测试摘要:

{
  "mode": "8k",
  "source_count": 10,
  "tile_count": 40,
  "raw_tile_detections": 43,
  "total_detections": 12,
  "pure_inference_sec": 1.5406,
  "fps_8k": 6.490978,
  "wall_time_sec": 70.2809,
  "ap50": 0.36
}

这里要分清两种时间:

  • pure_inference_sec:来自下位机侧推理摘要,三台并行时取最慢的那台作为瓶颈。
  • wall_time_sec:上位机从开始分发到下载、合并完成的墙钟时间。

调试日志里能看到分布式执行过程:

8K 前置处理完成:10 张大图生成 40 张 tile
开始 8K 并行推理:3 台下位机,10 张 8K 大图,40 张 tile
下位机 1 分配 14 张
下位机 2 分配 13 张
下位机 3 分配 13 张
远程推理结束:ok=True,纯推理耗时=...
8K tile 结果合并完成:raw tile 目标 43,全局合并后 12
验证完成:AP50=0.3600

从这个日志也能看出来,分布式执行不是简单地“同时点三次运行”,而是上位机统一准备任务、统一分发、统一监控、统一回收。

13. 输出目录

普通推理输出:

output/
├── labels/
│   ├── image_001.txt
│   └── image_002.txt
├── predict_summary.json
├── predict_summary.csv
├── inference_fps.txt
└── parallel_debug.log

8K 推理输出:

output/
├── labels/
│   ├── big_image_001.txt
│   └── big_image_002.txt
├── predict_summary_8k.json
├── validation_metrics.json
└── parallel_debug.log

中间生成的 tile 和 tile 标签会放在临时目录,推理完成后自动清理。最终保留下来的主要是大图标签、摘要文件和调试日志。

14. 排查方法

检查上位机代码是否能正常导入:

cd E:\project\shijiazhuang\distributed
python -m py_compile config.py preprocess_8k.py gui_client_parallel.py

检查三台 310B4 是否在线:

ping 192.168.137.100
ping 192.168.137.101
ping 192.168.137.102

检查 SSH 端口:

Test-NetConnection 192.168.137.100 -Port 22
Test-NetConnection 192.168.137.101 -Port 22
Test-NetConnection 192.168.137.102 -Port 22

检查模型和推理脚本:

ssh root@192.168.137.100 "test -f /shijiazhuang/best.om && test -f /shijiazhuang/inference_yolov26_full.py && echo ok"

如果 GUI 提示失败,优先看:

output/parallel_debug.log

这份日志会记录连接、上传、远程命令、stderr、下载和结果缺失情况,比单纯看界面提示有用得多。

15. 小结

这套方案的核心卖点很明确:用 Windows 上位机把三台昇腾 310B4 下位机组织成一个分布式推理系统。下位机负责 NPU 推理,上位机负责调度、传输、8K 切块、结果合并、AP50 验证和日志。

我比较满意的一点是,下位机侧保持了“只做推理”的简单形态。只要 SSH、SFTP、CANN、ais_bench 和 OM 模型正常,上位机就可以把普通图片或 8K tile 分发过去跑。后续无论是增加下位机数量,还是把任务分配改成动态调度,都有继续扩展的空间。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐