wholebody-vlatwist2unitree-g1picowholebody-vlateleoperationsim2realredishumanoid

TWIST2: PICO teleop và G1 sim2real

Dựng vòng TWIST2 từ ONNX checkpoint đến G1 thật: Redis bus, PICO teleop, sim2sim, sim2real và data recording.

Nguyễn Anh Tuấn11 tháng 6, 202617 phút đọc
TWIST2: PICO teleop và G1 sim2real

Vì sao TWIST2 là stack thứ hai?

bài 1 về OpenWBT, chúng ta bắt đầu bằng một stack thực dụng: Apple Vision Pro, joystick, policy lower-body và IK upper-body cho Unitree G1/H1. OpenWBT rất hợp để hiểu cách tách teleoperation thành các mảnh debug được. Nhưng nếu mục tiêu của lab là thu dữ liệu whole-body imitation và đi dần tới VLA humanoid, bạn cần một stack cho phép operator stream chuyển động toàn thân, retarget sang robot, chạy low-level RL tracker, ghi dữ liệu và chuyển cùng logic đó từ simulator sang G1 thật.

Đó là lý do bài 2 đi vào TWIST2. Paper TWIST2: Scalable, Portable, and Holistic Humanoid Data Collection System mô tả một hệ teleoperation không cần optical mocap, dùng PICO 4 Ultra, hai PICO Motion Tracker ở chân, controller cầm tay và cổ robot 2 DoF để tạo egocentric active vision. Theo paper, TWIST2 nhắm vào full whole-body control thay vì chỉ điều khiển base velocity hoặc chỉ retarget cổ tay. Repo amazon-far/TWIST2 cũng cung cấp checkpoint assets/ckpts/twist2_1017_20k.onnx, script chạy simulator, script chạy robot thật, GUI và script ghi dữ liệu.

Bài này là hướng dẫn theo kiểu "đứng cạnh robot trong lab": đi từ checkpoint ONNX tới vòng chạy thật bằng bash run_motion_server.sh, bash sim2sim.sh, bash teleop.sh, bash sim2real.sh, với Redis làm bus giữa high-level motion streaming và low-level RL controller. Nếu bạn đang theo series, bài sau sẽ nối sang EgoHumanoid và dữ liệu egocentric, còn bài 4 sẽ bàn retarget/eval qua VIRAL.

Roadmap series

  1. OpenWBT: G1 teleop trong MuJoCo/Isaac: dựng môi trường và hiểu split joystick/IK.
  2. TWIST2: PICO teleop và G1 sim2real: dùng Redis để nối retargeting, motion streaming, RL controller và data recording.
  3. EgoHumanoid: dữ liệu egocentric cho thao tác: nối camera góc nhìn người vận hành với action/pose.
  4. VIRAL: retarget và kiểm tra kỹ năng: đánh giá motion source bên ngoài trước khi train policy.
  5. FromW1: chuyển kỹ năng sang robot thật: xử lý latency, contact và giới hạn actuator.
  6. CLONE: closed-loop whole-body teleop: nhìn closed-loop teleop như data stack dài hạn.

Nguồn kỹ thuật cần mở song song

Nguồn Dùng để làm gì Chi tiết quan trọng
TWIST2 README Cài môi trường và chạy workflow chính Có hai môi trường Conda: twist2 cho training/deploy/data collection và gmr cho online retargeting
TWIST2 arXiv Hiểu mục tiêu hệ thống Low-level controller chạy ở 50 Hz; high-level sinh command từ teleop hoặc policy thị giác
sim2real.sh Kiểm tra path checkpoint, network interface và flag real robot Dùng assets/ckpts/twist2_1017_20k.onnx, --net, --device cuda, --use_hand
teleop.sh Chạy PICO teleop online Kích hoạt env gmr, gọi xrobot_teleop_to_robot_w_hand.py, target FPS 100
twist2_dataset.yaml Train controller riêng Cần sửa root_path về folder dataset đã tải; motions có weight và mô tả

Operator dùng VR trong một workflow robot teleoperation
Operator dùng VR trong một workflow robot teleoperation

Mental model: Redis là xương sống của vòng lặp

TWIST2 không gộp mọi thứ vào một process. Nó tách thành hai tầng:

High-level side
  offline motion server hoặc PICO teleop
  -> retarget / motion library
  -> 35D mimic observation + hand pose + neck command
  -> Redis keys

Low-level side
  sim controller hoặc real G1 controller
  -> đọc robot state / simulated state
  -> đọc command từ Redis
  -> ghép observation history
  -> ONNX policy
  -> target 29 DoF body + optional dexterous hands

Điểm beginner cần nhớ: bash sim2sim.sh hoặc bash sim2real.sh không tự sinh chuyển động toàn thân từ PICO. Chúng chỉ chạy low-level controller và đọc action_body_unitree_g1_with_hands, action_hand_left_unitree_g1_with_hands, action_hand_right_unitree_g1_with_hands, action_neck_unitree_g1_with_hands từ Redis. Phía sinh các key này là bash run_motion_server.sh nếu bạn muốn replay motion offline, hoặc bash teleop.sh nếu bạn muốn operator đeo PICO và điều khiển realtime.

Vì vậy thứ tự debug đúng là:

# Terminal 0: Redis
redis-server --daemonize yes

# Terminal 1: high-level offline stream để warm up key trong Redis
bash run_motion_server.sh

# Terminal 2: low-level simulator controller
bash sim2sim.sh

# Terminal 3: thay offline stream bằng PICO teleop khi đã sẵn sàng
bash teleop.sh

# Terminal 4: chỉ dùng sau khi sim ổn và robot đã ở chế độ an toàn
bash sim2real.sh

README của TWIST2 nhấn mạnh lần đầu chạy sim2sim nên warm up Redis bằng motion server. Lý do rất thực tế: low-level policy cần một command hợp lệ ngay từ frame đầu. Nếu Redis chưa có key, process controller có thể lỗi JSON, đọc giá trị rỗng hoặc bắt đầu bằng command không mong muốn. Trong lab, hãy xem Redis như "topic bus" tối giản: dễ inspect, dễ restart, nhưng không có type safety như ROS message.

Bước 1: chuẩn bị môi trường và Redis

TWIST2 dùng hai Conda environment vì Isaac Gym thường khóa Python 3.8, trong khi phần GMR/online retargeting dùng Python mới hơn. README đề xuất:

conda create -n twist2 python=3.8
conda activate twist2

cd rsl_rl && pip install -e . && cd ..
cd legged_gym && pip install -e . && cd ..
cd pose && pip install -e . && cd ..

pip install redis[hiredis]
pip install onnx onnxruntime-gpu
pip install customtkinter

Sau đó tạo môi trường retargeting:

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

# Cài GMR theo hướng dẫn repo TWIST2
pip install -e /path/to/GMR

Redis nên chạy local khi bạn kiểm thử trên laptop:

sudo apt install -y redis-server
sudo systemctl enable redis-server
sudo systemctl start redis-server
redis-cli ping

Nếu high-level process chạy trên máy khác, README có hướng dẫn bind Redis ra 0.0.0.0 và tắt protected mode. Với lab thật, đừng làm vậy trên mạng chung. Redis trong TWIST2 không có authentication mặc định; nếu bạn mở cổng 6379 ra Wi-Fi công cộng, bất kỳ máy nào thấy Redis đều có thể ghi command vào robot. Thực hành tốt hơn là dùng mạng lab riêng, firewall cụ thể theo IP, hoặc SSH tunnel.

Bước 2: hiểu checkpoint twist2_1017_20k.onnx

Repo cung cấp checkpoint assets/ckpts/twist2_1017_20k.onnx để test trực tiếp. sim2real.sh trỏ tới file này:

ckpt_path=${SCRIPT_DIR}/assets/ckpts/twist2_1017_20k.onnx

python server_low_level_g1_real.py \
  --policy ${ckpt_path} \
  --net ${net} \
  --device cuda \
  --use_hand

Trong server_low_level_g1_real.py, ONNX model được load bằng ONNX Runtime. Nếu có CUDAExecutionProvider, policy chạy GPU; nếu không, nó fallback CPU. Controller đặt num_actions = 29, tức output body action là 29 joint target tương đối so với default_dof_pos. Sau khi clip action, code tính:

target_dof_pos = default_dof_pos + raw_action * action_scale

Đây không phải torque policy trực tiếp. Policy sinh desired joint positions, rồi wrapper robot gửi target xuống vòng PD thấp hơn. Với beginner, điều này quan trọng vì bạn sẽ debug theo ba lớp: command mimic 35D, output policy 29D, và tracking thực tế qua actuator/PD.

Observation cũng đáng bóc tách:

Thành phần Kích thước Ý nghĩa
n_mimic_obs 35 Command whole-body: vận tốc xy gốc, z, roll/pitch, yaw angular velocity, 29 joint reference
n_proprio 92 Angular velocity, roll/pitch, joint position, joint velocity đã scale, last action
n_obs_single 127 35 + 92 cho một frame
history_len 10 Lịch sử proprio/action để policy thấy quán tính ngắn hạn
total_obs_size 1402 127 * 11 + 35, gồm frame hiện tại, lịch sử và future mimic

Khi sim hoặc robot thật chạy lệch, hãy log từng lớp theo thứ tự. Nếu action_body_* trong Redis đã bất thường, lỗi nằm ở motion server hoặc teleop. Nếu Redis đúng nhưng target_dof_pos giật, lỗi có thể nằm ở observation scaling, history, latency hoặc policy runtime. Nếu target mượt mà robot vẫn rung, kiểm tra network interface, mode robot, PD gain, nhiệt độ actuator và giới hạn joint.

Bước 3: chạy offline motion server

run_motion_server.sh là cách đơn giản nhất để tạo command high-level mà không cần PICO. Script chọn motion trong assets/example_motions, vào thư mục deploy_real, rồi chạy server_motion_lib.py với robot unitree_g1_with_hands, visualizer và Redis local.

conda activate twist2
bash run_motion_server.sh

Bạn có thể đổi motion trong script:

motion_file="${script_dir}/assets/example_motions/0807_yanjie_walk_001.pkl"

Trong server_motion_lib.py, motion được load bằng MotionLib, rồi mỗi control_dt = 0.02 giây sinh một mimic observation. 0.02 giây tương ứng 50 Hz, khớp với mô tả low-level controller trong paper. Command được publish vào Redis theo key action_body_<robot>, hand mặc định là zero, neck mặc định [0, 0].

Một lỗi hay gặp: run_motion_server.sh dùng unitree_g1_with_hands, trong khi code cũ hoặc script tùy biến của bạn có thể dùng unitree_g1. Key Redis khác nhau theo robot name. Nếu controller real đang đọc action_body_unitree_g1_with_hands nhưng motion server publish action_body_unitree_g1, robot sẽ đứng im hoặc đọc key cũ. Khi debug, dùng:

redis-cli keys '*unitree_g1*'
redis-cli get action_body_unitree_g1_with_hands

Bước 4: chạy sim2sim trước khi đụng robot thật

sim2sim.sh dùng cùng checkpoint ONNX nhưng chạy trong MuJoCo:

conda activate twist2
bash sim2sim.sh

Script gọi server_low_level_g1_sim.py với XML ../assets/g1/g1_sim2sim_29dof.xml, policy ONNX, --measure_fps 1, --policy_frequency 100--limit_fps 1. README ghi expected policy FPS từ decimation là khoảng 50 Hz. Nếu laptop yếu, FPS thấp hơn sẽ làm policy execution xấu đi. Hãy xem 50 Hz như ngưỡng vận hành cho low-level tracking, không phải con số trang trí.

Checklist sim2sim:

Kiểm tra Cách làm Kết quả mong muốn
Redis sống redis-cli ping PONG
Có command body redis-cli get action_body_unitree_g1_with_hands JSON list dài 35
Simulator mở Quan sát MuJoCo G1 không rơi, không rung mạnh ở idle
FPS đủ Xem terminal controller Xấp xỉ 50 Hz sau decimation
Motion offline chạy Mở run_motion_server.sh terminal khác Robot bắt đầu track motion

Nếu robot trong sim đứng yên dù motion server đang chạy, đừng vội sửa policy. Trước hết kiểm tra key Redis, redis_ip, robot name, và liệu motion server đã kết thúc motion rồi interpolate về default pose chưa.

Bước 5: đổi offline motion thành PICO teleop

Khi sim2sim ổn với motion offline, chuyển sang teleop.sh:

conda activate gmr
bash teleop.sh

Script này gọi:

python xrobot_teleop_to_robot_w_hand.py \
  --robot unitree_g1 \
  --actual_human_height 1.6 \
  --redis_ip localhost \
  --target_fps 100 \
  --measure_fps 1

Comment trong script nói chiều cao actual_human_height nên nhỏ hơn chiều cao thật một chút vì PICO estimation có sai số. Đừng xem 1.6 là giá trị universal. Với operator cao 1.75 m, bạn vẫn có thể bắt đầu thấp hơn và quan sát knee/hip tracking trong sim trước. Mục tiêu là retargeted pose không làm G1 squat quá sâu, bước quá dài hoặc nghiêng pelvis ngoài vùng policy đã học.

xrobot_teleop_to_robot_w_hand.py có một state machine rõ ràng:

State Ý nghĩa Khi nào dùng
idle Gửi default mimic observation Chuẩn bị operator, kiểm tra stream
teleop Retarget PICO/SMPL-X sang G1 và gửi command Điều khiển realtime
pause Giữ pose cuối hoặc command an toàn Tạm dừng giữa episode
exit Interpolate về default rồi thoát Kết thúc buổi

Controller mapping trong file cũng rất thực tế. Right controller key_one chuyển idle -> teleop -> pause -> teleop. Left controller key_one thoát. Left axis_click gọi emergency stop bằng cách kill process sim2real. Trigger và grip điều khiển đóng/mở tay. Joystick trái tạo velocity xy, joystick phải tạo yaw. Đây là điểm khác OpenWBT: TWIST2 lấy PICO whole-body stream làm nguồn chính cho mimic observation, rồi vẫn cho operator thêm velocity/hand control ở controller.

Trong hàm extract_mimic_obs_whole_body, 35D mimic observation gồm:

[base_vel_x, base_vel_y,
 root_z,
 roll, pitch,
 yaw_angular_velocity,
 29 robot_joint_positions]

Sau retargeting, teleop process ghi vào Redis:

action_body_unitree_g1_with_hands
action_hand_left_unitree_g1_with_hands
action_hand_right_unitree_g1_with_hands
action_neck_unitree_g1_with_hands
t_action
controller_data

Nếu muốn ghi log để train VLA sau này, t_actioncontroller_data là hai key nên giữ lại cùng camera/proprioception. Chúng giúp bạn phân biệt robot đang được operator điều khiển thế nào, lúc nào pause, lúc nào đóng tay, lúc nào operator xoay yaw bằng stick.

Bước 6: chuyển sang G1 thật bằng sim2real.sh

Chỉ chạy real robot khi sim2sim đã ổn. Flow từ README:

  1. Bật G1 và nối laptop với robot qua Ethernet.
  2. Đặt IP laptop trong subnet robot, ví dụ 192.168.123.222/24.
  3. Kiểm tra ping 192.168.123.164.
  4. Dùng remote Unitree đưa robot vào deploy/dev mode theo hướng dẫn G1.
  5. Đổi net=eno1 trong sim2real.sh thành interface thật của laptop.
  6. Chạy low-level controller trước, rồi chạy offline motion hoặc teleop ở terminal khác.

Command:

conda activate twist2
bash sim2real.sh

server_low_level_g1_real.py sẽ yêu cầu robot về default pose, chờ remote xác nhận, rồi bắt đầu loop policy. Trong loop, nó đọc state robot qua G1RealWorldEnv, publish state body/hand lên Redis, đọc command body/hand/neck từ Redis, ghép observation 1402 chiều, chạy ONNX policy và gửi target joint xuống robot. Nếu bật --use_hand, file cũng khởi tạo Dex3_1_Controller và gửi command cho hai tay.

Thói quen vận hành an toàn:

Rủi ro Cách giảm
Network interface sai Luôn ip addr, ping, và in --net trước khi chạy
Redis key cũ redis-cli flushdb trong mạng test trước khi start buổi mới, rồi warm up bằng motion server
Operator vào teleop quá đột ngột Dùng state idle, quan sát sim, rồi mới chuyển teleop
FPS thấp Chạy --measure_fps; nếu dưới 50 Hz ổn định, đừng chuyển real
Hand command sai Test body không hand trước, sau đó bật --use_hand
Robot rung khi idle Dừng ngay, kiểm tra default pose, PD gain, joint limit và command 35D

twist2_dataset.yaml: khi nào cần sửa?

Nếu bạn chỉ dùng checkpoint có sẵn, bạn không cần train lại controller. Nhưng nếu muốn train controller riêng hoặc thêm motion dataset, file legged_gym/motion_data_configs/twist2_dataset.yaml là điểm vào. File có root_path trỏ tới thư mục motion data của tác giả và danh sách motion, ví dụ các file OMOMO_g1_GMR/...pkl, mỗi motion có weight: 1.0description: general movement.

Bạn cần sửa:

root_path: /data/twist2/motion_data
motions:
  - file: OMOMO_g1_GMR/sub1_clothesstand_000.pkl
    weight: 1.0
    description: general movement

Ba nguyên tắc:

Nguyên tắc Vì sao
root_path phải là folder đã unzip dataset Nếu để path của tác giả, training không tìm được motion
Motion phải đã retarget sang G1 format Low-level tracker học theo joint/reference của robot đích
Weight không nên chỉnh bừa Weight quyết định distribution mà controller học; tăng quá nhiều motion dynamic có thể làm policy yếu ở idle/slow motion

Khi bạn thêm motion do lab tự thu, hãy version hóa manifest riêng: nguồn motion, operator, robot target, license, ngày retarget, script retarget, và test sim2sim. Đây là bước nối tự nhiên với WholeBodyVLA data + WBCsim2real evaluation.

GUI và data recording

TWIST2 có gui.sh rất ngắn:

conda activate twist2
python gui.py

Nhưng gui.py không chỉ là wrapper thẩm mỹ. Nó tạo các panel terminal cho offline motion, online teleop, sim2sim, sim2real, data recording, neck control, ZED teleop/policy và các nút start/kill/clear. Với lab có nhiều terminal, GUI giảm lỗi operator chạy sai thứ tự. Tuy vậy, hãy hiểu command gốc trước khi dùng GUI; nếu GUI fail, bạn vẫn phải biết bash sim2sim.sh đang gọi file nào.

Ghi dữ liệu dùng:

bash data_record.sh

Script kích hoạt env twist2, vào deploy_real, đặt robot_ip="192.168.123.164", data_frequency=30, rồi chạy server_data_record.py --frequency ${data_frequency} --robot_ip ${robot_ip}. Tần số 30 Hz không giống low-level policy 50 Hz hay teleop target 100 Hz. Đây là data logging rate. Khi align dataset, bạn cần timestamp và interpolation, không nên giả định mọi stream cùng frequency.

TWIST2 so với OpenWBT

Tiêu chí TWIST2 OpenWBT
Thiết bị VR chính PICO 4 Ultra, controller, hai PICO Motion Tracker ở chân Apple Vision Pro và joystick
Mục tiêu thiết kế Portable, mocap-free, full whole-body teleop và data collection Whole-body teleop thực dụng cho G1/H1, split lower-body/upper-body
Command high-level 35D mimic obs: root velocity/height/orientation + 29 joint reference Lower-body joystick/skill policy, upper-body IK từ hand pose
Low-level policy FPS Paper/README đặt kỳ vọng khoảng 50 Hz sau decimation Không trình bày cùng một contract 50 Hz trong README public
Teleop FPS teleop.sh đặt --target_fps 100 Phụ thuộc web/VR/ROS/runner setup
Bus giữa process Redis keys cho body, hand, neck, controller data Pipeline Python/ROS/WebRTC theo entrypoint của repo
GUI gui.shgui.py gom nhiều panel README chủ yếu dùng script/command riêng
Data recording data_record.sh, mặc định 30 Hz Repo public không có root-level data_record.sh tương đương
Điểm mạnh Thu whole-body imitation có cấu trúc, dễ nối sang VLA Dễ hiểu split control, phù hợp dựng baseline G1/H1
Điểm yếu cần lưu ý PICO pose kém mocap ở elbow/knee; setup Redis/retarget nhiều process Không phải full PICO whole-body tracking stack; data recording không nổi bật như TWIST2

Nếu lab mới bắt đầu, dùng OpenWBT để hiểu robot, network và safety trước. Khi đã có quy trình vận hành G1 ổn định, chuyển sang TWIST2 để thu demonstration toàn thân và bắt đầu nghĩ tới VLA. Đừng coi hai stack là đối thủ trực tiếp; chúng là hai bài test khác nhau cho cùng mục tiêu: robot có đi, cúi, với, nắm và ghi lại được dữ liệu đủ sạch hay không.

Checklist một buổi chạy thật

[ ] Redis chạy local, không mở bừa ra mạng chung
[ ] `redis-cli ping` trả PONG
[ ] `run_motion_server.sh` publish được 35D command
[ ] `sim2sim.sh` đạt FPS gần 50 Hz và G1 sim không rung
[ ] `teleop.sh` nhận PICO stream, state machine idle/teleop/pause hoạt động
[ ] `actual_human_height` đã tune trong sim
[ ] G1 thật ping được qua Ethernet
[ ] `sim2real.sh` dùng đúng network interface
[ ] Remote/killswitch sẵn sàng, operator đứng ngoài vùng nguy hiểm
[ ] `data_record.sh` chỉ bật khi vòng điều khiển đã ổn

Kết luận

TWIST2 đáng học vì nó cho thấy một humanoid VLA stack không bắt đầu từ model ngôn ngữ. Nó bắt đầu từ vòng điều khiển có thể chạy thật: high-level motion hoặc PICO teleop sinh 35D mimic observation, Redis truyền command, low-level RL tracker dùng checkpoint ONNX để điều khiển 29 DoF của G1, optional hand controller xử lý tay, và data recorder ghi lại episode. Khi vòng này ổn, VLA mới có dữ liệu và actuator interface đáng tin để học.

Trong bài tiếp theo, EgoHumanoid sẽ đưa câu hỏi lên một tầng khác: không chỉ operator stream motion, mà còn làm sao biến dữ liệu egocentric và action trace thành dataset học thao tác humanoid.

Bài viết liên quan

NT

Nguyễn Anh Tuấn

Robotics & AI Engineer. Building VnRobo — sharing knowledge about robot learning, VLA models, and automation.

Khám phá VnRobo

Bài viết liên quan

CLONE: MoE teleop và chọn stack
wholebody-vla

CLONE: MoE teleop và chọn stack

11/6/202617 phút đọc
NT
EgoHumanoid: human demo sang G1 VLA
wholebody-vla

EgoHumanoid: human demo sang G1 VLA

11/6/202617 phút đọc
NT
VIRAL: RGB sim2real cho G1 loco-manip
wholebody-vla

VIRAL: RGB sim2real cho G1 loco-manip

11/6/202616 phút đọc
NT