Guided Action Flow, viết tắt là GAF, là một hướng rất thực dụng cho robot manipulation: thay vì fine-tune lại toàn bộ Vision-Language-Action policy, ta giữ SmolVLA đóng băng và thêm một action-chunk critic nhỏ ở inference time. Critic này không thay policy, không chọn lại action theo kiểu reranking, và cũng không backprop qua toàn bộ VLA. Nó chỉ cung cấp gradient theo action chunk để điều chỉnh đường đi của flow sampler, giúp action cuối cùng nghiêng về vùng có xác suất thành công cao hơn.
Bài này dựa trên paper Guided Action Flow: Q-Guided Inference for Flow-Matching Vision-Language-Action Policies trên arXiv 2607.02092 và repo chính thức ylhaichen/guided-action-flow. Mục tiêu là giải thích đủ chậm để beginner có thể follow: ý tưởng paper, kiến trúc, cách cài đặt, cách collect rollout, train critic, bật Q-guided inference, và đọc kết quả trên LIBERO. Nếu bạn mới làm quen với VLA, nên đọc trước VLA Models trong robotics và LeRobot hands-on guide trong series AI robotics.
Vì sao cần GAF?
Các VLA hiện đại như OpenVLA, pi0, GR00T hoặc SmolVLA thường được huấn luyện bằng imitation learning trên demonstration. Cách này rất mạnh khi dataset gần với task deployment, nhưng khi policy thất bại ở một số trạng thái, việc cải thiện policy thường đòi hỏi thêm demonstration, fine-tuning, reinforcement learning hoặc data pipeline phức tạp. Với model lớn, fine-tune có thể tốn GPU, rủi ro overfit và làm thay đổi behavior đã được kiểm chứng.
GAF đặt câu hỏi hẹp hơn: nếu policy gốc đã có kỹ năng tương đối tốt, liệu một critic nhỏ có thể kéo action sampler đi đúng hơn trong lúc inference không? Đây là câu hỏi hấp dẫn vì SmolVLA là flow-matching policy. Action không được sinh ra một lần duy nhất; nó được tạo dần qua một quá trình iterative transport từ noise về action chunk sạch. Chính quá trình nhiều bước này tạo điểm bám cho guidance.
Về trực giác, hãy tưởng tượng SmolVLA đang tạo một action chunk 50 bước cho task LIBERO. Ở mỗi denoising step, sampler đang giữ một action trung gian x_t và velocity v_t. Nếu ta có một critic biết chấm điểm action chunk nào dễ thành công hơn, ta có thể tính gradient của critic theo action ước lượng sạch a_hat, rồi chỉnh velocity một chút. Nếu critic đúng, action cuối có thể tốt hơn. Nếu critic sai hoặc quá tự tin ngoài distribution, guidance có thể làm hỏng action. Vì vậy paper nhấn mạnh ensemble critic, gradient clipping và uncertainty gate.
Nguồn gốc và tài nguyên chính thức
| Tài nguyên | Link |
|---|---|
| Paper GAF | arXiv:2607.02092 |
| Repo GAF | github.com/ylhaichen/guided-action-flow |
| SmolVLA model card | HuggingFaceVLA/smolvla_libero |
| LeRobot | github.com/huggingface/lerobot |
| LIBERO benchmark | github.com/Lifelong-Robot-Learning/LIBERO |
Paper ghi rõ GAF là một nghiên cứu empirical giai đoạn đầu, không phải tuyên bố rằng Q-guided inference đã giải quyết xong adaptation cho VLA. Kết quả mạnh nhất là: critic gradient có thể cải thiện frozen SmolVLA trong rollout LIBERO thật, nhưng generalization của critic vẫn là bottleneck. Repo cũng mô tả codebase như "project glue": không vendor LeRobot, SmolVLA hay LIBERO vào package chính; các repo upstream nằm ở third_party/, checkpoint ở checkpoints/, rollout ở runs/.

Bức tranh kiến trúc
GAF có ba phần chính.
| Thành phần | Vai trò |
|---|---|
| Frozen SmolVLA | Nhận ảnh, proprioception và language instruction, rồi sinh action chunk bằng flow sampler |
| Action-chunk critic | Dự đoán giá trị scalar Q(obs_features, action_chunk, task_feature) |
| QGF hook | Tính gradient theo action sạch ước lượng và chỉnh velocity trong sampler |
Điểm quan trọng là policy không bị update. SmolVLA vẫn chạy như bình thường: observation đi qua vision-language pathway, action expert tạo velocity cho flow sampler, sampler tích phân từ noise về action. GAF chỉ cài một hook ở bên trong denoising loop. Hook nhận x_t, v_t, timestep t, observation features và task feature; nó ước lượng action sạch, hỏi critic, rồi trả về velocity đã được chỉnh.
Repo chia code theo ranh giới khá rõ:
src/guided_action_flow/
benchmarks/ adapter cho LIBERO, LIBERO-plus, LIBERO-PRO
critics/ action-chunk critic, ensemble, checkpoint loading
guidance/ QGF update rule
policies/ SmolVLA/LeRobot wrapper và QGF hook
rewards/ reward/success extraction
training/ rollout dataset, returns, task features
evaluation/ rollout và metrics
Nếu bạn từng dùng diffusion guidance trong image generation, GAF có mùi vị tương tự: một model nền sinh mẫu, còn một tín hiệu ngoài điều chỉnh đường sinh mẫu. Nhưng robotics khó hơn vì action chunk phải ổn định động học, đúng scale, đúng gripper command và an toàn trong closed-loop simulator.
Flow convention của SmolVLA
Một phần kỹ thuật dễ sai nhất là dấu của guidance. Theo docs trong repo, implementation SmolVLA được pin dùng reverse-time convention:
x_t = t * noise + (1 - t) * action
v_t = noise - action
Inference bắt đầu từ noise ở t = 1 và đi về action ở t = 0:
dt = -1 / num_steps
x_t = x_t + dt * v_t
Vì vậy action sạch ước lượng tại timestep trung gian là:
a_hat = x_t - t * v_t
Nếu bạn copy công thức Q-guided flow từ một paper dùng chiều thời gian ngược lại, bạn có thể chỉnh velocity sai dấu. Với SmolVLA pinned trong repo GAF, muốn tăng action sạch theo gradient +g, velocity phải bị giảm:
g = grad_a Q(obs_features, proprio, a_hat)
v_guided = v_t - g / beta
Trong bản đầy đủ, repo dùng thêm ensemble, clipping và gate:
q_mean = mean(Q_k(obs_features, a_hat, task_feature))
g = grad_a q_mean
g = clip_by_norm(g, c)
gate = max(m_min, exp(-alpha * std(Q_k)))
v_guided = v_t - gate * g / beta
beta càng nhỏ thì guidance càng mạnh. grad_clip_norm chặn gradient quá lớn. std(Q_k) đo disagreement giữa các critic trong ensemble; khi các critic không đồng ý, gate giảm lực guidance.
Critic học gì?
Critic của GAF không cần dự đoán action. Nó dự đoán một scalar value cho một action chunk. Input cơ bản gồm observation/state feature đã được policy preprocess, proprioception nếu có, action chunk 7D của LIBERO, và optional task feature. Target đơn giản là sparse success-to-go từ rollout thật của frozen SmolVLA.
Ví dụ một episode dài 80 bước. Nếu success lần đầu xuất hiện ở bước 60, các chunk bắt đầu trước đó có target giảm theo discount gamma, còn các chunk sau success được đặt gần 1. Nếu episode không thành công, target là 0. Cách target này rẻ, dễ debug, nhưng không hoàn hảo: nó không phân biệt tinh tế giữa "suýt thành công" và "rất tệ" nếu cả hai đều không đạt success cuối cùng.
Repo nhấn mạnh split train/validation phải ở episode level, không random theo chunk. Nếu random chunk-level split, các chunk gần nhau trong cùng trajectory sẽ rơi vào cả train và val, làm val loss đẹp giả tạo. Với beginner, đây là bài học quan trọng: trong robot learning, temporal correlation có thể làm evaluation sai lệch rất dễ.
Task-conditioned critic mạnh nhất trong paper dùng hidden-state từ language pathway của frozen SmolVLA. Cụ thể, task description đi qua VLM text path của SmolVLA, rồi mean-pooling hidden states trên token không phải padding để tạo task feature. Cách này tốt hơn task id vì task id của LIBERO không portable giữa task family. Nó cũng tránh phải train thêm một text encoder mới.

Cài đặt môi trường
Linux được khuyến nghị vì LIBERO phụ thuộc MuJoCo và robosuite. Repo ghi nhận các run hiện tại được tạo trên laptop RTX 4070 8GB VRAM; official SmolVLA 0.45B LIBERO eval và QGF eval với K=3 critics đều fit trong 8GB VRAM. Đây là điểm khiến GAF đáng thử với lab nhỏ: bạn không cần cluster lớn chỉ để tái hiện ablation đầu tiên.
Bắt đầu bằng môi trường Python:
conda create -n gaf-libero python=3.12 -y
conda activate gaf-libero
python -m pip install --upgrade pip setuptools wheel
python -m pip install -e ".[dev,torch]"
Bootstrap upstream repo:
bash scripts/bootstrap_third_party.sh
git clone https://github.com/sylvestf/LIBERO-plus.git third_party/LIBERO-plus
git clone https://github.com/Zxy-MLlab/LIBERO-PRO.git third_party/LIBERO-PRO
Sau đó cài LeRobot từ checkout đã pin:
cd third_party/lerobot
python -m pip install -e ".[smolvla,libero]"
cd ../..
Thiết lập MuJoCo headless:
export MUJOCO_GL=egl
export WANDB_MODE=disabled
export PYTHONPATH="$PWD/src:$PWD/third_party/lerobot/src${PYTHONPATH:+:$PYTHONPATH}"
Tạo local LIBERO config:
mkdir -p .libero_configs/vanilla .libero_configs/plus .libero_configs/pro
export LIBERO_CONFIG_PATH="$PWD/.libero_configs/vanilla"
Các file config .libero_configs/* chứa absolute paths tới assets, BDDL files, benchmark root, datasets và init states. Đây là machine-specific config, không nên commit. Nếu simulator fail trước khi policy được gọi, kiểm tra LIBERO_CONFIG_PATH, MuJoCo EGL, asset paths và version robosuite trước.
Tải checkpoint và chạy baseline
GAF dùng official SmolVLA checkpoint cho LIBERO:
python scripts/download_models.py \
--repo-id lerobot/smolvla_libero \
--local-dir checkpoints/smolvla_libero
Chạy smoke một episode:
PYTHON_BIN="$CONDA_PREFIX/bin/python" \
POLICY_PATH="$PWD/checkpoints/smolvla_libero" \
OUTPUT_DIR="$PWD/runs/smolvla_libero_smoke" \
TASK=libero_spatial \
TASK_IDS='[0]' \
N_EPISODES=1 \
bash scripts/eval_smolvla_libero.sh
Chạy baseline bằng evaluator chung:
python scripts/eval_policy.py \
--policy-path checkpoints/smolvla_libero \
--output-dir runs/baseline_spatial3_ep50_seed3000 \
--env-type libero \
--task libero_spatial \
--task-ids '[3]' \
--n-episodes 50 \
--seed 3000 \
--device cuda \
--max-videos 0
Baseline rất quan trọng. Nếu baseline của bạn thấp hơn nhiều so với repo report, đừng vội tune QGF. Hãy kiểm tra checkpoint path, action normalization, camera keys, task id, seed, max episode length, version LeRobot và LIBERO. GAF là lớp guidance nhỏ; nó không sửa được pipeline sai.
Collect rollout để train critic
Critic phải học từ rollout thật của frozen SmolVLA, không phải dummy action. Với single-task trial:
python scripts/collect_rollouts.py \
--policy-path checkpoints/smolvla_libero \
--output-dir runs/qgf_single_task_spatial3_train50 \
--env-type libero \
--task libero_spatial \
--task-ids '[3]' \
--n-episodes 50 \
--seed 2000 \
--device cuda
Output trong runs/ chứa episode trajectories, action sequences, success flags và metadata. Từ đó dataset builder tạo overlapping action chunks. Với horizon 50, một episode 80 bước có thể tạo nhiều chunk bắt đầu tại các bước khác nhau. Observation feature lấy tại đầu chunk, action input là chuỗi action kế tiếp, target là success-to-go.
Để beginner debug, hãy kiểm tra các điểm này trước khi train:
| Kiểm tra | Vì sao quan trọng |
|---|---|
| Episode có cả success và failure | Critic cần tín hiệu phân biệt, toàn success hoặc toàn failure đều yếu |
| Action shape là 7D cho LIBERO | SmolVLA có padding nội bộ tới max action dim lớn hơn |
| Success flag đúng | Reward wrapper sai sẽ làm target critic sai |
| Split theo episode | Tránh leakage giữa train và validation |
Train single-task critic
Lệnh train critic cơ bản:
python scripts/train_critic.py \
--data-dir runs/qgf_single_task_spatial3_train50 \
--output-dir runs/qgf_single_task_spatial3_critic_train50 \
--action-horizon 50 \
--hidden-dim 512 \
--depth 3 \
--epochs 20 \
--seed 0 \
--device cuda
Paper và README cũng report biến thể mạnh hơn với critic ensemble K=3, hidden dimension 768, depth 4, 30 epochs và task-description feature từ VLM hidden state. Tuy nhiên, nếu bạn mới chạy lần đầu, single-task critic đơn giản là bước tốt nhất. Nó giúp xác minh toàn bộ data path: rollout đúng, target đúng, action chunk đúng, autograd qua action hoạt động.
Một lỗi hay gặp là để outer evaluation chạy trong torch.inference_mode(), rồi bên trong QGF lại cần autograd.grad. Repo xử lý bằng cách dùng torch.inference_mode(False) và torch.enable_grad() quanh critic gradient path. Denoiser output được detach trước khi hỏi critic, nên gradient không đi ngược vào SmolVLA.
Bật Q-guided inference
Sau khi có critic:
python scripts/eval_policy.py \
--policy-path checkpoints/smolvla_libero \
--output-dir runs/qgf_single_task_spatial3_eval50_qgf_beta2_seed3000 \
--env-type libero \
--task libero_spatial \
--task-ids '[3]' \
--n-episodes 50 \
--seed 3000 \
--device cuda \
--max-videos 0 \
--critic-path runs/qgf_single_task_spatial3_critic_train50/critic.pt \
--qgf-beta 2 \
--qgf-grad-clip-norm 1.0
Với ensemble và adaptive gate:
python scripts/eval_policy.py \
--policy-path checkpoints/smolvla_libero \
--output-dir runs/qgf_vlm_hidden_val_spatial_5_7_8_beta2_gate20_seed8000 \
--env-type libero \
--task libero_spatial \
--task-ids '[5, 7, 8]' \
--n-episodes 10 \
--seed 8000 \
--device cuda \
--max-videos 0 \
--critic-paths \
runs/qgf_multitask_spatial0to4_critic_vlm_hidden_seed0/critic.pt \
runs/qgf_multitask_spatial0to4_critic_vlm_hidden_seed1/critic.pt \
runs/qgf_multitask_spatial0to4_critic_vlm_hidden_seed2/critic.pt \
--qgf-beta 2 \
--qgf-grad-clip-norm 1.0 \
--qgf-uncertainty-scale 20 \
--qgf-min-gate 0.1
Đọc log nên chú ý không chỉ success rate mà cả q_guidance_norm_mean, số denoise steps được guided, gains/regressions theo episode và disagreement giữa critics. Một setting có success ngang baseline nhưng đổi danh sách episode thành công/thất bại vẫn hữu ích: nó chứng minh guidance thật sự tác động, chỉ là critic chưa ổn định.

Kết quả trong paper
Các con số chính của GAF:
| Setting | Baseline | QGF/GAF | Gain |
|---|---|---|---|
| Single-task, seed window 3000 | 34/50 = 68.0% | 41/50 = 82.0% | +14.0 pp |
| Single-task, seed window 4000 | 41/50 = 82.0% | 43/50 = 86.0% | +4.0 pp |
| Multi-family validation | 46.0% | 56.0% | +10.0 pp |
| Locked held-out test | 65.0% | 67.5% | +2.5 pp |
Baseline anchors trong repo cũng đáng nhớ:
| Setting | Success |
|---|---|
| LIBERO vanilla, 100 episodes | 65/100 = 65.0% |
| LIBERO-plus spatial subset, 50 episodes | 39/50 = 78.0% |
| LIBERO-PRO zero-shot với vanilla checkpoint, 100 episodes | 1/100 = 1.0% |
Cách đọc đúng là: single-task có tín hiệu mạnh, validation multi-family có tiến bộ rõ, nhưng held-out gain còn nhỏ. Đây không phải kết quả "cứ thêm critic là thắng". Paper cho thấy critic generalization là vấn đề trung tâm. Một critic spatial-only có thể không transfer tốt sang object family; một critic quá mạnh tay có thể tạo regressions; một gate quá yếu có thể không đủ bảo vệ khi model đi ra ngoài distribution training.
Khi nào nên dùng GAF?
GAF phù hợp khi bạn đã có frozen flow-matching policy tương đối tốt, có simulator hoặc robot rollout để thu thập success/failure, và muốn cải thiện một task family hẹp mà chưa muốn fine-tune toàn bộ VLA. Nó đặc biệt hấp dẫn cho lab dùng SmolVLA/LeRobot vì chi phí thấp hơn nhiều so với RL fine-tuning policy lớn.
Không nên dùng GAF như lớp vá cho policy chưa học được task. Nếu baseline gần 0%, critic sẽ thiếu rollout thành công để học. Nếu observation pipeline sai, critic chỉ học nhiễu. Nếu deployment task rất khác LIBERO, critic cần OOD gate tốt hơn và dữ liệu deployment thật.
Checklist tối thiểu:
[ ] Baseline SmolVLA chạy ổn trên task mục tiêu
[ ] Rollout dataset có cả success và failure
[ ] Critic input chỉ dùng feature có thể có ở inference
[ ] Train/val split theo episode
[ ] QGF sign đã verify theo sampler thực tế
[ ] Beta, clip norm và gate được chọn trên validation
[ ] Held-out test chỉ mở sau khi freeze hyperparameters
Liên hệ với các hướng VLA khác
GAF nằm giữa imitation learning và reinforcement learning. Nó không cần thêm expert demonstration như supervised fine-tuning. Nó cũng không update policy bằng gradient RL. Critic học từ rollout, rồi chỉ can thiệp khi action đang được sample. So với fine-tune VLA trên LIBERO bằng Embodied-R1.5, điểm khác biệt là Embodied-R1.5 thay đổi checkpoint policy, còn GAF giữ checkpoint SmolVLA đóng băng. So với SLIM-0.5B trên LIBERO, GAF lại khác ở chỗ nó không đề xuất backbone mới mà thêm một guidance layer quanh sampler có sẵn.
Hướng này cũng gần với các phương pháp value-guided generation trong diffusion/flow model. Tuy nhiên robot action không phải ảnh. Action bị ràng buộc bởi robot kinematics, gripper state, control frequency và closed-loop execution. Vì vậy paper rất thận trọng: critic gradients có thể hữu ích, nhưng phải được clip, gate và đánh giá bằng rollout thật.
Kết luận
Guided Action Flow đáng chú ý vì nó đưa ra một workflow gọn: giữ SmolVLA frozen, collect rollout trên LIBERO, train action-chunk critic, rồi dùng critic gradient để guide reverse-time flow sampler. Ý tưởng cốt lõi rất rõ: thay vì bắt policy học lại, ta dùng value signal để chỉnh quá trình sinh action ngay tại inference.
Điểm mạnh là chi phí thấp, modular và dễ rollback: tắt critic là quay về SmolVLA baseline. Điểm yếu là critic generalization. Kết quả single-task từ 68.0% lên 82.0% rất đáng quan tâm, nhưng held-out test chỉ từ 65.0% lên 67.5%, nên người triển khai phải giữ thái độ thực nghiệm: validate kỹ, dùng ensemble gate, không tune trên test, và không kỳ vọng critic nhỏ sẽ tự giải quyết mọi distribution shift.
Với beginner, bài học lớn nhất không chỉ là một công thức v_guided = v_t - g / beta. Bài học là cách xây một adaptation layer có ranh giới rõ: policy frozen, critic nhỏ, data từ rollout thật, target đơn giản, evaluation tách validation/test. Đây là một mẫu thiết kế đáng học cho robot learning năm 2026.



