下面给你一份可直接照着做、从环境搭建到训练 / 推理的完整 VLA(Vision-Language-Action)实现步骤,偏向开源路线(OpenVLA / π0 / RDT 通用流程),Ubuntu 22.04 + CUDA 11.8/12.1 实测可用。

一、硬件与系统要求

1.1 最低 / 推荐配置

  • 系统:Ubuntu 20.04/22.04(Windows 用 WSL2,macOS 仅推理)
  • GPU:RTX 3090/4090 24GB+(训练);RTX 3060+(推理)
  • CUDA:11.8 / 12.1(必须与 PyTorch 匹配)
  • 内存:≥32GB;存储:≥500GB NVMe

1.2 系统基础依赖

sudo apt update && sudo apt install -y build-essential git curl wget \
  libglfw3-dev libgles2-mesa-dev libusb-1.0-0-dev pkg-config

二、conda 环境搭建(隔离依赖)

2.1 安装 Miniconda

wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh
bash Miniconda3-latest-Linux-x86_64.sh -b -p ~/miniconda3
source ~/miniconda3/bin/activate
conda init bash
source ~/.bashrc

2.2 创建 VLA 专属环境

conda create -n vla python=3.10 -y
conda activate vla

三、核心依赖安装(PyTorch + CUDA + VLA 库)

3.1 安装 PyTorch(匹配 CUDA)

# CUDA 11.8
pip install torch==2.1.0 torchvision==0.16.0 torchaudio==2.1.0 --index-url https://download.pytorch.org/whl/cu118

# CUDA 12.1(推荐新卡)
pip install torch==2.2.0 torchvision==0.17.0 torchaudio==2.2.0 --index-url https://download.pytorch.org/whl/cu121

3.2 安装 VLA 通用依赖

pip install transformers==4.37.2 accelerate==0.27.2 datasets==2.18.0 \
opencv-python==4.9.0.80 pillow==10.2.0 numpy==1.26.4 scipy==1.11.4 \
matplotlib==3.8.3 tqdm==4.66.2 h5py==3.10.0 wandb==0.16.3 \
-i https://pypi.tuna.tsinghua.edu.cn/simple

3.3 开源 VLA 模型库(选一个,以 OpenVLA 为例)

# 克隆 OpenVLA
git clone https://github.com/openvla/openvla.git
cd openvla
pip install -e .

# 或 π0(物理智能)
git clone https://github.com/physical-intelligence/pi0.git
cd pi0
pip install -e .

3.4 模型下载

模型路径和下载的文件如下图:

3.5 文本代码测试

代码如下:

# test_step_loading.py
import os
import torch
import json
from safetensors.torch import load_file
from transformers import LlamaConfig, LlamaForCausalLM
import gc
import psutil

model_dir = "/home/wyq/openvla/models/openvla-7b-finetuned-libero-10"

print(f"分步加载模型,最大限度节省内存...")

# 1. 检查内存
print(f"可用内存: {psutil.virtual_memory().available / 1024 ** 3:.2f} GB")

# 2. 创建配置
vocab_size = 32064
config = LlamaConfig(
    vocab_size=vocab_size,
    hidden_size=4096,
    intermediate_size=11008,
    num_hidden_layers=32,
    num_attention_heads=32,
    num_key_value_heads=32,
    max_position_embeddings=2048,
    rms_norm_eps=1e-5,
    use_cache=True,
)

print("步骤 1/6: 创建模型结构...")
model = LlamaForCausalLM(config)

# 3. 准备权重文件
print("步骤 2/6: 准备权重文件...")
safetensors_files = sorted([f for f in os.listdir(model_dir) if f.endswith('.safetensors')])
print(f"找到 {len(safetensors_files)} 个权重文件")

# 加载索引
with open(os.path.join(model_dir, "model.safetensors.index.json"), "r") as f:
    index = json.load(f)
    weight_map = index.get("weight_map", {})
    print(f"权重映射: {len(weight_map)} 个键")

# 4. 逐层加载(这是关键优化)
print("步骤 3/6: 逐层加载权重...")

# 获取所有层的索引
layer_indices = set()
for key in weight_map.keys():
    if 'layers.' in key and key.startswith('language_model.'):
        parts = key.split('.')
        for i, part in enumerate(parts):
            if part == 'layers' and i + 1 < len(parts):
                try:
                    layer_idx = int(parts[i + 1])
                    layer_indices.add(layer_idx)
                except ValueError:
                    pass

layer_indices = sorted(layer_indices)
print(f"找到 {len(layer_indices)} 层: {layer_indices[:5]}...")

# 分批处理:每批处理 4 层
batch_size = 4
for batch_start in range(0, len(layer_indices), batch_size):
    batch_layers = layer_indices[batch_start:batch_start + batch_size]
    print(f"\n处理层 {batch_layers}...")

    # 加载这一批的权重
    batch_state_dict = {}
    for sf in safetensors_files:
        part = load_file(os.path.join(model_dir, sf))
        for key, tensor in part.items():
            if not key.startswith('language_model.'):
                continue
            # 检查是否属于当前批次
            is_in_batch = False
            for layer_idx in batch_layers:
                if f'layers.{layer_idx}.' in key:
                    is_in_batch = True
                    break
            if is_in_batch:
                new_key = key.replace('language_model.', '', 1)
                batch_state_dict[new_key] = tensor
        del part
        gc.collect()

    print(f"  加载了 {len(batch_state_dict)} 个权重")

    # 应用这批权重
    model.load_state_dict(batch_state_dict, strict=False)

    # 清理
    del batch_state_dict
    gc.collect()

    # 检查内存
    mem = psutil.Process().memory_info().rss / 1024 ** 3
    print(f"  当前内存: {mem:.2f} GB")

# 5. 加载非层权重(embedding, lm_head, norm)
print("\n步骤 4/6: 加载非层权重...")
non_layer_state_dict = {}
for sf in safetensors_files:
    part = load_file(os.path.join(model_dir, sf))
    for key, tensor in part.items():
        if key.startswith('language_model.'):
            # 只保留非层的权重
            if 'layers.' not in key:
                new_key = key.replace('language_model.', '', 1)
                non_layer_state_dict[new_key] = tensor
    del part
    gc.collect()

print(f"加载了 {len(non_layer_state_dict)} 个非层权重")
model.load_state_dict(non_layer_state_dict, strict=False)
del non_layer_state_dict
gc.collect()

# 6. 清理和转换
print("步骤 5/6: 清理和转换...")
model = model.half()
print(f"当前内存: {psutil.Process().memory_info().rss / 1024 ** 3:.2f} GB")

# 7. 推理测试
print("步骤 6/6: 测试推理...")
try:
    model.eval()
    input_ids = torch.tensor([[1, 2, 3, 4]], dtype=torch.long)
    with torch.no_grad():
        outputs = model(input_ids)
    print(f"✅ 推理成功!输出形状: {outputs.logits.shape}")
except Exception as e:
    print(f"⚠️ 推理失败: {e}")

print("✅ 模型加载完成!")

运行结果如下:

3.6 完整的模型加载及推理


import torch
import os
import sys
from transformers import AutoModelForVision2Seq, AutoProcessor
from PIL import Image
import numpy as np
import time

# ===== 设置离线模式 =====
os.environ["HF_HUB_OFFLINE"] = "1"
os.environ["TRANSFORMERS_OFFLINE"] = "1"

# 将模型目录添加到Python路径,以便加载自定义模块
model_path = '/home/wyq/openvla/models/openvla-7b-finetuned-libero-10'
sys.path.insert(0, model_path)

print("=" * 60)
print("🚀 VLA模型离线加载")
print("=" * 60)

print(f"✅ GPU: {torch.cuda.get_device_name(0)}")
print(f"✅ 显存: {torch.cuda.get_device_properties(0).total_memory / 1024 ** 3:.2f} GB")
print(f"✅ 离线模式: 已启用")


def load_model_and_processor(model_path):
    """离线加载模型和处理器"""
    print("\n📦 加载模型...")

    # 检查本地文件
    required_files = [
        "modeling_prismatic.py",
        "processing_prismatic.py",
        "configuration_prismatic.py"
    ]

    print("检查本地文件:")
    for f in required_files:
        fpath = os.path.join(model_path, f)
        if os.path.exists(fpath):
            print(f"  ✅ {f} 存在 ({os.path.getsize(fpath)} bytes)")
        else:
            print(f"  ❌ {f} 不存在")
            print(f"  请确保文件在: {fpath}")

    # 加载模型 - 强制使用本地文件
    model = AutoModelForVision2Seq.from_pretrained(
        model_path,
        torch_dtype=torch.bfloat16,
        device_map='auto',
        trust_remote_code=True,
        local_files_only=True,  # 关键: 只使用本地文件
        use_safetensors=True
    )

    print("📦 加载处理器...")
    processor = AutoProcessor.from_pretrained(
        model_path,
        trust_remote_code=True,
        local_files_only=True  # 关键: 只使用本地文件
    )

    print("✅ 加载完成!")
    return model, processor


def predict_action(model, processor, image, prompt, max_new_tokens=50):
    """预测动作"""
    print(f"\n📝 指令: {prompt}")
    print(f"📷 图像尺寸: {image.size}")

    # 处理输入
    inputs = processor(
        images=image,
        text=prompt,
        return_tensors='pt'
    )

    # 移到GPU并转换数据类型
    for key in inputs.keys():
        if isinstance(inputs[key], torch.Tensor):
            inputs[key] = inputs[key].cuda()
            if inputs[key].dtype in [torch.float32, torch.float64]:
                inputs[key] = inputs[key].to(torch.bfloat16)

    print("⚡ 执行推理...")
    start = time.time()

    with torch.no_grad():
        outputs = model.generate(
            **inputs,
            max_new_tokens=max_new_tokens,
            do_sample=False,
            pad_token_id=32000,
            eos_token_id=2,
        )

    end = time.time()
    print(f"⏱️ 推理时间: {(end - start) * 1000:.2f}ms")

    return outputs


def main():
    # 加载模型和处理器
    model, processor = load_model_and_processor(model_path)

    # 显示模型信息
    print(f"\n📊 模型信息:")
    print(f"  类型: {type(model).__name__}")
    print(f"  设备: {model.device}")

    # 显示模型组件
    print("\n📊 模型组件:")
    for name, module in model.named_children():
        params = sum(p.numel() for p in module.parameters()) / 1e6
        print(f"  - {name}: {type(module).__name__} ({params:.1f}M参数)")

    # 创建测试图像
    print("\n创建测试图像...")
    if os.path.exists("test.jpg"):
        print("使用现有图像: test.jpg")
        image = Image.open("test.jpg").convert('RGB').resize((224, 224))
    else:
        image = Image.new('RGB', (224, 224), color='red')
        image.save('test.jpg')
        print("✅ 创建 test.jpg")

    # 测试不同的指令
    prompts = [
        "Pick up the red block from the table.",
        "Move the robot arm to the left.",
        "Grasp the blue cube.",
    ]

    for prompt in prompts:
        print("\n" + "=" * 60)
        print(f"🧪 测试: {prompt}")
        print("=" * 60)

        try:
            outputs = predict_action(model, processor, image, prompt)

            # 解码输出
            if hasattr(processor, 'decode'):
                decoded = processor.decode(outputs[0], skip_special_tokens=True)
                print(f"📤 解码输出: {decoded}")

            print(f"📊 输出形状: {outputs.shape}")
            print(f"📊 输出token (前20个): {outputs[0][:20].tolist()}")

        except Exception as e:
            print(f"❌ 推理错误: {e}")
            import traceback
            traceback.print_exc()

    print("\n" + "=" * 60)
    print("✅ 所有测试完成!")
    print("=" * 60)


if __name__ == "__main__":
    main()

运行结果如下:

四、数据准备(VLA 核心:视觉 + 语言 + 动作三元组)

4.1 数据格式(统一要求)

  • 视觉:rgb(H,W,3 uint8)、depth(H,W float32,可选)
  • 语言:instruction(字符串,如 “把红色方块放到盘子里”)
  • 动作:action(连续 / 离散,如末端执行器 6D + 夹爪)
  • 存储:HDF5 / RLDS / Parquet

4.2 公开数据集(快速起步)

  • Open X-Embodiment:1M+ 机器人轨迹(RLDS 格式)

    bash

# 下载脚本(需 gsutil)
gsutil -m cp -r gs://open-x-embodiment/data/bridge ./data/
  • ALOHA / Mini-ALOHA:低成本双臂操作数据集

4.3 自定义数据采集(仿真 / 真机)

  1. 仿真:用 Mujoco + RobosuiteVLA-Arena 收集
python scripts/collect_demo.py --task pick_place --num_episodes 100

2. 真机:用 ROS2 订阅 /camera/color/joint_states/cmd_vel,同步时间戳存 HDF5

五、模型结构与配置(以 OpenVLA 为例)

5.1 核心架构

  • 视觉编码器:ViT-L/14(CLIP)→ 图像特征
  • 语言编码器:LLaMA-2-7B → 指令特征
  • 动作解码器:Transformer Decoder → 连续动作序列

5.2 配置文件(configs/train/openvla.yaml

model:
  vision_encoder: "clip-vit-large-patch14"
  lang_encoder: "llama-2-7b-chat"
  action_dim: 7  # 6D pose + 1夹爪
  max_seq_len: 256
training:
  batch_size: 8
  lr: 1e-4
  epochs: 10
  precision: "bf16"
data:
  dataset_path: "./data/bridge"
  image_size: [224, 224]

6.2 多卡分布式训练(DeepSpeed,≥2 卡)

accelerate launch --num_processes 4 --config_file ds_config.json train.py ...

6.3 训练监控

  • 自动启动 WandB:loss、action_error、任务成功率
  • 关键指标:action_mseinstruction_accuracytask_success_rate

七、模型推理(仿真 + ROS2 真机部署)

7.1 仿真推理(Mujoco)

from openvla import VLA
import gymnasium as gym

# 加载模型
model=VLA.from_pretrained("./checkpoints/openvla-finetune")
model.to("cuda")

# 环境
env=gym.make("RobosuitePickPlace-v0", render_mode="human")
obs, info=env.reset()

# 推理循环
for _ in range(500):
    rgb=obs["rgb"]  # (224,224,3)
    instruction="pick up the red cube"
    action=model.predict(rgb, instruction)  # (7,)
    obs, reward, done, truncated, info=env.step(action)
    if done:
        break

7.2 ROS2 真机部署(和你之前代码打通)

  1. 订阅彩图 / 深度 / 相机内参(你已有的代码)
  2. 回调中调用 VLA 模型生成动作
  3. 发布动作到 /cmd_vel/joint_trajectory_controller
# 在你现有的 callback 里加
def sync_rgbd_data(self):
    # ... 你已有的缓存逻辑 ...
    # 推理
    rgb_img=cv2.resize(self.cached_color_image, (224,224))
    instruction="put the box on the shelf"
    action=self.model.predict(rgb_img, instruction)  # (7,)
    # 发布动作(示例:发布到 ROS2 话题)
    action_msg=JointTrajectory()
    action_msg.joint_names=["joint1", "joint2", ...]
    action_msg.points=[JointTrajectoryPoint(positions=action[:6])]
    self.action_pub.publish(action_msg)

八、性能优化(必做,否则 4090 也不够用)

  1. 精度:训练用 bf16,推理用 fp16
  2. 量化bitsandbytes 4bit 加载 LLM,显存减少 60%+
pip install bitsandbytes
  1. 模型轻量化:用 TinyVLA / MobileVLA(速度提升 5 倍,参数 < 1B)
  2. TensorRT 加速:推理延迟从 200ms → 20ms

九、常见坑与避坑

  1. CUDA 版本不匹配:严格按上面命令装 PyTorch,不要混装
  2. 显存爆炸:开启 --bf16、梯度累积、4bit 量化
  3. 动作抖动:推理后加 EMA 平滑action=0.7*last_action+0.3*new_action
  4. 数据不同步:ROS2 中用 message_filters.TimeSynchronizer 对齐彩图 / 深度

十、下一步建议

  • 先跑通 仿真训练 + 推理(用 OpenVLA + Bridge 数据集)
  • 再迁移到 ROS2 真机(复用你已有的相机订阅代码)
  • 最后做 自定义数据采集 + 微调,适配你的任务

十一、常见出错

11.1 OpenVLA 依赖冲突

1.修正你openvla/__init__.py
from .prismatic.models.vla import VLA, VLAConfig
__all__ = ["VLA", "VLAConfig"]
2.检查并修正目录结构
cd /home/wyq/public/openvla
mv prismatic/ openvla/
3.强制重装 OpenVLA
pip uninstall -y openvla
pip install -e . --force-reinstall --no-cache-dir
4.验证
python -c "
from openvla import VLA, VLAConfig
print('✅ OpenVLA 导入成功!')
"
5.写一个test.py文件验证
import sys
sys.path.insert(0, "/home/wyq/public/openvla/openvla")

from prismatic.models.vlas.openvla import OpenVLA
import torch
import numpy as np
from PIL import Image

print("✅ 导入成功!")

# 加载模型(注意:类名是 OpenVLA,不是 VLA)
model = OpenVLA.from_pretrained("openvla/openvla-7b")
model.to("cuda" if torch.cuda.is_available() else "cpu")
print("✅ 模型加载成功!")

# 模拟图片
dummy_image = Image.fromarray(np.random.randint(0, 255, size=(224, 224, 3), dtype=np.uint8))
instruction = "pick up the red cube"

# 推理(根据源码,方法名可能是 predict 或 forward,这里先用 predict)
action = model.predict(dummy_image, instruction)
print(f"✅ 生成动作: {action}")

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐