> ## Documentation Index
> Fetch the complete documentation index at: https://phyai.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# LingBot V2 动作推理

> 在 PhyAI 中运行 LingBot V2 动作推理。

# LingBot V2 动作推理

## 概述

LingBot V2 是一个 VLA 模型。它接收相机图像、任务指令和 `state`，预测后续的 `action` 序列。

先运行仓库中提供的示例，确认环境和模型能够正常工作，再替换成自己的图像与 `state`。

| 项目 | 配置 |
| - | - |
| 模型权重 | LingBot V2 RoboTwin checkpoint |
| Processor | Qwen3-VL-4B-Instruct |
| 输入 | 三路 RGB 图像、任务指令和 `state` |
| 输出 | `shape` 为 `(1, 50, 55)` 的 `action tensor` |
| 输出类型 | CPU 上的 `float32` |
| 运行方式 | 单 GPU、BF16 |
| 示例脚本 | `examples/lingbot_v2/run_lingbot_v2.py` |

## 架构

一次推理的主要流程如下：

```text theme={null}
图像 + 任务指令 + 机器人状态
                    ↓
LingBotV2Processor.preprocess()
                    ↓
LingBotV2Request
                    ↓
Engine.step()
                    ↓
LingBotV2Processor.postprocess()
                    ↓
CPU float32 动作张量
```

`LingBotV2Processor` 将图像、任务文本和 `state` 转换为模型所需的输入格式。

`Engine` 通过 `lingbot_v2` 插件加载模型。模型首先处理视觉和语言输入，再使用 Euler 方法迭代生成 `action` 序列。

`kernel_policy.yaml` 用于选择模型运行时采用的算子实现。示例使用仓库中提供的配置。

## 运行

### 1. 安装 PhyAI

本指南使用 Linux、Python 3.12 或更高版本、`uv`，以及支持 BF16 的 NVIDIA GPU。

```bash theme={null}
git clone https://github.com/mingti-org/phyai.git
cd phyai

uv sync
source .venv/bin/activate
```

检查 PyTorch 是否能够访问 GPU：

```bash theme={null}
python -c "import torch; print(torch.cuda.is_available())"
```

预期输出：

```text theme={null}
True
```

### 2. 下载模型资源

下载 LingBot V2 的 RoboTwin checkpoint：

```bash theme={null}
hf download robbyant/lingbot-vla-v2-6b-robotwin \
  --local-dir checkpoints/lingbot-vla-v2-6b-robotwin
```

后续步骤使用以下目录作为模型路径：

```text theme={null}
checkpoints/lingbot-vla-v2-6b-robotwin/
└── checkpoints/
    └── global_step_50000/
        └── hf_ckpt/
```

该目录应包含 `config.json`、`model.safetensors.index.json` 和多个 `.safetensors` 文件。

下载 Qwen3-VL Processor 所需的资源：

```bash theme={null}
hf download Qwen/Qwen3-VL-4B-Instruct \
  --include "*.json" "*.jinja" "*.txt" "*.model" \
  --local-dir checkpoints/Qwen3-VL-4B-Instruct
```

这条命令只下载 Processor 和 tokenizer 所需的文件，不需要下载 Qwen 模型权重。

### 3. 配置路径

打开 `examples/lingbot_v2/run_lingbot_v2.py`，修改文件开头的两个路径：

```python theme={null}
CHECKPOINT_DIR = Path(
    "checkpoints/lingbot-vla-v2-6b-robotwin/"
    "checkpoints/global_step_50000/hf_ckpt"
)
PROCESSOR_DIR = "checkpoints/Qwen3-VL-4B-Instruct"
```

这里的路径均相对于 PhyAI 仓库根目录。

### 4. 运行示例

先检查 GPU 状态：

```bash theme={null}
nvidia-smi
```

选择一张 GPU 运行示例：

```bash theme={null}
CUDA_VISIBLE_DEVICES=0 python examples/lingbot_v2/run_lingbot_v2.py
```

示例使用三张合成图像、任务指令 `pick up the cup`，以及全零 `state` 向量。它会加载模型、执行一次推理、打印结果，最后关闭 `Engine`。

默认输出 `shape` 如下：

```text theme={null}
Action shape: (1, 50, 55)
```

三个维度依次表示 `batch size`、`action` 时间步数和每个时间步的 `action` 维度。在这个示例中，一次观测对应 50 个 `action` 时间步，每个时间步包含 55 个模型 `action` 值。

示例未提供数据集统计信息，因此输出还没有被转换成特定机器人使用的控制值。

## 端到端示例

下面的代码展示完整的 Python 调用过程。将其保存为仓库根目录下的 `run_lingbot_demo.py`，然后运行：

```bash theme={null}
python run_lingbot_demo.py
```

```python theme={null}
from pathlib import Path

import torch

from phyai.engine import Engine, EngineArgs
from phyai.engine_config import DeviceConfig, EngineConfig, RuntimeConfig
from phyai.kernel.config import KernelConfig
from phyai.models.lingbot_v2 import (
    LingBotV2Args,
    LingBotV2Request,
    LingBotVLA2Config,
)
from phyai.utils import load_config
from phyai_utils_tools.models.lingbot_v2 import LingBotV2Processor


checkpoint_dir = Path(
    "checkpoints/lingbot-vla-v2-6b-robotwin/"
    "checkpoints/global_step_50000/hf_ckpt"
)
processor_dir = "checkpoints/Qwen3-VL-4B-Instruct"

config = load_config(checkpoint_dir, LingBotVLA2Config)

processor = LingBotV2Processor(
    processor_name=processor_dir,
    num_channels=config.vision.in_channels,
    patch_vector_dim=config.vision.patch_vector_dim,
    tokenizer_max_length=config.tokenizer_max_length,
    max_state_dim=config.max_state_dim,
    action_dim=config.action_dim,
    device="cuda",
    params_dtype=torch.bfloat16,
)

observation = {
    "images": [
        torch.zeros(
            1,
            config.vision.in_channels,
            224,
            224,
            dtype=torch.uint8,
        )
        for _ in range(3)
    ],
    "task": ["pick up the cup"],
    "state": torch.zeros(1, config.max_state_dim),
}

processed = processor.preprocess(observation)
request = LingBotV2Request(**vars(processed))

max_vision_tokens = (
    processed.pixel_values.shape[2] // config.vision.spatial_merge_unit
)

engine = Engine(
    EngineArgs(
        plugin="lingbot_v2",
        plugin_args=LingBotV2Args(
            checkpoint_dir=checkpoint_dir,
            config=config,
            max_vision_tokens_per_image=max_vision_tokens,
        ),
        config=EngineConfig(
            kernel=KernelConfig(
                config_path="examples/lingbot_v2/kernel_policy.yaml",
            ),
            device=DeviceConfig(
                target="cuda",
                params_dtype=torch.bfloat16,
            ),
            runtime=RuntimeConfig(
                allow_tf32=True,
                use_cuda_graph=True,
            ),
        ),
    )
)

try:
    with torch.inference_mode():
        raw_actions = engine.step(request)
        actions = processor.postprocess(raw_actions)

    print("Action shape:", tuple(actions.shape))
    print("Action dtype:", actions.dtype)
    print("First action:", actions[0, 0].tolist())
finally:
    engine.close()
```

核心调用如下：

```python theme={null}
processed = processor.preprocess(observation)
request = LingBotV2Request(**vars(processed))
raw_actions = engine.step(request)
actions = processor.postprocess(raw_actions)
```

`preprocess()` 将原始输入转换为模型请求所需的 tensor。

`engine.step()` 执行模型推理。

`postprocess()` 保留配置中指定的 `action` 维度，按需执行反归一化，并返回 CPU 上的 `float32` tensor。

如需保存结果，可以添加以下代码：

```python theme={null}
import numpy as np

np.save("actions.npy", actions.numpy())
```

### 使用自己的图像

`images` 是一个列表，包含来自三路相机的图像。

对于单次观测，每路相机图像的 `shape` 为 `(1, 3, H, W)`，通道顺序为 RGB，`dtype` 为 `uint8`。批量输入的 `shape` 为 `(B, 3, H, W)`。

如果原始图像是 `shape` 为 `(H, W, 3)` 的 NumPy 数组，可以这样转换：

```python theme={null}
image_tensor = (
    torch.from_numpy(rgb_image)
    .permute(2, 0, 1)
    .unsqueeze(0)
)
```

相机顺序必须与训练数据一致。RoboTwin 使用以下顺序：

```text theme={null}
cam_high
cam_left_wrist
cam_right_wrist
```

修改图像分辨率后，需要重新调用 `processor.preprocess()`，并根据新的 `pixel_values` 计算 `max_vision_tokens`。

### 使用自己的任务指令

每次观测对应一条任务指令，例如：

```python theme={null}
"task": ["pick up the cup"]
```

Processor 会处理对话模板和分词，无需手动构造 token ID。

### 使用自己的 `state`

在预处理前，保持 `state tensor` 位于 CPU：

```python theme={null}
"state": torch.tensor(
    [[0.0] * config.max_state_dim],
    dtype=torch.float32,
)
```

Processor 会先对 `state` 进行归一化和维度填充，再将计算所需的 tensor 移动到 GPU。

`state` 中每个数值的含义和顺序必须与 checkpoint 一致。RoboTwin 的原始 `state` 包含左右机械臂的关节和夹爪数值，而 LingBot V2 使用 55 维的模型 `state` 布局。

不能简单地将任意 14 维 `state` 补零后就作为有效输入，也不能直接截取模型输出的前 14 个值作为机器人 `action`。接入真实机器人时，需要明确 `state` 布局、归一化统计信息和 `action` 映射关系。

### 使用固定噪声比较结果

比较两次推理结果时，需要向两个请求传入相同的 `noise` tensor，`shape` 为：

`(B, chunk_size, max_action_dim)`

如果没有提供 `noise`，模型会为每次请求重新采样初始 `noise`，因此两次推理可能生成不同的 `action`。

## 限制

* 接入真实机器人时，需要匹配相机顺序、`state` 布局、归一化统计信息和 `action` 映射关系。
* 本指南不涉及多 GPU 运行。


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.