Mục tiêu của bài này
Năm bài trước của series đã đi từ kiến trúc SONIC, đánh giá sim, dữ liệu huấn luyện, triển khai qua ZMQ đến teleoperation và VLA. Bài 6 khép lại series bằng một lớp đang rất đáng chú ý trong nhánh GR00T Whole-Body Control: MotionBricks. Nếu SONIC là policy điều khiển toàn thân để robot thực thi chuyển động, MotionBricks là lớp sinh chuyển động thời gian thực: nó biến ý định cấp cao thành token hoặc primitive chuyển động có thể đưa vào pipeline điều khiển.
Điểm cần hiểu ngay từ đầu: MotionBricks trong repo NVlabs/GR00T-WholeBodyControl hiện là preview release dưới thư mục motionbricks/. README của dự án mô tả nó gồm hai phần chính: demo G1 tương tác và pipeline huấn luyện synthetic tự chứa. Trang dự án nói MotionBricks đạt thông lượng 15.000 FPS với độ trễ 2 ms trên benchmark của paper, bao phủ hơn 350.000 motion skills bằng một latent neural backbone. Đây là claim nghiên cứu, không có nghĩa một robot thật sẽ điều khiển motor ở 15.000 Hz. Với humanoid, con số này nên được đọc là khả năng sinh hoặc truy vấn motion backbone đủ nhanh để nằm trước một policy điều khiển 50-200 Hz.
Trong bài này ta nối hai thế giới lại với nhau:
- MotionBricks tạo hoặc mã hóa chuyển động qua VQVAE, pose model và root model.
- SONIC nhận token chuyển động qua observation
token_state. - ZMQ Protocol v4 có thể stream
token_statetrực tiếp vào decoder policy, bỏ qua encoder ở phía robot.
Nếu bạn chưa đọc các bài trước, nên xem lại Kiến trúc GR00T SONIC, triển khai SONIC qua ZMQ và teleop/VLA cho humanoid. Ngoài series, hai bài nền hữu ích là training pipeline WholeBodyVLA và hướng dẫn open-source WholeBodyVLA.

MotionBricks giải quyết phần nào của bài toán?
Trong whole-body control, có một khoảng trống lớn giữa "ý định" và "torque". Người dùng hoặc VLA có thể nói: đi tới, đổi style đi bộ, bò thấp, né vật, cầm vật, ngồi xuống, nhảy qua ghế. Policy điều khiển thấp hơn lại cần trạng thái liên tục, reference motion hoặc latent action ổn định để tạo hành động motor. Nếu truyền trực tiếp lệnh ngôn ngữ hoặc velocity thô xuống policy, robot dễ tạo gait cứng, chuyển pha xấu hoặc lỗi khi nhiệm vụ yêu cầu cả chân, thân và tay.
MotionBricks đặt một lớp motion-generation ở giữa. Paper mô tả hai ý chính: một modular latent generative backbone và smart primitives. Backbone học không gian chuyển động lớn; smart primitives cho phép điều khiển theo navigation, style, keyframe hoặc object interaction mà không phải viết graph animation thủ công. Trong robotics demo, lớp này không thay thế controller vật lý. Nó tạo reference hoặc token chuyển động mà whole-body policy có thể track.
Với SONIC, cách nghĩ thực dụng là:
| Lớp | Vai trò |
|---|---|
| Người dùng, planner hoặc VLA | Sinh ý định: hướng, style, thao tác, token hoặc keyframe |
| MotionBricks | Sinh chuỗi motion feature, pose token hoặc root trajectory khả dụng |
| ZMQ v4 | Đóng gói token đã tính sẵn trong trường token_state |
| SONIC decoder policy | Dùng token_state cùng state hiện tại để xuất action điều khiển |
| Robot/simulator | Thực thi action, cập nhật history và trạng thái vật lý |
Điều làm Protocol v4 quan trọng là nó cho phép nguồn bên ngoài gửi token đã tính sẵn. Các protocol cũ gửi motion frame rồi robot-side encoder mã hóa lại thành token. V4 bỏ bước đó: message chỉ cần token_state, còn policy có observation token_state sẽ nhận trực tiếp.
Cấu trúc thư mục cần đọc trong motionbricks/
README của MotionBricks liệt kê project structure rất rõ. Với beginner, chỉ cần bắt đầu từ sáu vị trí:
| Đường dẫn | Cần học gì |
|---|---|
scripts/interactive_demo_g1.py |
Demo G1 trong MuJoCo, nơi control signal được tạo và motion backbone sinh frame mới |
scripts/train_vqvae.py |
Huấn luyện VQVAE, tức motion tokenizer cho pose |
scripts/train_pose.py |
Huấn luyện pose backbone, dùng VQVAE pretrained để encode motion thành token |
scripts/train_root.py |
Huấn luyện root backbone, dự đoán root motion liên tục, không cần VQVAE |
motionbricks/data/synthetic_dataset.py |
Dataset synthetic để kiểm tra pipeline mà không cần BONES-SEED thật |
out/motionbricks_* |
Checkpoint, config, skeleton và stats cho VQVAE, pose, root |
Các checkpoint nằm dưới out/ và được quản lý bằng Git LFS. README khuyến nghị kiểm tra kích thước để tránh nhầm file pointer: out/motionbricks_vqvae/version_1/checkpoints/*.ckpt khoảng 273 MB, out/motionbricks_pose/version_1/checkpoints/*.ckpt khoảng 1,6 GB, và out/motionbricks_root/version_1/checkpoints/*.ckpt khoảng 391 MB. Nếu file chỉ khoảng 1 KB, bạn chưa pull LFS thật.
cd GR00T-WholeBodyControl
git lfs pull --include="motionbricks/out/**" --exclude=""
git lfs pull --include="motionbricks/assets/skeletons/g1/meshes/**" --exclude=""
cd motionbricks
Đây là bước quan trọng với demo G1. Không có mesh và checkpoint thật, script vẫn có thể import nhưng demo sẽ fail ở runtime hoặc load model sai.
Demo G1: đọc scripts/interactive_demo_g1.py
File scripts/interactive_demo_g1.py là entry point dễ hiểu nhất. Script tạo demo_agent = navigation_demo(args), reset agent, rồi lặp qua từng step MuJoCo. Trong mỗi step, nó làm bốn việc:
- Lấy frame hiện tại từ
demo_agent.full_agent.get_next_frame(). - Lấy context bằng
get_context_motion_features()hoặcget_context_mujoco_qpos(). - Dùng controller tạo
control_signalstừ viewer, model và data. - Gọi
demo_agent.full_agent.generate_new_frames(control_signals, dt)để sinh đoạn chuyển động mới.
Các option quan trọng nằm ở cuối file. --humanoid_xml mặc định trỏ tới assets/skeletons/g1/scene_29dof.xml. --result_dir mặc định là ./out. --controller có thể là wasd hoặc random. --use_qpos=1 nghĩa là truyền context MuJoCo qpos thay vì motion feature. --generate_dt=2.0 nhân với controller dt để quyết định horizon sinh motion mới.
Chạy cơ bản:
DISPLAY=:1 python scripts/interactive_demo_g1.py
WASD điều khiển hướng di chuyển tương đối với camera. Các phím style như V, Z, X, B, R, T, C, E, F, G, Q chọn slow walk, crawl, boxing walk, stealth, injured, happy dance, zombie và vài style khác. Điểm cần nhớ là demo này không phải ZMQ deployment. Nó là môi trường để nhìn MotionBricks tự sinh frame trong vòng lặp MuJoCo trước khi bạn nghĩ tới việc nối sang SONIC policy runtime.

VQVAE: motion tokenizer trong scripts/train_vqvae.py
VQVAE là phần biến motion liên tục thành biểu diễn rời rạc hoặc latent có cấu trúc. Trong MotionBricks preview, scripts/train_vqvae.py được viết để beginner kiểm tra training loop mà không cần dataset thật. Nó load config từ:
out/motionbricks_vqvae/version_1/hparams.yaml
Sau đó script patch config để chạy single GPU: devices = 1, num_nodes = 1, max_steps = args.max_steps, accelerator = "auto", strategy = "auto". Nó cũng sửa đường dẫn skeleton và motion statistics vào version directory:
out/motionbricks_vqvae/version_1/skeleton
out/motionbricks_vqvae/version_1/stats/motion
Phần beginner hay bỏ qua là motion_rep. Script gọi load_motion_rep(conf), sau đó lấy feat_dim = len(motion_rep.indices['all']). Nghĩa là synthetic data không chọn đại chiều tensor; nó phải khớp đúng motion representation của skeleton/config. Comment trong dataset nói ví dụ G1Skeleton34 có feature dimension khoảng 418. Nếu feature dimension sai, model có thể chạy tới lúc batch vào network rồi lỗi shape.
Lệnh thử nhanh:
python scripts/train_vqvae.py --max_steps 100 --batch_size 8 --num_samples 500
Trong setup thật, VQVAE học cách nén pose motion thành token để pose backbone có một không gian gọn hơn. Trong setup synthetic, dữ liệu chỉ là tensor random nên kết quả không có ý nghĩa motion đẹp; giá trị của nó là kiểm tra config, dataloader, instantiation bằng Hydra/OmegaConf, Lightning trainer và shape của motion representation.
Pose model: token hóa pose rồi sinh chuyển động
scripts/train_pose.py là nơi MotionBricks bắt đầu giống một motion generator hơn. Script này load config từ:
out/motionbricks_pose/version_1/hparams.yaml
Nó cũng dùng SyntheticMotionDataset, nhưng khác VQVAE ở chỗ pose model cần VQVAE pretrained. Comment trong file nói rõ: pose model requires a pretrained VQVAE checkpoint to encode motions into discrete tokens, và weights được load tự động từ path trong config. Khi script chạy, nó in VQVAE loaded: {model.vqvae_model_loaded} để bạn biết model có thật sự nạp được tokenizer hay không.
Luồng logic là:
- Load motion representation và feature dimension.
- Tạo synthetic dataset với motion có độ dài 80-200 frame.
- Instantiate
pose_vqvae_networkbằngmotion_rep.dual_rep.local_motion_rep. - Instantiate
backbone_networkbằng fullmotion_rep. - Tạo Lightning model với VQVAE network, backbone, optimizer và scheduler.
Lệnh thử:
python scripts/train_pose.py --max_steps 100 --batch_size 8 --num_samples 500
Trong kiến trúc nối với SONIC, pose model là nguồn tự nhiên để tạo token mô tả tư thế toàn thân hoặc đoạn motion ngắn. Nếu VLA hoặc planner chỉ phát "đi kiểu injured về phía trước", pose backbone có thể biến primitive đó thành latent motion. Token này sau đó là ứng viên để đưa sang token_state, miễn là dimension và semantics khớp encoder/decoder policy của SONIC.
Root model: quỹ đạo gốc không cần VQVAE
scripts/train_root.py load config từ:
out/motionbricks_root/version_1/hparams.yaml
Script này không nạp VQVAE. Comment trong file nói root model directly predicts continuous root motion values. Đó là khác biệt rất quan trọng: pose model xử lý thân, chân, tay trong không gian token/pose; root model xử lý chuyển động gốc như tiến, xoay, cao độ hoặc trajectory base liên tục.
Dataset synthetic cho root dài hơn: min_frames=200, max_frames=400. Điều này hợp lý vì root trajectory cần horizon dài để học hướng, tốc độ và drift. Trong humanoid robotics, root không chỉ là tọa độ animation. Nó liên quan đến base frame, heading, foot placement và khả năng policy tracking giữ robot không ngã.
Lệnh thử:
python scripts/train_root.py --max_steps 100 --batch_size 8 --num_samples 500
Khi nối với SONIC, bạn không nên nghĩ "pose token đủ rồi". Nếu token pose nói robot đang bước sang trái nhưng root trajectory hoặc heading reference không nhất quán, decoder có thể nhận tín hiệu mâu thuẫn. Vì vậy adapter thực tế nên xuất một gói gồm token pose, root/heading metadata và hand joints khi có manipulation.

Synthetic dataset không phải dữ liệu huấn luyện thật
motionbricks/data/synthetic_dataset.py rất ngắn nhưng đáng đọc. Class SyntheticMotionDataset trả về dict:
{"keyid": idx, "motion": motion}
Trong đó motion là tensor random shape [T, feat_dim], với T được chọn ngẫu nhiên giữa min_frames và max_frames. Hàm collate_batch() pad các sequence khác độ dài thành batch [B, T, D], trả thêm motion_len, motion_pad_mask và batch_size.
Điều này có hai hệ quả:
| Hiểu đúng | Hiểu sai |
|---|---|
| Synthetic dataset dùng để verify end-to-end training pipeline | Synthetic dataset tạo ra motion có chất lượng robotics |
| Nó kiểm tra shape, config, padding, dataloader và optimizer | Nó thay thế BONES-SEED hoặc motion capture thật |
| Nó giúp bạn port dataset riêng vì biết interface cần gì | Nó đủ để train policy deploy lên G1 |
README trỏ tới BONES-SEED dataset cho dữ liệu thật. Trang dự án MotionBricks nói training corpus của họ có quy mô hơn 350.000 motion skills hoặc clip tùy cách mô tả trong paper/project page. Với lab nhỏ, cách thực dụng là dùng synthetic để kiểm pipeline, sau đó thay class dataset bằng loader motion thật đã retarget về skeleton G1.
ZMQ Protocol v4: token_state đi thẳng vào decoder
Tài liệu ZMQ của GR00T Whole-Body Control mô tả Protocol v4 là "Token-Only Streaming". Nó stream token đã tính sẵn vào policy và bypass encoder hoàn toàn. Required field duy nhất là:
| Field | Shape | Dtype | Ý nghĩa |
|---|---|---|---|
token_state |
[D] |
f32 hoặc f64 |
Mảng motion token, dimension phải khớp encoder.dimension trong obs config |
Các field optional gồm frame_index, left_hand_joints, right_hand_joints và body_quat_w. frame_index chỉ dùng cho logging. Hand joints có thể áp trực tiếp cho Dex3 hands. body_quat_w có thể cập nhật heading reference trong lúc token streaming.
Điểm kỹ thuật quan trọng: v4 chỉ có tác dụng khi policy có encoder configuration và observation token_state. Nếu model không có encoder (encode_mode == -2 trong tài liệu), token có thể được nhận nhưng không ảnh hưởng pipeline inference. Nói cách khác, không phải mọi policy SONIC đều tự nhiên nhận MotionBricks token. Bạn cần obs config khớp.
Một message tối thiểu có thể được hình dung như sau:
payload = {
"protocol_version": 4,
"token_state": token.astype("float32"), # shape [64] nếu encoder.dimension = 64
"frame_index": np.array([frame_id], dtype=np.int32),
}
Nếu đang làm loco-manipulation với Dex3:
payload = {
"protocol_version": 4,
"token_state": motion_token.astype("float32"),
"frame_index": np.array([frame_id], dtype=np.int32),
"left_hand_joints": left_dex3.astype("float32"),
"right_hand_joints": right_dex3.astype("float32"),
"body_quat_w": heading_quat.astype("float32"),
}
Trong bài triển khai SONIC qua ZMQ, ta đã xem ZMQ như kênh stream motion. Bài này thêm một bước: thay vì stream joint/SMPL để robot-side encoder mã hóa, ta stream latent action đã chuẩn bị. Đây là cách sạch nhất để MotionBricks hoặc VLA trở thành planner token phía ngoài.
Adapter MotionBricks → SONIC nên trông như thế nào?
Vì preview release chưa phải "model fully embedded" vào robotics formulation, đừng giả định có sẵn một file motionbricks_to_sonic.py hoàn chỉnh. Cách thiết kế adapter nên bắt đầu từ contract, không bắt đầu từ model:
MotionBricks primitive
-> pose/root generator
-> token_state [D]
-> ZMQ v4 publisher
-> SONIC decoder policy observation
-> action.wbc / robot control
Checklist adapter:
| Bước | Kiểm tra |
|---|---|
| Dimension | token_state.shape[0] phải bằng encoder.dimension, thường là 64 trong ví dụ tài liệu |
| Rate | Publisher không cần chạy 15.000 Hz; chỉ cần feed đủ mới cho control loop và bật conflate nếu cần drop frame cũ |
| Coordinate frame | Root/heading của MotionBricks phải cùng convention với SONIC deployment |
| History | Decoder vẫn dùng his_* observations từ state robot, nên vài frame đầu cần warm-up ổn định |
| Safety | Token lạ có thể tạo motion vượt giới hạn; luôn test trong sim trước |
| Fallback | Nếu v4 mất token hoặc mismatch dimension, chuyển về reference motion hoặc idle |
Pseudo-code mức hệ thống:
while running:
command = read_user_or_vla_command()
primitive = motionbricks.plan(command)
pose_token = pose_model.sample(primitive, context)
root_state = root_model.predict(primitive, context)
token_state = adapter.pack_token(pose_token, root_state)
assert token_state.shape == (encoder_dim,)
zmq_pub.send({
"protocol_version": 4,
"token_state": token_state,
"frame_index": frame_id,
"body_quat_w": root_state.heading_quat,
})
Trong code thật, adapter.pack_token() là phần khó nhất. Nó phải biết token nào decoder policy được train để hiểu. Nếu SONIC decoder được train với encoder riêng của SONIC, còn MotionBricks token đến từ VQVAE khác distribution, bạn cần alignment hoặc retraining. Nếu không, policy có thể nhận vector đúng shape nhưng sai nghĩa.
Vì sao 15.000 FPS không phải là tần số điều khiển robot?
MotionBricks claim 15.000 FPS và 2 ms latency rất ấn tượng, nhưng beginner cần đọc đúng. Trong animation hoặc motion generation, FPS thường là throughput sinh frame motion, có thể batch hoặc chạy backbone trên GPU. Robot thật bị giới hạn bởi actuator loop, state estimation, network latency, safety filter và policy inference. SONIC deployment thường quan tâm vòng điều khiển thực tế ổn định hơn là sinh token càng nhanh càng tốt.
Với humanoid, cách dùng đúng là:
- MotionBricks sinh hoặc refresh motion token nhanh hơn control loop.
- ZMQ publisher giữ token mới nhất, có thể bật conflate để bỏ message cũ.
- SONIC decoder chạy ở rate deployment đã test.
- Safety layer kiểm joint limit, torque, contact và fall detection.
Nếu MotionBricks sinh 100 token trong lúc robot chỉ tiêu thụ 1 token, 99 token kia không tự làm robot tốt hơn. Chất lượng nằm ở token đúng semantics, đúng coordinate frame, đúng horizon và khớp distribution huấn luyện của decoder.
Lộ trình thử nghiệm cho nhóm mới
Một lộ trình an toàn gồm bốn vòng:
| Vòng | Mục tiêu | Không làm |
|---|---|---|
| 1. Demo local | Chạy interactive_demo_g1.py, hiểu control keys và style |
Chưa nối robot thật |
| 2. Training loop | Chạy train_vqvae.py, train_pose.py, train_root.py với synthetic |
Chưa đánh giá motion quality |
| 3. ZMQ dry run | Gửi vector token_state dummy đúng dimension vào policy sim |
Chưa dùng token ngẫu nhiên trên hardware |
| 4. Adapter thật | Chuyển primitive thành token đã align, test sim-to-sim rồi sim-to-real | Không bỏ qua fall/safety check |
Log tối thiểu nên có:
frame_index,token_norm,token_dim,body_quat_w,decoder_action_norm,base_lin_vel,base_ang_vel,fall_flag
1001,3.82,64,"1,0,0,0",0.44,"0.20,0.01,0.00","0,0,0.03",false
1002,3.79,64,"0.999,0,0,0.04",0.46,"0.22,0.01,0.00","0,0,0.04",false
Nếu token norm spike hoặc action norm tăng mạnh ngay sau khi đổi primitive, hãy dừng ở sim. Lỗi thường nằm ở normalization, frame convention hoặc token distribution.
Kết luận
MotionBricks làm bài toán whole-body control dễ mở rộng hơn vì nó đưa chuyển động vào không gian primitive/token thay vì ép mọi thứ thành velocity command hoặc animation graph thủ công. Trong repo hiện tại, beginner có thể học pipeline qua interactive_demo_g1.py, ba script train và SyntheticMotionDataset. VQVAE đóng vai trò tokenizer, pose model sinh chuyển động toàn thân trong không gian token, root model giữ phần quỹ đạo gốc liên tục.
Đường nối với SONIC rõ nhất là ZMQ Protocol v4: token_state đi thẳng vào observation của decoder policy. Nhưng "đúng shape" chưa đủ. Token phải cùng semantics với policy đã train, root/heading phải cùng frame, và safety phải được kiểm trong sim. Khi làm đúng, MotionBricks có thể trở thành lớp smart primitives phía trên SONIC: VLA hoặc người dùng chọn ý định, MotionBricks sinh latent motion, SONIC biến latent đó thành điều khiển toàn thân ổn định.



