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
- OpenWBT: G1 teleop trong MuJoCo/Isaac: dựng môi trường và hiểu split joystick/IK.
- TWIST2: PICO teleop và G1 sim2real: dùng Redis để nối retargeting, motion streaming, RL controller và data recording.
- 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.
- VIRAL: retarget và kiểm tra kỹ năng: đánh giá motion source bên ngoài trước khi train policy.
- FromW1: chuyển kỹ năng sang robot thật: xử lý latency, contact và giới hạn actuator.
- 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ả |
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 và --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_action và controller_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:
- Bật G1 và nối laptop với robot qua Ethernet.
- Đặt IP laptop trong subnet robot, ví dụ
192.168.123.222/24. - Kiểm tra
ping 192.168.123.164. - Dùng remote Unitree đưa robot vào deploy/dev mode theo hướng dẫn G1.
- Đổi
net=eno1trongsim2real.shthành interface thật của laptop. - 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.0 và description: 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 + WBC và sim2real 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 | Có gui.sh và gui.py gom nhiều panel |
README chủ yếu dùng script/command riêng |
| Data recording | Có 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.