unifolm-vla + Unitree G1 (Bài 5): deploy inference server, SSH tunnel, và locomotion song song
Đây là bài cuối của series unifolm-vla + Unitree G1. Bài trước đã có checkpoint. Bài này: deploy lên G1 thật, chạy inference, và kết hợp locomotion song song để có whole-body behavior.
Trước khi đọc: Deploy lên robot thật luôn có rủi ro. Làm theo đúng thứ tự: sim trước → test offline → test với robot ở chế độ an toàn → full deploy.
Kiến trúc deploy
[Workstation GPU] [Unitree G1]
FastAPI Server Robot SDK
run_real_eval_server.py (arm control)
port 8777 ←→ 192.168.123.xxx
↑
POST /act
{image, state, instruction} (leg control)
↓ unitree_rl_gym
{action: [28 joints]} motion.pt
Ba luồng chạy song song:
- VLA inference (workstation): nhận ảnh → predict action → gửi lên G1
- Arm control (G1): nhận joint commands từ VLA, thực thi
- Locomotion (G1): chạy
motion.ptpolicy độc lập, điều khiển chân
Bước 1: Khởi động inference server
conda activate unifolm
cd ~/unifolm_ws/unifolm-vla
# Với Approach A checkpoint (8-GPU full fine-tune)
python run_real_eval_server.py \
--ckpt_path /home/user/checkpoints/unifolm_g1_pickplace/best_checkpoint.pt \
--vlm_pretrained_path /home/user/models/Qwen2.5-VL-7B-Instruct \
--unnorm_key new_embodiment \
--host 0.0.0.0 \
--port 8777 \
--use_bf16
# Với Approach B checkpoint (LoRA single-GPU)
# Cần load LoRA weights trước khi start server
python run_real_eval_server.py \
--ckpt_path /home/user/checkpoints/lora_g1_pickplace \
--vlm_pretrained_path /home/user/models/Qwen2.5-VL-7B-Instruct \
--lora_mode true \
--unnorm_key new_embodiment \
--host 0.0.0.0 \
--port 8777 \
--use_bf16
Terminal output khi server sẵn sàng:
Loading VLM from: /home/user/models/Qwen2.5-VL-7B-Instruct
Loading checkpoint: /home/user/checkpoints/unifolm_g1_pickplace/best_checkpoint.pt
Model loaded. VRAM usage: 14.3 GB / 24 GB
Starting FastAPI server on 0.0.0.0:8777
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8777 (Press CTRL+C to quit)
Verify server hoạt động:
# Test với dummy data
curl -X POST http://localhost:8777/act \
-H "Content-Type: application/json" \
-d '{
"full_image": null,
"state": [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],
"instruction": "pick up the red cup"
}'
# Response mong đợi (action values ngẫu nhiên vì image null):
# {"action": [0.12, -0.05, 0.33, ...], "timestamp": 1234567890.123}
Bước 2: Thiết lập robot client trên G1
G1 có onboard computer (Jetson Orin NX). Ta cần gửi HTTP requests từ onboard computer → workstation server.
SSH tunnel setup
Kết nối workstation và G1 cùng LAN, sau đó tạo tunnel:
# Trên workstation — biết IP của G1 onboard computer
# IP mặc định G1: 192.168.123.18 (có thể khác)
# Test kết nối
ping 192.168.123.18
# SSH vào G1 onboard computer
ssh [email protected]
# Password: thường là "123" hoặc xem docs G1 của bạn
Robot client script
Tạo script robot_client.py trên G1 onboard computer (hoặc workstation nếu dùng LAN trực tiếp):
"""
Robot client: nhận ảnh từ G1 camera, gửi đến inference server,
nhận action, thực thi trên G1 arm.
"""
import requests
import numpy as np
import time
import base64
import cv2
from unitree_sdk2py.core.channel import ChannelFactory
from unitree_sdk2py.idl.unitree_hg.msg.dds_ import LowCmd_
import json
INFERENCE_SERVER = "http://192.168.1.100:8777" # IP workstation
TASK_INSTRUCTION = "pick up the red cup"
CONTROL_FREQ_HZ = 5 # ~5Hz, phù hợp với VLM inference latency
def encode_image(frame: np.ndarray) -> str:
"""Encode camera frame to base64 string."""
_, buffer = cv2.imencode('.jpg', frame, [cv2.IMWRITE_JPEG_QUALITY, 85])
return base64.b64encode(buffer).decode('utf-8')
def get_joint_states(sdk_interface) -> list:
"""Đọc joint states hiện tại từ G1."""
# Đọc 14 joints arm (7 trái + 7 phải) từ Unitree SDK
state = sdk_interface.get_low_state()
arm_joints = [state.motor_state[i].q for i in range(13, 27)] # arm joint indices
return arm_joints
def send_action(sdk_interface, actions: list):
"""Gửi joint commands đến G1 arm."""
cmd = LowCmd_()
# Map action predictions → G1 joint commands
for i, joint_idx in enumerate(range(13, 27)): # arm joints
if i < len(actions):
cmd.motor_cmd[joint_idx].q = float(actions[i])
cmd.motor_cmd[joint_idx].kp = 60.0 # position gain
cmd.motor_cmd[joint_idx].kd = 3.0 # velocity damping
cmd.motor_cmd[joint_idx].dq = 0.0
cmd.motor_cmd[joint_idx].tau = 0.0
sdk_interface.publish_low_cmd(cmd)
def main():
# Init G1 SDK
ChannelFactory.Instance().Init(0, "eth0")
# ... (khởi tạo SDK interface — xem unitree_sdk2py docs)
# Init camera
cap = cv2.VideoCapture(0) # camera trái cổ tay
print(f"Starting robot client. Instruction: '{TASK_INSTRUCTION}'")
print(f"Server: {INFERENCE_SERVER}")
period = 1.0 / CONTROL_FREQ_HZ
while True:
t_start = time.time()
# 1. Đọc camera frame
ret, frame = cap.read()
if not ret:
print("Camera error")
continue
# 2. Đọc joint states
joint_states = get_joint_states(sdk_interface=None) # placeholder
# 3. Gửi đến inference server
payload = {
"full_image": encode_image(frame),
"state": joint_states,
"instruction": TASK_INSTRUCTION,
}
try:
response = requests.post(
f"{INFERENCE_SERVER}/act",
json=payload,
timeout=0.5 # 500ms timeout — nếu server chậm thì skip
)
if response.status_code == 200:
result = response.json()
actions = result["action"]
# 4. Thực thi actions
send_action(sdk_interface=None, actions=actions)
else:
print(f"Server error: {response.status_code}")
except requests.exceptions.Timeout:
print("Inference timeout — keeping last action")
except requests.exceptions.ConnectionError:
print("Cannot connect to inference server")
# Rate control
elapsed = time.time() - t_start
sleep_time = period - elapsed
if sleep_time > 0:
time.sleep(sleep_time)
if __name__ == "__main__":
main()
Chạy robot client:
# Trên G1 onboard computer hoặc workstation (cùng mạng LAN với G1)
python robot_client.py
Bước 3: Locomotion song song với motion.pt
Trong khi arm VLA đang chạy, locomotion có thể chạy song song qua G1 built-in locomotion SDK hoặc motion.pt từ unitree_rl_gym.
Option A: G1 built-in locomotion (đơn giản nhất)
G1 có locomotion controller built-in. Dùng Unitree App hoặc remote controller để điều khiển di chuyển, trong khi arm VLA policy tự điều khiển tay.
# Trên workstation — gửi locomotion velocity commands qua SDK
python -c "
from unitree_sdk2py.core.channel import ChannelFactory
from unitree_sdk2py.idl.unitree_hg.msg.dds_ import SportModeCmd_
ChannelFactory.Instance().Init(0, 'eth0')
# G1 đứng tại chỗ (locomotion mode = stand)
# arm VLA tự chạy song song
cmd = SportModeCmd_()
cmd.mode = 1 # stand mode
# publish cmd...
print('G1 standing, arm VLA running in parallel')
"
Option B: unitree_rl_gym motion.pt policy
Nếu muốn locomotion tùy chỉnh (đi lại trong khi arm hoạt động):
conda activate loco
cd ~/unifolm_ws/unitree_rl_gym
# Chạy pretrained motion.pt policy
python legged_gym/scripts/play.py \
--task g1 \
--load_run pretrained \
--checkpoint motion
# Policy này điều khiển chân G1 (12 joints)
# Trong khi đó arm VLA (robot_client.py) điều khiển tay song song
Lưu ý quan trọng về running simultaneously:
Arm VLA (robot_client.py):
→ Gửi commands cho joints 13-26 (arm + gripper)
→ Frequency: ~5Hz
Locomotion (motion.pt):
→ Gửi commands cho joints 0-11 (legs) + 12 (waist)
→ Frequency: 200-500Hz
Hai hệ thống KHÔNG conflict nhau vì điều khiển joint indices khác nhau.
Safety checklist trước khi chạy trên G1 thật
TRƯỚC KHI KHỞI ĐỘNG:
[ ] E-stop (nút dừng khẩn cấp) đã test và trong tầm tay
[ ] G1 đứng trên nền phẳng, không có chướng ngại vật trong 1m
[ ] Joint position limits đã set trong robot_client.py (kp/kd hợp lý)
[ ] Inference server đang running và đã verify bằng curl test
[ ] Camera kết nối và frame rate ổn định (>10 FPS)
KHI CHẠY:
[ ] Quan sát G1 liên tục — không rời mắt
[ ] Có người thứ 2 sẵn sàng bấm E-stop nếu cần
[ ] Log terminal để debug nếu có lỗi
DỪNG KHI:
[ ] G1 mất thăng bằng hoặc dao động khớp
[ ] Joint torque vượt ngưỡng (kiểm tra qua SDK monitor)
[ ] Arm di chuyển không dự đoán được (policy hallucination)
[ ] Inference server timeout > 3 lần liên tiếp
Troubleshooting thường gặp
Server không nhận được ảnh (full_image: null)
Nguyên nhân: camera capture fail hoặc base64 encode lỗi
Debug: test capture riêng trước
import cv2
cap = cv2.VideoCapture(0)
ret, frame = cap.read()
print("Camera OK:", ret, "Frame shape:", frame.shape if ret else None)
Arm di chuyển giật cục không mượt
Nguyên nhân: inference latency không ổn định
Fix options:
1. Tăng kp/kd smoothing trong robot_client.py
2. Thêm exponential moving average cho action output:
alpha = 0.7 # EMA filter
smoothed_action = alpha * new_action + (1 - alpha) * prev_action
send_action(smoothed_action)
prev_action = smoothed_action
Policy không thực hiện đúng task
Checklist debug theo thứ tự:
1. Lighting có giống lúc training không? (đèn khác = visual distribution shift)
2. Vật thể có đúng vị trí không? (lệch quá nhiều = out-of-distribution)
3. Camera calibration còn đúng không? (wrist camera có bị xê dịch không?)
4. Instruction text có đúng với training instruction không?
5. Checkpoint có phải best checkpoint không? (kiểm tra val loss thấp nhất)
G1 arm tự reset về vị trí home sau vài giây
Nguyên nhân: safety timeout trong G1 SDK — nếu không nhận được command
trong N giây, arm tự về home position
Fix: đảm bảo robot_client.py gửi command liên tục (kể cả khi action = [0, 0, ...])
Tóm tắt toàn series
Sau 5 bài, bạn đã hoàn thành pipeline đầy đủ:
| Bài | Input | Output | Tools |
|---|---|---|---|
| 1: Architecture | — | Hiểu kiến trúc | 3 repos |
| 2: Data collection | G1 + Meta Quest 3 | 50+ JSON demos | xr_teleoperate |
| 3: Data pipeline | JSON demos | RLDS dataset | unitree_IL_lerobot + unifolm-vla |
| 4: Fine-tune | RLDS dataset | VLA checkpoint | DeepSpeed / LoRA |
| 5: Deploy (bài này) | Checkpoint + G1 | Robot thực hiện task | FastAPI + Unitree SDK |
Bước tiếp theo để cải thiện:
- Thêm data diversity: 200+ demos với variation ánh sáng, vị trí vật thể
- Multi-task: thu thập data cho nhiều task, train trên cùng 1 model
- Locomotion tích hợp sâu hơn: train policy biết đi đến vật thể trước khi nhặt
- Khi Unifolm-VLM-0 public: fine-tune lại từ checkpoint đó → performance tốt hơn ~15-20%



