基于 2toINF/X-VLA-WidowX 的 Ascend NPU 服务化推理适配。X-VLA 是清华 THU-AIR 提出的 Soft-Prompted Transformer 跨本体视觉-语言-动作(VLA)模型,本文档记录了将该模型从 PyTorch 生态迁移到华为昇腾 NPU 并对外提供 HTTP 服务的完整过程。
X-VLA(Zheng et al., 2025, arXiv:2510.10274)采用 Florence-2-large 视觉语言编码器 + SoftPromptedTransformer flow-matching 动作去噪器 的架构,参数规模约 0.9B。其核心思想是为每个机器人本体(embodiment)引入可学习的 soft prompt,使模型能够跨本体泛化。本仓库权重为 WidowX 机械臂版本,动作模式为 ee6d(末端执行器 6D 位姿 + 夹爪,共 20 维),一次性输出 30 步动作序列。
模型组成:
| 组件 | 说明 |
|---|---|
| Florence2 视觉语言编码器 | DaViT 视觉塔(224x224 输入)+ 12 层语言编码器 |
| SoftPromptedTransformer | 24 层标准 Transformer,flow-matching 迭代去噪(默认 10 步) |
Action Hub (ee6d) | 动作空间定义、掩码规则、前后处理(夹爪通道 sigmoid) |
输入:多视角图像(最多 3 路,224x224)+ 自然语言指令 + 机械臂 proprio(20 维)+ 本体 domain_id。
输出:[30, 20] 的动作轨迹张量(位置 xyz、6D 旋转、夹爪开关等)。
适配结论:该模型为 flow-matching 非自回归策略,不属于 vLLM-Ascend 支持的生成式 LLM/VLM 范畴,
registry亦无对应架构。因此采用 torch_npu + 标准库单线程 HTTPServer 方案完成服务化。
| 项 | 配置 |
|---|---|
| 操作系统 | Linux aarch64 (kernel 5.10.0) |
| NPU 硬件 | Ascend 910 (Atlas 800 A2 系列,双卡,本适配使用物理 NPU1) |
| CANN | cann-8.5.1 / ascend-toolkit 25.5.5 |
| Python | 3.11.14 |
| PyTorch | 2.9.0 |
| torch_npu | 2.9.0.post1 |
| transformers | 4.57.6 |
| vllm / vllm-ascend | 0.18.0(本模型不适用) |
| timm / einops | 1.0.28 / 0.8.2 |
| 依赖 | safetensors, accelerate, Pillow, numpy, json_numpy, opencv-python-headless |
模型权重从 GitCode 镜像(ai.gitcode.com/hf_mirrors/2toINF/X-VLA-WidowX)拉取,model.safetensors(3.52GB)经 sha256 校验(fb5e3d5822...6c4a7f)通过。
# 1. 安装依赖(昇腾环境已含 torch_npu 时跳过 torch 相关行)
pip install -r requirements.txt
# 2. 启动服务(--device 1 为物理 NPU1,经 ASCEND_RT_VISIBLE_DEVICES 映射为进程内 npu:0)
python3 inference.py serve \
--model-dir /tmp/xvla_ascend/model_adapted \
--port 8388 \
--device 1启动日志(关键阶段):
2026-08-16 14:56:11 INFO Loading config ...
2026-08-16 14:56:11 INFO Loading XVLA model (fp32) ...
2026-08-16 14:56:30 INFO Trimmed unused Florence2 decoder/lm_head
2026-08-16 14:56:30 INFO Model loaded on npu:0 in 18.9s
2026-08-16 14:56:30 INFO Starting HTTP server on 0.0.0.0:8388 ...服务端点:
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /health | 存活探针 |
| POST | /act | 输入图像 + 指令,返回 {action: [30,20], action_shape, elapsed_ms} |
/act 请求体示例:
{
"image0": "<base64 编码的 JPEG/PNG 图片字节>",
"language_instruction": "pick up the red cube and place it in the green bowl",
"proprio": [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
"domain_id": 0,
"steps": 10
}image0 亦支持本地文件路径或 [H,W,3] 数组;可选 image1 / image2 提供第二、三视角。
# 健康检查
curl -s http://127.0.0.1:8388/health
# {"status": "ok", "model": "X-VLA-WidowX", "device": "npu:0"}
# 动作推理(assets/test_scene.png 为 224x224 合成桌面场景图)
python3 - <<'PY'
import base64, json, urllib.request
b64 = base64.b64encode(open('/tmp/xvla_ascend/assets/test_scene.png','rb').read()).decode()
payload = {"image0": b64, "language_instruction": "pick up the red cube",
"proprio": [0.0]*20, "domain_id": 0, "steps": 10}
req = urllib.request.Request('http://127.0.0.1:8388/act',
data=json.dumps(payload).encode(), headers={'Content-Type':'application/json'})
resp = json.loads(urllib.request.urlopen(req).read())
print("action_shape:", resp["action_shape"]) # [30, 20]
print("elapsed_ms:", resp["elapsed_ms"])
PY实测输出(首请求含 TBE 算子编译):
action_shape: [30, 20]
elapsed_ms: 497.6后续请求(已 warmup)单次约 135 ms。
| 指标 | 值 |
|---|---|
| 模型加载(fp32,含权重搬运) | 9.9 ~ 19.2 s |
forward_vlm(单视角编码) | NPU 287 ms / CPU 8348 ms(约 29x) |
generate_actions 10 步(warm) | 约 135 ms / 请求 |
| 串行吞吐(单线程 HTTPServer) | 约 1.1 req/s |
| AICore 利用率(推理期) | 55% ~ 74%(瞬时峰值) |
| 进程 HBM 占用(npu-smi 实测 PID) | 约 5140 MB |
| 精度(NPU vs CPU,见下节) | Pearson >= 0.99998 |
npu-smi 采集(推理期间快照,物理 NPU1 / Phy-7):
| 3 Ascend910 | OK | - 46 0 / 0 |
| 1 7 | 0000:0B:00.0 | 55 0 / 0 48517/ 65536 |
| 3 1 | 158809 | python3 | 5140 |AICore 时序(0.5s 采样,推理期命中非零值):62, 59, 74, 66, 60, 69, 67, 69,峰值 74%。
说明:单线程 HTTPServer + 全局锁是为规避 uvicorn 线程池在共享 NPU 上引发的 TBE 编译竞争,保证数值稳定;若需更高吞吐可在确认算子已编译后开启并发。
以 CPU fp32 官方权重 为基线,NPU(fp32)同输入同随机种子对比,Pearson 相关系数与最大绝对差如下:
| 对比项 | Pearson | MaxDiff | CPU 耗时 | NPU 耗时 |
|---|---|---|---|---|
forward_vlm 编码特征 [1,100,1024] | 0.99998976 | 6.53e-02 | 8348 ms | 287 ms |
flow-matching 动作生成(固定噪声,10 步)[1,30,20] | 0.99999770 | 5.72e-03 | 56871 ms | 222 ms |
动作输出样例(前 5 维,CPU vs NPU):
cpu: [0.032862, -0.005764, 0.014086, 0.301091, 0.228166]
npu: [0.032862, -0.005761, 0.014090, 0.301594, 0.227669]动作统计:CPU mean=0.111604 / std=0.318262,NPU mean=0.111683 / std=0.318202,完全一致量级。夹爪通道(idx 9/19)经 postprocess sigmoid 映射至 (0,1),符合 ee6d 动作空间语义。
说明:生成式推理含
torch.randn初始噪声,逐位对比时注入固定噪声x1以隔离 RNG 差异;编码器路径为确定性对比。
vllm serve 服务化;采用 torch_npu 直接推理。_attn_implementation 必设:模型 Florence2 子配置(florence_config 与 florence_config.text_config)默认 _attn_implementation=None,直接加载会触发 FLORENCE2_ATTENTION_CLASSES[None] KeyError,需显式设为 "eager"。lm_head/decoder 延迟释放:官方 modeling_xvla.py 在 __init__ 中直接 del lm.lm_head / lm.model.decoder,会在 transformers 权重加载的 tie_weights() 阶段抛 AttributeError。适配版改为加载完成后调用 trim_lm_unused() 释放。/opt/atomgit 目录配额紧张,模型权重与工作目录均放置于 /tmp。fb5e3d5822...6c4a7f 校验;推理前确保 model.safetensors 在位。TBE_PARALLEL_COMPILER=0、独立 ASCEND_CACHE_PATH,首请求含编译延迟。