SFT & RL Training cho OpenArm với SimpleVLA-RL
Ở bài trước, chúng ta đã thu thập hàng trăm episodes demonstration trong Isaac Lab cho OpenArm gắp hộp carton. Dữ liệu simulation đã sẵn sàng — giờ là lúc biến nó thành một chính sách (policy) thông minh có thể điều khiển robot thật. Bài này sẽ hướng dẫn bạn toàn bộ pipeline training SimpleVLA-RL từ đầu đến cuối: cài đặt stack, adapt action space cho OpenArm, chạy SFT (Supervised Fine-Tuning) để tạo baseline, rồi "bơm" thêm RL (Reinforcement Learning) với thuật toán GRPO để đẩy hiệu suất lên cao hơn đáng kể.
Điểm khác biệt lớn nhất so với bài 8 — nơi chúng ta dùng LeRobot — là ở đây chúng ta hoàn toàn sử dụng SimpleVLA-RL pipeline dựa trên veRL và OpenVLA-OFT. Không có LeRobot. Không có ACT hay SmolVLA. Chỉ có một VLA model duy nhất được fine-tune bằng SFT rồi nâng cấp bằng RL.
Tại sao SimpleVLA-RL thay vì LeRobot?
Trước khi đi vào chi tiết kỹ thuật, hãy hiểu tại sao chúng ta chọn con đường này. LeRobot là framework tuyệt vời cho Imitation Learning — bạn thu thập demo, train policy, deploy. Nhưng nó dừng lại ở đó. Policy chỉ tốt bằng dữ liệu bạn cung cấp.
SimpleVLA-RL đi xa hơn một bước: sau khi SFT tạo baseline từ demo, thuật toán GRPO cho phép robot tự khám phá chiến lược mới thông qua trial-and-error trong simulation. Kết quả từ paper gốc trên robot Piper cho thấy RL có thể tăng success rate từ 17.5% lên 38.5% — một cải thiện 120% so với chỉ dùng SFT.
Bước 1: Cài đặt SimpleVLA-RL Stack
Toàn bộ stack bao gồm 4 thành phần chính: Python environment, veRL (RL framework), OpenVLA-OFT (VLA model), và SimpleVLA-RL (glue code kết nối tất cả). Thứ tự cài đặt rất quan trọng vì có dependency chain.
Tạo Conda environment
# Tạo environment riêng — KHÔNG dùng chung với Isaac Lab env
conda create -n simplevla python==3.10 -y
conda activate simplevla
# PyTorch 2.4 với CUDA 12.4 — phiên bản được test kỹ nhất
pip3 install torch==2.4.0 torchvision==0.19.0 --index-url https://download.pytorch.org/whl/cu124
Tại sao Python 3.10 và không phải 3.11 hay 3.12? Vì veRL và flash-attention có các compiled extensions được build và test chủ yếu trên 3.10. Bạn có thể thử 3.11 nhưng sẽ gặp rủi ro compatibility issues mà không đáng.
Cài veRL — RL Training Framework
# Clone veRL v0.2 — phiên bản stable được SimpleVLA-RL hỗ trợ
git clone -b v0.2.x https://github.com/volcengine/verl.git
cd verl
pip install -e .
cd ..
veRL (Volcano Engine Reinforcement Learning) là framework của ByteDance được thiết kế cho RL training trên LLM-scale models. Nó xử lý distributed training, rollout collection, và policy optimization — những thứ bạn không muốn tự implement.
Cài OpenVLA-OFT — VLA Model
# Clone OpenVLA-OFT — VLA model với Open Fine-Tuning support
git clone https://github.com/moojink/openvla-oft.git
cd openvla-oft
pip install -e .
cd ..
# Flash Attention — tăng tốc inference 2-3x
pip install flash-attn --no-build-isolation
OpenVLA-OFT là phiên bản mở rộng của OpenVLA với hỗ trợ fine-tuning tốt hơn. Nó sử dụng kiến trúc Vision-Language-Action với Prismatic VLM backbone, tokenize actions thành 256 discrete tokens cho mỗi bậc tự do.
Clone SimpleVLA-RL
git clone https://github.com/simple-vla-rl/SimpleVLA-RL.git
cd SimpleVLA-RL
pip install -e .
Sau bước này, verify toàn bộ stack:
python -c "
import torch
import verl
import prismatic
print(f'PyTorch: {torch.__version__}')
print(f'CUDA available: {torch.cuda.is_available()}')
print(f'GPU count: {torch.cuda.device_count()}')
print(f'veRL imported successfully')
print(f'Prismatic (OpenVLA) imported successfully')
"
Nếu mọi thứ OK, bạn sẽ thấy PyTorch 2.4.0, CUDA available, và số GPU. Nếu torch.cuda.device_count() trả về 0, kiểm tra CUDA driver version với nvidia-smi.
Bước 2: Adapt Action Space cho OpenArm
Đây là bước quan trọng nhất và cũng dễ sai nhất. OpenVLA-OFT mặc định được thiết kế cho 7-DoF (6 khớp + 1 gripper, như robot Piper). OpenArm có 7 khớp + 1 gripper = 8-DoF. Chúng ta cần quyết định cách xử lý sự khác biệt này.
Option A: Ánh xạ trực tiếp 8-DoF (Khuyến nghị)
OpenArm 8-DoF (7 joints + 1 gripper) có cùng chiều với một số config mặc định của OpenVLA-OFT. Nếu model hỗ trợ action_dim=8, chúng ta chỉ cần ánh xạ đúng thứ tự khớp:
# Trong config file hoặc trực tiếp trong code
ACTION_DIM = 8 # 7 joints + 1 gripper
STATE_DIM = 8 # Joint positions feedback
NUM_ACTION_TOKENS = 256 # Mỗi DoF được tokenize thành 256 giá trị rời rạc
# Ánh xạ OpenArm joints → action vector
# [joint1, joint2, joint3, joint4, joint5, joint6, joint7, gripper]
# Mỗi joint value được normalize về [-1, 1] trước khi tokenize
JOINT_LIMITS = {
'joint1': (-3.14, 3.14), # Base rotation
'joint2': (-1.57, 1.57), # Shoulder
'joint3': (-1.57, 1.57), # Elbow
'joint4': (-3.14, 3.14), # Wrist 1
'joint5': (-1.57, 1.57), # Wrist 2
'joint6': (-3.14, 3.14), # Wrist 3
'joint7': (-1.57, 1.57), # Wrist 4
'gripper': (0.0, 1.0), # Gripper open/close
}
Option B: Giữ 7-DoF và pad
Nếu bạn muốn giữ nguyên architecture 7-DoF của OpenVLA-OFT mà không sửa model:
# Gộp 2 wrist joints cuối thành 1
# OpenArm 8-DoF → 7-DoF mapping
def openarm_to_7dof(joint_positions_8):
"""Map 8-DoF OpenArm to 7-DoF OpenVLA format"""
return [
joint_positions_8[0], # base
joint_positions_8[1], # shoulder
joint_positions_8[2], # elbow
joint_positions_8[3], # wrist1
joint_positions_8[4], # wrist2
# Gộp wrist3 + wrist4 thành 1 giá trị trung bình
(joint_positions_8[5] + joint_positions_8[6]) / 2,
joint_positions_8[7], # gripper
]
Mình khuyến nghị Option A vì nó giữ nguyên toàn bộ thông tin kinematic. Việc gộp joints sẽ mất precision, đặc biệt với các task đòi hỏi wrist dexterity cao.
Sửa Action Tokenizer Config
Trong OpenVLA-OFT, mở file config và thay đổi:
# openvla-oft/prismatic/models/backbones/llm/action_tokenizer.py
# Hoặc trong config JSON tùy phiên bản
tokenizer_config = {
"action_dim": 8, # Thay từ 7 → 8 cho OpenArm
"state_dim": 8, # Feedback dimension
"num_action_tokens": 256, # Giữ nguyên
"action_chunk_size": 1, # Predict 1 action mỗi step
}
Bước 3: Đăng ký OpenArm Environment
SimpleVLA-RL cần biết cách tương tác với environment của bạn. Có 3 file cần sửa: rob_dataset.py (dữ liệu), rob_rollout.py (rollout loop), và reward function.
Thêm dataset vào rob_dataset.py
# SimpleVLA-RL/verl/workers/rob_dataset.py
# Thêm vào dictionary DATASETS
DATASETS = {
# ... các dataset có sẵn (LIBERO, Piper, etc.)
"openarm_box_grasp": {
"data_path": "/path/to/openarm_sim_demos/",
"task_description": "Pick up the carton box from the table",
"action_dim": 8,
"state_dim": 8,
"camera_names": ["front_camera"], # Tên camera trong Isaac Lab
"image_size": (224, 224), # Resize cho VLA input
"max_episode_length": 400, # Phải khớp với sim config
},
}
Thêm environment vào rob_rollout.py
# SimpleVLA-RL/verl/workers/rob_rollout.py
def create_environment(env_name, env_config):
"""Tạo environment cho rollout"""
if env_name == "openarm_box_grasp":
# Import Isaac Lab environment
from omni.isaac.lab.envs import ManagerBasedRLEnv
import omni.isaac.lab_tasks # Register environments
env = ManagerBasedRLEnv(
cfg=env_config,
render_mode="rgb_array",
)
return OpenArmWrapper(env)
# ... các environment khác
# Thêm max_steps cho OpenArm
MAX_STEPS = {
# ... existing entries
"openarm_box_grasp": 400, # 400 steps × 0.02s = 8 giây mỗi episode
}
Implement get_info() cho success detection
Đây là phần quan trọng nhất — nó quyết định reward signal cho RL:
class OpenArmWrapper:
"""Wrapper kết nối Isaac Lab env với SimpleVLA-RL rollout"""
def __init__(self, isaac_env):
self.env = isaac_env
self.step_count = 0
def get_info(self):
"""
Trả về dict chứa thông tin success/failure.
SimpleVLA-RL dùng binary reward: 1 nếu success, 0 nếu fail.
"""
# Kiểm tra: box đã được nâng lên khỏi mặt bàn chưa?
box_pos = self.env.scene["box"].get_world_poses()[0]
table_height = 0.75 # Chiều cao mặt bàn trong sim
# Gripper đang kẹp box + box cao hơn mặt bàn 10cm
gripper_holding = self.env.scene["gripper"].is_closed()
box_lifted = box_pos[2] > table_height + 0.10
success = gripper_holding and box_lifted
return {
"success": success,
"reward": 1.0 if success else 0.0, # Binary reward
"box_height": float(box_pos[2]),
"step": self.step_count,
}
def step(self, action):
"""Execute action và trả về observation"""
obs, reward, terminated, truncated, info = self.env.step(action)
self.step_count += 1
# Override reward bằng binary success signal
custom_info = self.get_info()
return obs, custom_info["reward"], terminated, truncated, custom_info
def reset(self):
self.step_count = 0
return self.env.reset()
Bước 4: SFT Training — Tạo Baseline
SFT (Supervised Fine-Tuning) là bước "cold-start" — dạy VLA model bắt chước demonstrations bạn đã thu thập. Nó tương tự Imitation Learning nhưng được thực hiện trên nền tảng một Vision-Language Model đã được pretrain trên hàng triệu hình ảnh và văn bản.
Download OpenVLA-OFT Base Model
# Download từ HuggingFace — khoảng 15GB
huggingface-cli download moojink/openvla-7b-oft \
--local-dir ./checkpoints/openvla-oft-base
Model base này đã được pretrain trên Open X-Embodiment dataset — một tập hợp dữ liệu từ hàng chục loại robot khác nhau. Nó "hiểu" khái niệm chung về manipulation nhưng chưa biết gì về OpenArm cụ thể.
Chuẩn bị config SFT
# Tạo file config cho SFT training
cat > configs/openarm_sft.yaml << 'EOF'
# === Model ===
model:
base_model_path: "./checkpoints/openvla-oft-base"
action_dim: 8
action_tokens: 256
use_flash_attn: true
# === Data ===
data:
dataset_name: "openarm_box_grasp"
data_path: "/path/to/openarm_sim_demos/"
image_size: 224
batch_size: 32
num_workers: 4
# === Training ===
training:
learning_rate: 2e-5 # LR cho SFT — cao hơn RL
warmup_steps: 100
max_steps: 5000 # 500-1000 episodes × ~5 steps/epoch
gradient_accumulation: 2
fp16: true # Mixed precision
# === Logging ===
logging:
wandb_project: "openarm-simplevla"
wandb_run_name: "sft-openarm-v1"
log_interval: 50
save_interval: 1000
# === Hardware ===
hardware:
num_gpus: 4 # Tối thiểu 4× A100/A800
per_gpu_batch_size: 8
EOF
Chạy SFT Training
# Activate environment
conda activate simplevla
# Set WANDB key cho monitoring
export WANDB_API_KEY="your_wandb_key_here"
# Chạy SFT training
python -m verl.trainer.sft \
--config configs/openarm_sft.yaml \
--output_dir ./checkpoints/openarm-sft-v1
# Hoặc dùng torchrun cho multi-GPU
torchrun --nproc_per_node=4 \
-m verl.trainer.sft \
--config configs/openarm_sft.yaml \
--output_dir ./checkpoints/openarm-sft-v1
Thời gian training: Với 500 episodes và 4× A800, SFT mất khoảng 2-4 giờ. Trên 8× A800 chỉ khoảng 1-2 giờ. Bạn sẽ thấy loss giảm nhanh trong 1000 steps đầu rồi plateau.
Đánh giá SFT Baseline
Sau khi SFT xong, chạy evaluation trong sim để có baseline:
python -m verl.trainer.evaluate \
--model_path ./checkpoints/openarm-sft-v1/final \
--env_name openarm_box_grasp \
--num_episodes 100 \
--render_video true \
--output_dir ./eval_results/sft_baseline
Kỳ vọng: SFT baseline cho success rate khoảng 15-25% trên task gắp hộp. Con số này nghe thấp nhưng hoàn toàn bình thường — SFT policy chỉ bắt chước demo mà không hiểu "tại sao" mỗi hành động hoạt động. Nó thiếu khả năng generalize khi vị trí hộp thay đổi hoặc khi có nhiễu trong observation.
Bước 5: RL Training — Phép thuật GRPO
Đây là bước tạo nên sự khác biệt lớn nhất của SimpleVLA-RL. GRPO (Group Relative Policy Optimization) là biến thể của PPO được thiết kế cho LLM/VLA — nó không cần critic network riêng, thay vào đó dùng group-relative advantages từ nhiều samples cùng một query.
Cách GRPO hoạt động (trực giác)
Hãy tưởng tượng bạn đang dạy một người mới chơi bowling. SFT giống như cho họ xem video hướng dẫn — họ hiểu cơ bản nhưng chưa giỏi. GRPO giống như để họ ném thử 8 lần mỗi lượt, rồi so sánh kết quả:
- Lần ném nào đổ nhiều pin nhất → "chiến lược tốt, làm giống vậy nhiều hơn"
- Lần ném nào miss → "tránh làm như vậy"
Trong SimpleVLA-RL:
- Mỗi "query" là một observation (hình ảnh từ camera + task description)
- Model sinh ra 8 action samples cho cùng observation
- Mỗi sample được rollout trong sim để lấy binary reward (0 hoặc 1)
- Samples thành công được khuyến khích, samples thất bại bị penalize
- Quá trình lặp lại hàng nghìn lần
Tạo RL Training Script
# Tạo script chạy RL training cho OpenArm
cat > examples/run_openvla_oft_rl_openarm.sh << 'SCRIPT_EOF'
#!/bin/bash
set -euo pipefail
# === Paths ===
SFT_MODEL_PATH="./checkpoints/openarm-sft-v1/final"
CKPT_PATH="./checkpoints/openarm-rl-v1"
DATASET_NAME="openarm_box_grasp"
# === Hardware ===
NUM_GPUS=8 # 8× A800 80GB recommended
# Cũng hoạt động với 4× A100 hoặc 2× H100
# === Hyperparameters (đã tune cho manipulation tasks) ===
LEARNING_RATE=5e-6 # Thấp hơn SFT vì đang fine-tune policy
BATCH_SIZE=64 # Rollout batch size
SAMPLES_PER_QUERY=8 # Số action samples mỗi observation
MINI_BATCH_SIZE=128 # Mini-batch cho gradient update
CLIP_LOW=0.2 # Async clipping lower bound
CLIP_HIGH=1.28 # Async clipping upper bound
TEMPERATURE=1.6 # Sampling temperature — cao để explore
MAX_STEPS=400 # Max steps per episode
NUM_EPOCHS=200 # Tổng số RL epochs
# === WANDB ===
export WANDB_PROJECT="openarm-simplevla"
export WANDB_RUN_NAME="rl-grpo-openarm-v1"
# === Launch RL training ===
torchrun --nproc_per_node=$NUM_GPUS \
-m verl.trainer.main_ppo \
--config configs/openarm_rl.yaml \
trainer.sft_model_path=$SFT_MODEL_PATH \
trainer.ckpt_path=$CKPT_PATH \
data.dataset_name=$DATASET_NAME \
algorithm.lr=$LEARNING_RATE \
algorithm.batch_size=$BATCH_SIZE \
algorithm.n_samples=$SAMPLES_PER_QUERY \
algorithm.mini_batch_size=$MINI_BATCH_SIZE \
algorithm.clip_range_low=$CLIP_LOW \
algorithm.clip_range_high=$CLIP_HIGH \
algorithm.temperature=$TEMPERATURE \
rollout.max_steps=$MAX_STEPS \
trainer.num_epochs=$NUM_EPOCHS \
trainer.val_only=False
SCRIPT_EOF
chmod +x examples/run_openvla_oft_rl_openarm.sh
Bảng Hyperparameters chi tiết
| Parameter | Giá trị | Tại sao? |
|---|---|---|
| Learning rate | 5e-6 | Nhỏ để tránh catastrophic forgetting từ SFT |
| Batch size | 64 | Cân bằng giữa throughput và memory |
| Samples/query | 8 | Đủ đa dạng để GRPO so sánh, không quá tốn compute |
| Mini-batch | 128 | Gradient update mượt hơn |
| Clip low | 0.2 | Ngăn policy thay đổi quá mạnh theo hướng giảm |
| Clip high | 1.28 | Async clipping — cho phép tăng nhiều hơn giảm |
| Temperature | 1.6 | Cao để khuyến khích exploration |
| Action tokens | 256 | Độ phân giải action — 256 mức cho mỗi DoF |
Lưu ý về async clipping (0.2, 1.28): Đây là innovation của SimpleVLA-RL. PPO truyền thống dùng symmetric clipping (0.2, 0.2). Async clipping cho phép policy "tăng xác suất" một action nhiều hơn so với "giảm xác suất" — điều này hợp lý vì trong manipulation, khi robot tìm được chiến lược thành công, nó nên commit mạnh vào chiến lược đó.
Chạy RL Training
# Chạy RL training
bash examples/run_openvla_oft_rl_openarm.sh
Training RL mất 8-16 giờ trên 8× A800 tùy vào số epochs và tốc độ sim. Trên 4× A100, có thể lên đến 24 giờ.
Monitor trên W&B
Trong quá trình training, theo dõi các metrics sau trên Weights & Biases:
📈 Metrics quan trọng cần theo dõi:
1. rollout/success_rate — Nên tăng dần từ SFT baseline
2. policy/entropy — Giảm dần khi policy converge
3. policy/loss — Dao động nhưng trend giảm
4. rollout/avg_reward — Tăng dần (tương quan với success rate)
5. policy/kl_divergence — Không nên quá cao (< 0.5)
Kỳ vọng timeline:
- Epoch 1-20: Success rate dao động quanh SFT baseline (15-25%)
- Epoch 20-80: Bắt đầu tăng, robot tìm được chiến lược mới
- Epoch 80-150: Tăng nhanh, success rate đạt 40-60%
- Epoch 150-200: Plateau hoặc tiếp tục tăng chậm
Hiện tượng "Pushcut" — Robot tự phát minh
Một trong những kết quả thú vị nhất từ RL training là robot có thể khám phá chiến lược hoàn toàn mới mà không có trong demonstration data. Paper SimpleVLA-RL gọi đây là hành vi "emergent" — robot Piper đã tự học cách đẩy vật thể vào vị trí thuận lợi trước khi gắp, thay vì gắp trực tiếp như trong demo.
Với OpenArm gắp hộp carton, bạn có thể thấy robot tự học:
- Xoay cổ tay để tiếp cận hộp từ góc tốt hơn
- Đẩy hộp về phía trung tâm workspace trước khi gắp
- Mở gripper rộng hơn demo để "an toàn hơn"
Bước 6: Evaluate trong Simulation
Sau khi RL training hoàn tất, đánh giá model trên 100+ episodes để có kết quả thống kê đáng tin cậy.
Chạy Evaluation
# Sửa script để chỉ evaluate (không train)
bash examples/run_openvla_oft_rl_openarm.sh \
trainer.val_only=True \
trainer.sft_model_path=./checkpoints/openarm-rl-v1/best \
rollout.num_eval_episodes=100
So sánh SFT vs SFT+RL
┌─────────────────────────────────────────────────┐
│ OpenArm Box Grasp — Sim Results │
├─────────────────┬───────────┬───────────────────┤
│ Method │ Success % │ Improvement │
├─────────────────┼───────────┼───────────────────┤
│ SFT only │ ~20% │ Baseline │
│ SFT + RL (GRPO) │ ~45-55% │ +125-175% │
├─────────────────┴───────────┴───────────────────┤
│ * Kết quả ước tính dựa trên paper gốc (Piper): │
│ SFT 17.5% → SFT+RL 38.5% (+120%) │
│ OpenArm sim có thể cao hơn vì sim dễ hơn │
│ real-world │
└─────────────────────────────────────────────────┘
Phân tích Failure Modes
Khi success rate dừng ở mức nào đó, phân tích tại sao robot fail:
# Script phân tích failure
import json
with open("eval_results/rl_eval/results.json") as f:
results = json.load(f)
failures = [r for r in results if not r["success"]]
print(f"Total failures: {len(failures)}/{len(results)}")
# Phân loại failure
failure_types = {
"miss_grasp": 0, # Gripper đóng nhưng không kẹp được hộp
"wrong_position": 0, # Robot không tiếp cận đúng vị trí hộp
"timeout": 0, # Hết thời gian (400 steps)
"collision": 0, # Va chạm với bàn hoặc vật cản
}
for f in failures:
if f["step"] >= 400:
failure_types["timeout"] += 1
elif f["box_height"] < 0.76: # Box chưa được nâng
if f.get("gripper_closed", False):
failure_types["miss_grasp"] += 1
else:
failure_types["wrong_position"] += 1
else:
failure_types["collision"] += 1
for ftype, count in failure_types.items():
pct = count / len(failures) * 100
print(f" {ftype}: {count} ({pct:.1f}%)")
Dựa vào phân tích failure, bạn có thể điều chỉnh:
- Nhiều miss_grasp → Tăng domain randomization cho vị trí hộp trong sim
- Nhiều timeout → Tăng max_steps hoặc giảm task difficulty
- Nhiều wrong_position → Thu thập thêm demo cho các vị trí hộp đa dạng hơn
Tổng kết Pipeline
Isaac Lab Sim
│
▼
Collect Demonstrations (500-1000 episodes)
│
▼
SFT Training (OpenVLA-OFT base → fine-tuned model)
│ ~20% success rate
▼
RL Training (GRPO với binary rewards)
│ ~45-55% success rate
▼
Sim Evaluation (100+ episodes)
│
▼
Ready for Sim-to-Real (Bài tiếp theo!)
Toàn bộ pipeline này — từ cài đặt đến evaluation — có thể hoàn thành trong 2-3 ngày với hardware phù hợp. SFT mất nửa ngày, RL mất 1 ngày, evaluation và iteration mất nửa ngày.
Trong bài tiếp theo, chúng ta sẽ đưa model đã train từ sim sang robot OpenArm thật — bước sim-to-real transfer đầy thử thách nhưng cũng đầy phấn khích.
Hardware Requirements
| Config | GPUs | SFT Time | RL Time | Tổng |
|---|---|---|---|---|
| Recommended | 8× A800 80GB | 2h | 10h | ~12h |
| Minimum viable | 4× A100 40GB | 4h | 20h | ~24h |
| Budget | 2× H100 80GB | 3h | 14h | ~17h |
Cloud GPU providers như Lambda Labs, RunPod, hoặc Vast.ai cho thuê 8× A800 khoảng $15-25/giờ. Tổng chi phí training khoảng $180-300 cho full pipeline.
Bài viết liên quan
- SimpleVLA-RL (1): Tổng quan framework RL cho VLA — Hiểu tại sao RL quan trọng cho robot manipulation
- SimpleVLA-RL (3): Training pipeline chi tiết — Deep dive vào GRPO và veRL internals
- SimpleVLA-RL (11): Sim-to-Real cho OpenArm — Đưa model từ sim sang robot thật