输出结构:
SAM3模型迁移指导文档/→SAM3模型迁移指导文档/SAM3模型迁移指导文档.md→SAM3模型迁移指导文档/SAM3模型迁移指导文档_assets/{images,files}/。 每个小节中若存在多个操作步骤,统一使用有序编号1.、2.、3.表达。
SAM3(Segment Anything Model 3)是Meta AI Research开发的通用图像分割模型,支持交互式分割、目标检测和视频追踪等任务。SAM3在SAM/SAM2的基础上引入了文本-视觉双编码器架构(ViT + Text Encoder),结合RoPE(Rotary Position Embedding)和TwoWayTransformer实现prompt-to-object对齐。模型原始运行环境为GPU(CUDA)。
用于图像与视频的交互式分割、开放词汇目标检测、Roboflow 100-VL / ODinW13等下游微调任务。本文档聚焦NPU训练迁移与PyTorch PT模式推理迁移场景。
| 项目 | 版本 / 规格 |
|---|---|
| 操作系统 / 架构 | Linux / aarch64 |
| 驱动 / 固件 | 25.2.2 / TODO(固件版本) |
| CANN | 8.1.RC1(ascend-toolkit + kernels) |
| Python | 3.10.14(编译安装至 /usr/local/python3.10/) |
| torch / torch_npu | 2.1.0 / 2.1.0.post11 |
SAM3预训练权重文件(sam3.pt,约3.3GB),放置于 /home/sam3.pt。在训练配置中通过checkpoint_path参数指定。
https://github.com/roboflow/rf100-vl(需Roboflow API Key)coco2017/
val2017/ ← 图片文件夹(5000张)
annotations/
instances_val2017.json ← 标注文件https://github.com/facebookresearch/sam3(基线 commit: 8e451d5)
TODO(待补充官方或自建镜像)
npu-smi、atc、set_env.sh(CANN 环境配置)
克隆 SAM3 源码:
git clone https://gitcode.com/gh_mirrors/sam3.git /root/sam3每次运行前需执行以下基础环境配置(训练和推理通用):
source /usr/local/Ascend/ascend-toolkit/ascend-toolkit/set_env.sh
export LD_LIBRARY_PATH=/usr/local/Ascend/driver/lib64:/usr/local/Ascend/driver/lib64/common:/usr/local/Ascend/driver/lib64/driver:$LD_LIBRARY_PATH
export PATH=/usr/local/python3.10/bin:$PATH训练场景额外配置:
export PYTHONPATH=/root/sam3:$PYTHONPATH
export ASCEND_RT_VISIBLE_DEVICES=4,5,6,7 # 多卡训练指定4卡;单卡训练指定1卡,如=6纯 OM 推理场景额外配置(不需要 PYTHONPATH,不需要 torch_npu):
source /usr/local/Ascend/ascend-toolkit/ascend-toolkit/8.1.RC1/bin/setenv.bash
export LD_LIBRARY_PATH=/usr/local/Ascend/driver/lib64:/usr/local/Ascend/driver/lib64/common:/usr/local/Ascend/driver/lib64/driver:$LD_LIBRARY_PATH安装 PyTorch 和 torch_npu:
pip3.10 install torch==2.1.0 --no-deps
pip3.10 install -i https://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com filelock typing-extensions sympy networkx fsspec jinja2 "numpy<2"
pip3.10 install -i https://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com torch-npu==2.1.0.post11 pyyaml安装 SAM3 核心及训练依赖:
pip3.10 install -i https://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com timm tqdm ftfy regex iopath huggingface_hub hydra-core submitit tensorboard zstandard torchmetrics fvcore fairscale scikit-image scikit-learn opencv-python-headless pillow torchvision==0.16.0 einops pycocotools降级 numpy:
pip3.10 install -i https://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com "numpy<2"安装 CANN TBE 编译器所需的 Python 模块:
pip3.10 install -i https://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com decorator scipy attr attrs psutil astunparse cffi pathlib2 protobuf方案A:使用 COCO 2017 验证集(smoke test 推荐)
mkdir -p /root/coco2017
python3.10 -c "import zipfile; zipfile.ZipFile('val2017.zip').extractall('/root/coco2017/')"
python3.10 -c "import zipfile; zipfile.ZipFile('annotations_trainval2017.zip').extractall('/root/coco2017/')"数据目录结构:
/root/coco2017/
val2017/ ← 图片文件夹
annotations/
instances_val2017.json ← 只需此文件方案B:使用 Roboflow 100-VL(正式训练)
pip3.10 install rf100vl
export ROBOFLOW_API_KEY=TODO(用户提供)
python3.10 -c "from rf100vl import download_rf100vl; download_rf100vl(path='./rf100-vl/')"SAM3 源码存在多处 GPU/CUDA 硬编码,需进行 NPU 兼容性适配。以下为已完成的代码修改清单:
torch.nn.attention 兼容适配层:torch 2.1.0 缺少该模块,创建兼容替代。
适配文件:torch_nn_attention_compat.py
from contextlib import contextmanager
from enum import Enum
class SDPBackend(Enum):
MATH = 0
FLASH_ATTENTION = 1
EFFICIENT_ATTENTION = 2
CUDNN_ATTENTION = 3
@contextmanager
def sdpa_kernel(*backends):
yield在 sam3/model/decoder.py 和 sam3/model/vl_combiner.py 中修改导入:
try:
from torch.nn.attention import sdpa_kernel, SDPBackend
except ImportError:
from sam3.compat.torch_nn_attention import sdpa_kernel, SDPBackendpytree.register_pytree_node 兼容性处理:torch 2.1.0 使用 _register_pytree_node。
在 sam3/model/data_misc.py 中修改:
_register_fn = getattr(pytree, 'register_pytree_node', None) or getattr(pytree, '_register_pytree_node')
_register_fn(NestedTensor, ...)EDT 算子 NPU 替代实现:triton 为 CUDA 专用,用 scipy 替代。
适配文件:edt_npu.py
在 sam3/model/sam3_tracker_utils.py 中修改导入:
try:
from sam3.model.edt import edt_triton
except ImportError:
from sam3.model.edt_npu import edt_tritondevice="cuda" 硬编码替换:将多处 "cuda" 改为动态设备选择。
设备工具:device_utils.py
在 sam3/model/position_encoding.py 中修改:
import sam3.compat
tensors = torch.zeros((1, 1) + size, device=sam3.compat.get_device())训练 YAML 配置修改:需修改以下字段。
trainer.accelerator → cuda 改为 nputrainer.distributed.backend → nccl 改为 hccltrainer.model.device → cuda 改为 nputrainer.model.load_from_HF → 设置为 false(本地加载权重)trainer.model.checkpoint_path → 设置为 /home/sam3.ptsubmitit.use_cluster → 设置为 False(本地执行)launcher.gpus_per_node → 设置为 1(单卡)NPU 设备初始化:在 sam3/train/trainer.py 的 _setup_device 方法中增加 NPU 分支。
elif accelerator == "npu":
self.device = torch.device("npu", self.local_rank)
torch.npu.set_device(self.local_rank)注意:CANN 8.1.RC1 已安装预编译 kernels(8869 个 .o 文件),不再需要 torch.npu.set_compile_mode(jit_compile=True)。如果使用 CANN 8.0.RC2,则需要开启 JIT 编译模式。
RoPE 实数运算替代:NPU 不支持在 NPU 上直接创建 Complex dtype 张量,需将 torch.polar(复数)改为 torch.cos/torch.sin(实数)。
注意:CANN 8.1.RC1 下 torch.randn(..., dtype=torch.complex64, device='npu:0') 仍会报错,Complex 张量只能在 CPU 上创建后通过 view_as_real 转为 float32 再移到 NPU。因此 use_rope_real=True 解决方案在所有 CANN 版本中均需保留。
在 sam3/sam/rope.py 的 compute_axial_cis 中增加 use_real=True 参数:
if use_real:
freqs_cis_x_real = torch.cos(freqs_x)
freqs_cis_x_imag = torch.sin(freqs_x)
freqs_cis_y_real = torch.cos(freqs_y)
freqs_cis_y_imag = torch.sin(freqs_y)
freqs_cis_real = torch.cat([freqs_cis_x_real, freqs_cis_y_real], dim=-1)
freqs_cis_imag = torch.cat([freqs_cis_x_imag, freqs_cis_y_imag], dim=-1)
return None, freqs_cis_real, freqs_cis_imag在 sam3/model_builder.py 中设置全局变量自动启用:
_USE_ROPE_REAL = sam3.compat.is_npu_available()涉及文件:sam3/sam/rope.py、sam3/model/decoder.py(RoPEAttention)、sam3/sam/transformer.py(RoPEAttention)、sam3/model/vitdet.py(Attention _setup_rope_freqs 和 compute_axial_cis)
GradScaler NPU 适配:torch 2.1.0 CPU 版本无 torch.amp.GradScaler。
在 sam3/train/trainer.py 中根据设备类型选择:
if sam3.compat.is_npu_available():
self.scaler = torch.npu.amp.GradScaler(enabled=...)
elif torch.cuda.is_available():
self.scaler = torch.amp.GradScaler(self.device, enabled=...)triton sigmoid_focal_loss 兼容处理:NPU 无 triton,需条件导入并走纯 PyTorch 路径。
在 sam3/train/loss/loss_fns.py 中修改:
try:
from sam3.train.loss.sigmoid_focal_loss import triton_sigmoid_focal_loss, triton_sigmoid_focal_loss_reduce
except ImportError:
triton_sigmoid_focal_loss = None
triton_sigmoid_focal_loss_reduce = None并在 sigmoid_focal_loss 函数中:if triton and triton_sigmoid_focal_loss is not None:
Trainer NPU 加速器支持:在 sam3/train/trainer.py 中增加 NPU 分支。
_setup_device:增加 accelerator == "npu" 分支_infer_distributed_backend_if_none:NPU 使用 hccl 后端_setup_ddp_distributed_training:NPU 传入 device_idstorch.amp.autocast:device_type 改为 sam3.compat.get_device_type_for_autocast()torch.cuda.is_available() → sam3.compat.is_accelerator_available()torch.cuda.empty_cache() → sam3.compat.empty_cache()multiprocessing set_start_method 兼容处理:重复调用会抛 RuntimeError。
在 sam3/train/train.py 中修改:
try:
torch.multiprocessing.set_start_method("spawn")
except RuntimeError:
passfunctional_attention RoPE 条件判断修复:freqs_cis is not None 条件在 use_rope_real=True 时为 False(freqs_cis=None),导致 RoPE 被跳过。
在 sam3/model/decoder.py 中修改:
if freqs_cis is not None or freqs_cis_real is not None:其他文件适配:
sam3/train/utils/train_utils.py:torch.cuda.manual_seed_all → sam3.compat.manual_seed_all;MemMeter 中 torch.cuda.max_memory_allocated → sam3.compat.max_memory_allocatedsam3/train/utils/distributed.py:set_cuda_device_index 增加 NPU 分支;DDP device_ids 增加条件判断sam3/model/model_misc.py:get_sdpa_settings 增加 NPU 分支(old_gpu=True, use_flash_attn=False)sam3/model/vl_combiner.py:device="cuda" 默认参数改为 sam3.compat.get_device()sam3/train/loss/sam3_loss.py:device="cuda" 默认改为 sam3.compat.get_device()sam3/train/masks_ops.py、sam3/perflib/nms.py、sam3/perflib/connected_components.py:.is_cuda → .is_cuda or (hasattr(x, 'is_npu') and x.is_npu)addmm_act NPU 降级处理:CUDA 融合算子 _addmm_activation 在 NPU 无高效实现,原始代码还会断梯度。
适配文件:fused_npu.py
在 sam3/perflib/fused.py 中,NPU 环境下用 linear(mat1) + activation(out) 替代融合算子:
_USE_NPU_ADDMM_FALLBACK = sam3.compat.is_npu_available()
def addmm_act(activation, linear, mat1):
if _USE_NPU_ADDMM_FALLBACK:
out = linear(mat1)
if activation in [torch.nn.functional.relu, torch.nn.ReLU]:
return torch.nn.functional.relu(out)
if activation in [torch.nn.functional.gelu, torch.nn.GELU]:
return torch.nn.functional.gelu(out)
raise ValueError(f"Unexpected activation {activation}")
# CUDA original path...torch.compiler.is_dynamo_compiling 兼容处理:torch 2.1.0 不含此函数。
在 sam3/model/decoder.py 顶部添加猴子补丁:
if not hasattr(torch.compiler, 'is_dynamo_compiling'):
torch.compiler.is_dynamo_compiling = lambda: FalseASCEND_RT_VISIBLE_DEVICES 指定物理卡号)。启动单卡训练(指定物理 NPU 卡6):
source /usr/local/Ascend/ascend-toolkit/ascend-toolkit/set_env.sh
export LD_LIBRARY_PATH=/usr/local/Ascend/driver/lib64:/usr/local/Ascend/driver/lib64/driver:$LD_LIBRARY_PATH
export PATH=/usr/local/python3.10/bin:$PATH
export ASCEND_RT_VISIBLE_DEVICES=6
export MASTER_ADDR=localhost
export MASTER_PORT=29500
export RANK=0
export LOCAL_RANK=0
export WORLD_SIZE=1
cd /root/sam3
python3.10 -m sam3.train.train \
-c configs/coco2017/coco2017_npu_real_train.yaml \
--use-cluster 0 \
--num-gpus 1说明:ASCEND_RT_VISIBLE_DEVICES=6 让进程只看到物理卡6,此时 local_rank=0 映射到 npu:0(即物理卡6)。
COCO 冒烟测试训练配置已创建于 sam3/train/configs/coco2017/coco2017_npu_smoke_test.yaml,关键参数:
accelerator: npu、backend: hccl、device: npu、gpus_per_node: 1load_from_HF: false、checkpoint_path: /home/sam3.ptmax_epochs: 2、limit_ids: 50(仅50张图片做快速验证)
ASCEND_RT_VISIBLE_DEVICES 指定可见卡。--num-gpus 参数控制每节点卡数。torch.multiprocessing.start_processes(spawn 方式)启动多进程,不需要 torchrun。使用4卡训练(指定物理卡4,5,6,7):
source /usr/local/Ascend/ascend-toolkit/ascend-toolkit/set_env.sh
export LD_LIBRARY_PATH=/usr/local/Ascend/driver/lib64:/usr/local/Ascend/driver/lib64/common:/usr/local/Ascend/driver/lib64/driver:$LD_LIBRARY_PATH
export PATH=/usr/local/python3.10/bin:$PATH
export ASCEND_RT_VISIBLE_DEVICES=4,5,6,7
export PYTHONPATH=/root/sam3:$PYTHONPATH
cd /root/sam3
python3.10 -m sam3.train.train \
-c configs/coco2017/coco2017_npu_real_train.yaml \
--use-cluster 0 \
--num-gpus 4注意:
ASCEND_RT_VISIBLE_DEVICES=4,5,6,7 让进程只看到4张卡,逻辑编号 0-3 对应物理卡4-7--num-gpus 4 传递给 cfg.launcher.gpus_per_node,控制 spawn 进程数Sam3VideoPredictorMultiGPU 使用 torch.multiprocessing.start_processes 启动多进程(不是 torchrun)backend: hccl 指定,无需手动设置ASCEND_RT_VISIBLE_DEVICES 和 --num-gpus
COCO 2017 验证集 smoke test 结果(50张图片,2个epoch,NPU:6,bfloat16 AMP):
真实训练结果(CANN 8.1.RC1 + torch_npu 2.1.0.post11,全量5000张COCO图片,10 epochs,NPU:6,bfloat16 AMP):
torchvision::roi_align、aten::_assert_async、torchvision::_roi_align_backward4卡完整训练结果(CANN 8.1.RC1 + torch_npu 2.1.0.post11,NPU:4,5,6,7,bfloat16 AMP,10 epochs 全量完成):
4卡训练 Loss 趋势(o2o,未加权平均值):
| Epoch | Total Loss | loss_ce | loss_bbox | loss_giou | presence_loss | ce_f1 | presence_dec_acc |
|---|---|---|---|---|---|---|---|
| 0 | 137.72 | 0.002579 | 0.027642 | 0.2046 | 0.0119 | 0.2314 | 0.9913 |
| 1 | 127.46 | 0.002429 | 0.023854 | 0.1899 | 0.0101 | 0.3001 | 0.9927 |
| 2 | 120.70 | 0.002269 | 0.022734 | 0.1801 | 0.0096 | 0.3332 | 0.9927 |
| 3 | 126.00 | 0.002479 | 0.023730 | 0.1895 | 0.0090 | 0.3532 | 0.9934 |
| 4 | 123.66 | 0.002412 | 0.023690 | 0.1800 | 0.0093 | 0.3566 | 0.9929 |
| 5 | 123.37 | 0.002422 | 0.023134 | 0.1841 | 0.0087 | 0.3514 | 0.9932 |
| 6 | 118.56 | 0.002334 | 0.021433 | 0.1756 | 0.0091 | 0.3351 | 0.9931 |
| 7 | 115.23 | 0.002268 | 0.021539 | 0.1723 | 0.0081 | 0.3253 | 0.9938 |
| 8 | 119.60 | 0.002335 | 0.022589 | 0.1784 | 0.0082 | 0.3380 | 0.9937 |
| 9 | 112.26 | 0.002262 | 0.021229 | 0.1640 | 0.0081 | 0.3311 | 0.9937 |

Loss 趋势分析:
4卡训练 COCO 检测 AP 结果(val_epoch_freq=5,仅在 Epoch 5 和 Epoch 9 执行验证):
| Epoch | AP | AP_50 | AP_75 | AP_small | AP_medium | AP_large |
|---|---|---|---|---|---|---|
| 5 | 0.620 | 0.796 | 0.676 | 0.461 | 0.668 | 0.794 |
| 9 | 0.629 | 0.804 | 0.686 | 0.477 | 0.679 | 0.802 |
AP 从 Epoch 5 到 Epoch 9 提升 0.009(0.620 → 0.629),训练趋势正确。
训练产出文件:
| 文件 | 路径 | 说明 |
|---|---|---|
| 训练日志 | /root/sam3_logs/coco2017_npu_real_train/logs/coco/log.txt | 完整训练过程日志 |
| 训练统计 | /root/sam3_logs/coco2017_npu_real_train/logs/coco/train_stats.json | 每 epoch 一行 JSON(loss、F1、accuracy) |
| 验证统计 | /root/sam3_logs/coco2017_npu_real_train/logs/coco/val_stats.json | 每 val epoch 一行 JSON(AP 指标) |
| 验证预测 | /root/sam3_logs/coco2017_npu_real_train/dumps/coco/coco_predictions_bbox.json | COCO bbox 预测 JSON(55MB) |
| Loss 曲线图 | /root/loss_curves.png | 6 子图:Total Loss + 4 分项 + ce_f1 + AP |
| Checkpoint(最新) | /root/sam3_logs/coco2017_npu_real_train/checkpoints/checkpoint.pt | 约 10GB |
| Checkpoint(Epoch 5) | /root/sam3_logs/coco2017_npu_real_train/checkpoints/checkpoint_5.pt | 约 10GB |
| Checkpoint(Epoch 10) | /root/sam3_logs/coco2017_npu_real_train/checkpoints/checkpoint_10.pt | 约 10GB |
说明:Total Loss 为加权 core_loss(包含 weight_dict 权重、aux loss、o2m loss 和 o2m_weight 乘数),与 4 个 o2o 分项(loss_ce、loss_bbox、loss_giou、presence_loss)的未加权加和之间存在约 5.4 倍的固定比率(因权重和层数乘数),不影响趋势分析正确性。 单卡 vs 4卡训练对比:
| 指标 | 单卡(NPU:6) | 4卡(NPU:4,5,6,7) |
|---|---|---|
| batch time | 7.0s | 7.31s |
| 每 epoch steps | 5000 | 1250 |
| 每 epoch 时间 | ~10h | ~2.5h |
| 内存 / 卡 | 46 GB | 46 GB |
| 加速比 | 1x | ~4x |
SAM3 推理采用 PyTorch PT 模式直接在 NPU 上运行,无需 ONNX/OM 转换。核心适配手段是 torch_npu.contrib.transfer_to_npu——一行代码即可全局将 .cuda() 重定向为 .npu()、torch.cuda.* 重定向为 torch.npu.*、torch.autocast(device_type="cuda") 自动适配 NPU、torch.distributed.init_process_group(backend="nccl") 自动改为 hccl。
transfer_to_npu(必须在 import torch 之前)。_setup_tf32() 和 get_sdpa_settings() 中 NPU 设备属性没有 major 字段的问题。freqs_cis buffer key 不匹配问题(strict_state_dict_loading=False)。推理适配代码修改清单:
transfer_to_npu 全局重定向:在推理脚本最顶部(import torch 之前)添加一行即可自动处理所有 .cuda() → .npu()、torch.cuda.* → torch.npu.*、autocast(device_type="cuda") → NPU 兼容、backend="nccl" → backend="hccl"。
from torch_npu.contrib import transfer_to_npu # 必须在 import torch 之前注意:transfer_to_npu 会禁用 torch.jit.script 和 torch.jit.script_method,如需启用则不可使用此方式。
_setup_tf32() NPU 保护:transfer_to_npu 使 torch.cuda.is_available() 返回 True,但 NPU 设备属性没有 major 字段,需先检测 NPU 再跳过 CUDA 逻辑。
在 sam3/model_builder.py 中修改 _setup_tf32():
def _setup_tf32() -> None:
try:
import torch_npu
if torch.npu.is_available():
return
except ImportError:
pass
if torch.cuda.is_available():
device_props = torch.cuda.get_device_properties(0)
if device_props.major >= 8:
torch.backends.cuda.matmul.allow_tf32 = True
torch.backends.cudnn.allow_tf32 = Trueget_sdpa_settings() NPU 优先:transfer_to_npu 使 torch.cuda.is_available() 返回 True,需先检测 NPU 跳过 CUDA major 字段检查。
在 sam3/model/model_misc.py 中修改 get_sdpa_settings():
def get_sdpa_settings():
try:
import torch_npu
is_npu = torch.npu.is_available()
except ImportError:
is_npu = False
if is_npu:
old_gpu = True
use_flash_attn = False
math_kernel_on = True
elif torch.cuda.is_available():
old_gpu = torch.cuda.get_device_properties(0).major < 7
use_flash_attn = torch.cuda.get_device_properties(0).major >= 8
...
else:
...freqs_cis buffer key 不匹配:NPU 上 _USE_ROPE_REAL=True,模型注册 freqs_cis_real/freqs_cis_imag buffer,但权重文件存储 freqs_cis(complex 格式)。这些 buffer 在运行时由 compute_axial_cis 重新生成,不影响推理正确性。
在 sam3/model_builder.py 的 build_sam3_video_model 中,NPU 推理时自动关闭 strict loading:
if _USE_ROPE_REAL and strict_state_dict_loading:
strict_state_dict_loading = FalseFA3 在 NPU 上禁用:Flash Attention 3 使用 NVIDIA Hopper float8 格式,NPU 不支持。在 sam3/sam/transformer.py 中修改:
if self.use_fa3 and not sam3.compat.is_npu_available():
from sam3.perflib.fa3 import flash_attn_func
...
else:
out = F.scaled_dot_product_attention(q, k, v, dropout_p=dropout_p)同时删除 torch.backends.cuda.enable_flash_sdp(True) 等 CUDA-specific SDPA 控制(NPU 的 SDPA 由 CANN kernels 提供)。
SAM3 推理链路拆分为 3 个独立 ONNX 模型:Image Encoder、Text Encoder、Grounding Head。每个模型单独导出 ONNX 后通过 ATC 转换为 OM,最终以纯 OM 方式在 NPU 上链式推理。
关键适配:不导入 torch_npu,通过动态修补(monkey-patch).cuda() 和 torch.nn.Module.cuda() 使模型保持在 CPU 上,修补 torch.compiler.is_dynamo_compiling。
导出脚本核心逻辑:
torch.cuda.is_available = lambda: False
def cuda_to_cpu(self, device=None, non_blocking=False):
return self
torch.Tensor.cuda = cuda_to_cpu
torch.nn.Module.cuda = cuda_to_cpu
torch.compiler.is_dynamo_compiling = lambda: Falsebackbone.forward_image() 输出封装为 7 个输出 tensor。class ImageEncoderONNXWrapper(torch.nn.Module):
def forward(self, image):
backbone_out = self.model.backbone.forward_image(image)
vision_features = backbone_out["vision_features"]
vision_pos_enc = backbone_out["vision_pos_enc"]
backbone_fpn = backbone_out["backbone_fpn"]
return (vision_features,
vision_pos_enc[0], vision_pos_enc[1], vision_pos_enc[2],
backbone_fpn[0], backbone_fpn[1], backbone_fpn[2])torch.onnx.export(
wrapper, (image_tensor,), ONNX_PATH,
input_names=["image"],
output_names=["vision_features", "vision_pos_enc_0", "vision_pos_enc_1",
"vision_pos_enc_2", "backbone_fpn_0", "backbone_fpn_1", "backbone_fpn_2"],
opset_version=17, do_constant_folding=True,
)/root/sam3_image_encoder.onnx(1.7GB),输入 image:1,3,1008,1008,输出 7 个 tensor。encoder(token_ids) + resizer(text_memory) 流程,输出 attention_mask(padding mask)和 text_memory_resized。class TextEncoderONNXWrapper(torch.nn.Module):
def forward(self, token_ids):
_, text_memory = self.encoder(token_ids)
attention_mask = (token_ids == 0) # padding mask: 1=padding, 0=real token
text_memory_seq = text_memory.transpose(0, 1)
text_memory_resized = self.resizer(text_memory_seq)
return attention_mask.to(torch.float32), text_memory_resized注意:attention_mask = (token_ids == 0) 是 padding mask(True 表示应忽略的 padding 位置),不是 attention mask(True 表示应关注的 token)。Grounding Head 中将其作为 prompt_key_padding_mask 使用。
/root/sam3_text_encoder.onnx(1.4GB),输入 token_ids:1,32(int64),输出 attention_mask:1,32(float32)+ text_memory_resized:32,1,256(float32)。F.linear output_dim=1 崩溃 bug(详见问题排查第 18 条)。import torch.nn.functional as F
original_linear = F.linear
def patched_linear(input, weight, bias=None):
if weight.shape[0] == 1:
weight_padded = torch.cat([weight, torch.zeros(1, weight.shape[1], device=weight.device, dtype=weight.dtype)], dim=0)
bias_padded = torch.cat([bias, torch.zeros(1, device=bias.device, dtype=bias.dtype)], dim=0) if bias is not None else None
result = original_linear(input, weight_padded, bias_padded)
return result[..., :1]
return original_linear(input, weight, bias)
F.linear = patched_linearimport 修复:inverse_sigmoid 应从 sam3.model.model_misc 导入(不是 sam3.model.data_misc);box_cxcywh_to_xyxy 应从 sam3.model.box_ops 导入。
Wrapper 设计:将 Transformer Encoder Fusion + Decoder + scoring + bbox_head + segmentation_head 封装为单模型。输入 6 个 tensor(因 num_feature_levels=1,ONNX 自动消除了未使用的 vision_pos_enc_0/1)。
class GroundingHeadONNXWrapper(torch.nn.Module):
def forward(self, backbone_fpn_0, backbone_fpn_1, backbone_fpn_2,
vision_pos_enc_2, text_memory_resized, text_attention_mask_float):
# encoder → decoder → scoring → bbox → seg_head
return pred_logits, pred_boxes, pred_masks, presence_logit_dec/root/sam3_grounding_head.onnx(97.4MB),6 个输入,4 个输出:
pred_logits:6,1,200,1(6 层 decoder 输出,取最后一层 [5])pred_boxes:6,1,200,4(同上,取 [5])pred_masks:1,200,288,288(ONNX 动态维度标记为 [0,0,0,0])presence_logit_dec:6,1,1source /usr/local/Ascend/ascend-toolkit/ascend-toolkit/set_env.sh
export LD_LIBRARY_PATH=/usr/local/Ascend/driver/lib64/driver:/usr/local/Ascend/ascend-toolkit/ascend-toolkit/latest/devlib:/usr/local/Ascend/ascend-toolkit/ascend-toolkit/latest/lib64:/usr/local/Ascend/ascend-toolkit/ascend-toolkit/latest/hccl/lib64:$LD_LIBRARY_PATHatc --framework=5 --soc_version=Ascend910B3 \
--model=/root/sam3_image_encoder.onnx \
--output=/root/sam3_om_static \
--input_shape="image:1,3,1008,1008" \
--input_format=NCHW
# 结果:sam3_om_static_linux_aarch64.om(1003MB)atc --framework=5 --soc_version=Ascend910B3 \
--model=/root/sam3_text_encoder.onnx \
--output=/root/sam3_text_encoder \
--input_shape="token_ids:1,32" \
--input_format=NCHW
# 结果:sam3_text_encoder_linux_aarch64.om(746MB)atc --framework=5 --soc_version=Ascend910B3 \
--model=/root/sam3_grounding_head.onnx \
--output=/root/sam3_grounding_head \
--input_shape="backbone_fpn_0:1,256,288,288;backbone_fpn_1:1,256,144,144;backbone_fpn_2:1,256,72,72;vision_pos_enc_2:1,256,72,72;text_memory_resized:32,1,256;text_attention_mask_float:1,32" \
--input_format=NCHW
# 结果:sam3_grounding_head_linux_aarch64.om(112MB)ATC 警告信息(不影响推理正确性,仅影响部分算子性能):Op [/decoder/layers.*/ca_text/Mod] does not hit the high-priority operator information library。
纯 OM 推理使用 acl API 链式调用 3 个 OM 模型,不依赖 PyTorch 或 torch_npu。
acl.init() + acl.rt.set_device(0) 初始化 NPU。acl.mdl.load_from_file)。[1,3,1008,1008] float32 格式;文本 BPE 分词 → [1,32] int64 格式。acl.rt.malloc(size, policy):2 个参数,policy=0(ACL_MEM_MALLOC_HUGE_FIRST)。acl.rt.memcpy(dst, dst_size, src, src_size, kind):5 个参数,kind=1(主机到设备),kind=2(设备到主机)。acl.create_data_buffer(dev, size):返回单个整数。acl.util.numpy_to_ptr 将 numpy 数组转换为指针(bytes_to_ptr 是新版 API)。acl.init() 需先执行,推理后不调用 acl.finalize(),直至最后结束。推理脚本路径:pure_om_inference_trained.py
运行命令:
source /usr/local/Ascend/ascend-toolkit/ascend-toolkit/set_env.sh
export LD_LIBRARY_PATH=/usr/local/Ascend/driver/lib64/driver:/usr/local/Ascend/ascend-toolkit/ascend-toolkit/latest/devlib:/usr/local/Ascend/ascend-toolkit/ascend-toolkit/latest/lib64:/usr/local/Ascend/ascend-toolkit/ascend-toolkit/latest/hccl/lib64:$LD_LIBRARY_PATH
/usr/local/python3.10/bin/python3.10 /root/pure_om_inference_trained.py纯 OM 推理脚本 /root/pure_om_inference.py 第 32 行定义了检测类别:
TEXT_PROMPT = "person"修改 TEXT_PROMPT 即可改变检测目标,例如:
TEXT_PROMPT = "car" → 检测汽车TEXT_PROMPT = "dog" → 检测狗TEXT_PROMPT = "person car" → 同时检测人和汽车(一条文本中包含多个类别词)注意:SAM3 是 grounding 检测模型,每次推理对应一条文本描述。每次只能检测当前 TEXT_PROMPT 中描述的类别。如需做完整的 COCO 80 类评估,需对每个类别分别运行一次 Text Encoder OM + Grounding Head OM(Image Encoder OM 只需运行一次,然后复用其 7 个输出),汇总所有类别的检测结果。
| 模型 | 输入 | 输出 |
|---|---|---|
| Image Encoder | image:1,3,1008,1008(float32) | vision_features:1,256,72,72、vision_pos_enc_0/1/2、backbone_fpn_0/1/2(7个,均为float32) |
| Text Encoder | token_ids:1,32(int64) | attention_mask:1,32(float32,padding mask)、text_memory_resized:32,1,256(float32) |
| Grounding Head | backbone_fpn_0/1/2、vision_pos_enc_2、text_memory_resized、text_attention_mask_float(6个) | pred_logits:6,1,200,1、pred_boxes:6,1,200,4、pred_masks:1,200,288,288、presence_logit_dec:6,1,1(4个) |
图片 → [Image Encoder OM] → backbone_fpn_0/1/2, vision_pos_enc_2
↓
文本 → [Text Encoder OM] → text_memory_resized, attention_mask_float
↓
[Grounding Head OM] → pred_logits, pred_boxes, pred_masks, presence_logit_dec
↓
后处理 → 检测框 + 分割maskPT 模式 NPU 推理结果(CANN 8.1.RC1 + torch_npu 2.1.0.post11,NPU:0,bfloat16 AMP):
start_session(单张图片)耗时约 0.1 秒add_prompt(文本提示"person")返回 mask 结果,mask 非零像素数约 11789freqs_cis 缓冲区,运行时重新生成,不影响推理):detector.backbone.vision_backbone.trunk.blocks.*.attn.freqs_cis_real/imagfreqs_cis,已被 strict=False 忽略):detector.backbone.vision_backbone.trunk.blocks.*.attn.freqs_cistorchvision::roi_align、aten::_assert_async纯 OM 推理结果(acl API,CANN 8.1.RC1,Ascend 910B3):
测试配置:COCO 2017 图片 000000000139.jpg(640x426),文本提示 "person"
| 模型 | 推理耗时 |
|---|---|
| Image Encoder OM | 0.72s |
| Text Encoder OM | 0.02s |
| Grounding Head OM | 0.04s |
| 总推理耗时 | 0.78s(不含模型加载) |
检测结果:2 个 person 目标
| 检测目标 | Score | Box (xyxy, 原图坐标) |
|---|---|---|
| Query 25 | 0.9428 | [413.1, 157.5, 464.4, 295.1] |
| Query 55 | 0.9080 | [384.5, 172.1, 400.5, 206.9] |
OM 与混合 OM+PT 推理对比:
| 方案 | 检测数 | 最高 Score | 推理方式 |
|---|---|---|---|
| 纯 OM(3 个 OM 链式 acl) | 2 | 0.9428 | acl API,无 PyTorch 依赖 |
| 混合 OM+PT(Image Encoder OM + PT Grounding) | 2 | 0.929 | Image Encoder OM + PyTorch 推理 |
| 纯 PT(PyTorch NPU) | 2 | ~0.93 | 全量 PyTorch 推理 |
三种方案检测结果一致(均为 2 个 person),score 差异 < 0.02,在 float16 舍入精度范围内。
输出文件保存在 /root/pure_om_outputs/:
img_enc_*.npy:Image Encoder 的 7 个输出txt_enc_*.npy:Text Encoder 的 2 个输出grd_head_*.npy:Grounding Head 的 4 个输出result.jpg:可视化结果图片(检测框 + mask 叠加)scores.npy、boxes_xyxy.npy:后处理结果训练保存的 checkpoint(如 checkpoint_10.pt)需额外处理才能正确导出 ONNX/OM 并推理。
训练 checkpoint 的 state_dict key 没有 detector. 前缀(DDP unwrap 后去除),而原始 sam3.pt 的 key 有 detector. 前缀。_load_checkpoint 函数只处理含 detector. 的 key,导致训练 checkpoint 的所有权重被忽略。
| 来源 | Key 示例 |
|---|---|
原始 sam3.pt | detector.backbone.language_backbone.encoder.positional_embedding |
训练 checkpoint_10.pt | backbone.language_backbone.encoder.positional_embedding |
影响:positional_embedding 等 key 被视为缺失键,模型用 torch.empty 初始化(含 NaN),导致 Text Encoder 输出全为 NaN。
在导出脚本中手动加载 checkpoint 并修复 key 前缀,不依赖 _load_checkpoint:
CKPT = '/root/sam3_logs/coco2017_npu_real_train/checkpoints/checkpoint_10.pt'
model = build_sam3_image_model(
bpe_path='.../bpe_simple_vocab_16e6.txt.gz',
checkpoint_path=None,
load_from_HF=False,
device='cpu',
)
ckpt_data = torch.load(CKPT, map_location='cpu', weights_only=True)
raw_state = ckpt_data['model'] if 'model' in ckpt_data else ckpt_data
fixed_state = {}
for k, v in raw_state.items():
if 'detector.' not in k:
fixed_state['detector.' + k] = v
else:
fixed_state[k] = v
sam3_ckpt = {k.replace("detector.", ""): v for k, v in fixed_state.items() if "detector" in k}
missing, unexpected = model.load_state_dict(sam3_ckpt, strict=False)
print(f"Loaded: missing={len(missing)}, unexpected={len(unexpected)}")
model.eval()注意:missing 中应只有 freqs_cis_real/freqs_cis_imag(运行时重新生成,不影响推理)。
训练后 checkpoint ONNX 导出需以下 patch(与原始权重导出相同,但额外需要 key 前缀修复):
torch.cuda.is_available = lambda: False:禁用 CUDA 检测.cuda() → 保持在 CPU:torch.Tensor.cuda = lambda self, *a, **kw: self、torch.nn.Module.cuda = lambda self, *a, **kw: self.npu() → 保持在 CPU:torch.Tensor.npu = lambda self, *a, **kw: self、torch.nn.Module.npu = lambda self, *a, **kw: selftorch.Tensor.to() 拦截 npu/cuda:拦截 device='npu'/device='cuda' 参数,返回 CPU tensorsam3.compat.get_device = lambda: 'cpu':设备选择强制 CPUsam3.compat.is_npu_available = lambda: False:禁用 NPU 检测(Grounding Head 导出必需)device='cpu' 参数:build_sam3_image_model(..., device='cpu')torch.compiler.is_dynamo_compiling = lambda: False:torch 2.1.0 兼容F.linear padding patch(Grounding Head 导出必需):oneDNN output_dim=1 bug 规避方法detector. 前缀与原始权重 OM 转换命令相同,只需替换输入 ONNX 文件路径:
# Image Encoder
atc --framework=5 --soc_version=Ascend910B3 \
--model=/root/sam3_image_encoder_trained.onnx \
--output=/root/sam3_image_encoder_trained \
--input_shape="image:1,3,1008,1008" --input_format=NCHW
# 结果:sam3_image_encoder_trained_linux_aarch64.om(1004MB)
# Text Encoder
atc --framework=5 --soc_version=Ascend910B3 \
--model=/root/sam3_text_encoder_trained.onnx \
--output=/root/sam3_text_encoder_trained \
--input_shape="token_ids:1,32" --input_format=NCHW
# 结果:sam3_text_encoder_trained_linux_aarch64.om(746MB)
# Grounding Head
atc --framework=5 --soc_version=Ascend910B3 \
--model=/root/sam3_grounding_head_trained.onnx \
--output=/root/sam3_grounding_head_trained \
--input_shape="backbone_fpn_0:1,256,288,288;backbone_fpn_1:1,256,144,144;backbone_fpn_2:1,256,72,72;vision_pos_enc_2:1,256,72,72;text_memory_resized:32,1,256;text_attention_mask_float:1,32" \
--input_format=NCHW
# 结果:sam3_grounding_head_trained_linux_aarch64.om(112MB)
测试配置:COCO 2017 图片 000000000139.jpg(640x426),文本提示 "person",训练 checkpoint 为 checkpoint_10.pt(10 轮次)。
| 模型 | 推理耗时 |
|---|---|
| Image Encoder OM | 0.72s |
| Text Encoder OM | 0.02s |
| Grounding Head OM | 0.04s |
检测结果:2 个 person 目标
| 检测目标 | Score | Box (xyxy, 原图坐标) |
|---|---|---|
| Query 25 | 0.8942 | [409.3, 157.0, 465.7, 296.5] |
| Query 166 | 0.8604 | [384.4, 172.1, 401.2, 206.9] |
与原始 OM 推理对比:
| 方案 | 检测数 | 最高 Score | Box |
|---|---|---|---|
| 原始 OM(sam3.pt) | 2 | 0.9428 | [413.1, 157.5, 464.4, 295.1] + [384.5, 172.1, 400.5, 206.9] |
| 训练 OM(checkpoint_10.pt) | 2 | 0.8942 | [409.3, 157.0, 465.7, 296.5] + [384.4, 172.1, 401.2, 206.9] |
两者检测目标一致(均为 2 个 person),框坐标接近,得分略有下降(0.89 vs 0.94)。这符合预期——在 COCO 上训练 10 轮次的检测 AP=0.629,而原始预训练权重在 COCO 上的 AP 通常更高(预训练数据量更大)。
推理脚本路径:/root/pure_om_inference_trained.py(指向训练后的 OM 文件,输出目录 pure_om_outputs_trained)。
现象:调用 Add、Mul(标量乘)、AsStrided 等算子时报错 Op xxx does not has any binary 或 FlashAttention AsStrided 内核执行失败。
可能原因:未安装 Ascend-cann-kernels 包,缺少预编译算子二进制文件。
处理方法:安装 CANN kernels 包(推荐 8.1.RC1,包含 8869 个预编译算子),安装后不再需要 jit_compile=True。如使用 CANN 8.0.RC2(无 kernels 包),则需开启 JIT 编译模式:torch.npu.set_compile_mode(jit_compile=True)。
安装 CANN 8.1.RC1 kernels 步骤:
curl -L -o /tmp/Ascend-cann-kernels-910b_8.1.RC1_linux-aarch64.zip \
"https://ascend-repo.obs.cn-east-2.myhuaweicloud.com/CANN/CANN%208.1.RC1/Ascend-cann-kernels-910b_8.1.RC1_linux-aarch64.zip"python3 -c "import zipfile; zipfile.ZipFile('/tmp/Ascend-cann-kernels-910b_8.1.RC1_linux-aarch64.zip').extractall('/tmp/kernels_stage1')"
python3 -c "import zipfile; zipfile.ZipFile('/tmp/kernels_stage1/Ascend-cann-kernels-910b_8.1.RC1_linux-aarch64.zip').extractall('/tmp/kernels_stage2')"chmod +x /tmp/kernels_stage2/Ascend-cann-kernels-910b_8.1.RC1_linux-aarch64.run
/tmp/kernels_stage2/Ascend-cann-kernels-910b_8.1.RC1_linux-aarch64.run --install --quiet --force校验命令:
python3.10 -c "
import torch, torch_npu
torch.npu.set_device(0)
# AsStrided 是之前缺失的算子
x = torch.randn(4, 4, device='npu:0')
y = torch.as_strided(x, (2, 2), (4, 1), 0)
print('AsStrided OK:', y.shape)
# FlashAttention
q = torch.randn(1, 8, 64, 128, device='npu:0')
k = torch.randn(1, 8, 64, 128, device='npu:0')
v = torch.randn(1, 8, 64, 128, device='npu:0')
out = torch.nn.functional.scaled_dot_product_attention(q, k, v)
print('SDPA OK:', out.shape)
"aclrtCreateContext 失败,日志提示 No module named 'decorator'、No module named 'scipy'、No module named 'attr'。pip3.10 install decorator scipy attr attrs psutil astunparse cffi pathlib2 protobuf。python3.10 -c "import decorator, scipy, attr, attrs, psutil, astunparse, cffi, pathlib2, protobuf; print('OK')"torch.nn.attention 模块ModuleNotFoundError: No module named 'torch.nn.attention'。torch.nn.attention(含 sdpa_kernel、SDPBackend)在 PyTorch 2.2 才引入。sam3/compat/torch_nn_attention.py,在 decoder.py 和 vl_combiner.py 中用 try/except 导入。python3.10 -c "from sam3.compat.torch_nn_attention import sdpa_kernel, SDPBackend; print('OK')"