paper_rl__qc
Q-Chunking (QC) applies action chunking to temporal-difference RL methods, enabling agents to operate in a temporally extended action space with unbiased n-step backups. This improves exploration and online sample efficiency in offline-to-online settings, particularly on long-horizon, sparse-reward manipulation tasks.
Paper Info
Method Overview
- Action chunking: The actor outputs
horizon_length actions concatenated into a single vector. The critic evaluates Q(s, a_0:H) where a_0:H is the full chunk. During execution, the entire chunk is committed open-loop.
- N-step returns: With chunk length H, the Bellman target uses the cumulative discounted reward over H steps and bootstraps from the state H steps later, with discount raised to the power H:
r_0 + gamma*r_1 + ... + gamma^(H-1)*r_{H-1} + gamma^H * Q(s_H, a_H).
- Offline-to-online: Offline pre-training uses
main.py (ACFQL agent). Online fine-tuning continues with main.py (same script, runs offline then online phases) or uses main_online.py (ACRLPD agent, pure online with replay buffer mixing).
- Two agent variants: ACFQL (flow Q-learning with flow-matching actor) and ACRLPD (SAC-based with TanhNormal actor).
Key insight: Action chunking applied to TD-based RL provides both structured exploration (by committing to multi-step action sequences from offline data) and correct value learning (via unbiased n-step returns), bridging the gap between behavior cloning's temporal coherence and RL's optimality.
Paper-Code Mapping
| Paper Concept |
Code Location |
Notes |
| QC (ACFQL agent) |
agents/acfql.py:ACFQLAgent |
Flow Q-learning with action chunking; action_chunking=True reshapes actions to (H*action_dim,) |
| QC-RLPD (ACRLPD agent) |
agents/acrlpd.py:ACRLPDAgent |
SAC-based agent with action chunking and BC regularization |
| Action chunk critic |
agents/acfql.py:ACFQLAgent.critic_loss |
Q(s, concat(a_0..a_H)) with discount^H bootstrap |
| Flow-matching actor |
agents/acfql.py:ACFQLAgent.actor_loss |
BC flow loss via conditional flow matching (CFM) + optional distill-ddpg Q loss |
| Flow action sampling (Euler) |
agents/acfql.py:ACFQLAgent.compute_flow_actions |
Euler integration over flow_steps steps from Gaussian noise |
| Best-of-N actor |
agents/acfql.py:ACFQLAgent.sample_actions |
actor_type="best-of-n": sample N flow actions, pick highest Q |
| Distill-DDPG actor |
agents/acfql.py:ACFQLAgent.sample_actions |
actor_type="distill-ddpg": one-step flow distillation + DDPG Q-gradient |
| SAC actor loss |
agents/acrlpd.py:ACRLPDAgent.actor_loss |
Entropy-regularized + optional BC loss (bc_alpha) |
| Sequence sampling |
utils/datasets.py:Dataset.sample_sequence |
Samples contiguous H-step windows; computes cumulative rewards, masks, validity |
| Chunked evaluation |
evaluation.py:evaluate |
Action queue: commit full chunk, pop one at a time |
| Offline-to-online training |
main.py:main |
Offline phase -> replay buffer init -> online phase |
| Pure online training |
main_online.py:main |
Online-only with 50/50 dataset/replay buffer mixing |
| Critic ensemble (ACFQL) |
utils/networks.py:Value |
ensemblize wrapper with num_ensembles (default 2) |
| Critic ensemble (ACRLPD) |
agents/model.py:Ensemble + rlpd_networks/state_action_value.py:StateActionValue |
nn.vmap-based ensemble with num_qs (default 10) |
| Actor vector field |
utils/networks.py:ActorVectorField |
Flow-matching velocity network; inputs: (obs, action, time) |
| Visual encoder |
utils/encoders.py:ImpalaEncoder |
IMPALA ResNet stack; variants: impala, impala_small, impala_large |
| Sparse reward transform |
main.py:process_train_dataset |
(reward != 0) * -1.0; controlled by --sparse=True |
Setup
Dependencies
- Python 3.10+
- JAX 0.6.0+ (with CUDA 12 support)
- Flax 0.10.5+
- Key packages:
jax, flax, optax, ml_collections, distrax, tensorflow-probability, wandb, ogbench, mujoco
- For RoboMimic tasks:
robomimic, h5py
- For D4RL tasks:
d4rl
Installation
git clone https://github.com/ColinQiyangLi/qc.git
cd qc
pip install -r requirements.txt
Datasets
OGBench (auto-downloaded):
- Standard datasets download automatically via
ogbench.make_env_and_datasets()
- Large datasets (e.g.,
cube-quadruple-play-100m-v0): manually download and use --ogbench_dataset_dir=<path>
# Download cube-quadruple 100M dataset
wget -r -np -nH --cut-dirs=2 -A "*.npz" https://rail.eecs.berkeley.edu/datasets/ogbench/cube-quadruple-play-100m-v0/
RoboMimic (manual download):
Usage Scenarios
Offline-to-Online RL (QC with best-of-N actor)
MUJOCO_GL=egl python main.py \
--agent=agents/acfql.py \
--agent.actor_type=best-of-n \
--agent.actor_num_samples=32 \
--env_name=cube-triple-play-singletask-task2-v0 \
--horizon_length=5 \
--offline_steps=1000000 \
--online_steps=1000000 \
--seed=0
Offline-to-Online RL (QC-FQL with distill-DDPG actor)
MUJOCO_GL=egl python main.py \
--agent=agents/acfql.py \
--agent.actor_type=distill-ddpg \
--agent.alpha=100 \
--env_name=cube-triple-play-singletask-task2-v0 \
--horizon_length=5 \
--seed=0
Pure Online RL (QC-RLPD with BC regularization)
MUJOCO_GL=egl python main_online.py \
--agent=agents/acrlpd.py \
--env_name=cube-triple-play-singletask-task2-v0 \
--horizon_length=5 \
--agent.bc_alpha=0.01 \
--seed=0
Sparse Reward Tasks
MUJOCO_GL=egl python main.py \
--agent.actor_type=best-of-n \
--agent.actor_num_samples=32 \
--env_name=scene-play-singletask-task0-v0 \
--sparse=True \
--horizon_length=5 \
--seed=0
Baselines (no action chunking)
# FQL (horizon_length=1, no chunking, no n-step)
MUJOCO_GL=egl python main.py --agent.alpha=100 --env_name=cube-triple-play-singletask-task2-v0 --horizon_length=1
# BFN-n (n-step returns without chunking, actor sees single action)
MUJOCO_GL=egl python main.py --agent.actor_type=best-of-n --agent.actor_num_samples=4 \
--env_name=cube-triple-play-singletask-task2-v0 --horizon_length=5 --agent.action_chunking=False
# RLPD (no chunking, standard SAC online)
MUJOCO_GL=egl python main_online.py --env_name=cube-triple-play-singletask-task2-v0 --horizon_length=1
Key Config Flags
| Flag |
Default |
Description |
--horizon_length |
5 |
Action chunk length H; controls both chunk size and n-step return horizon |
--agent |
agents/acfql.py (main.py) / agents/acrlpd.py (main_online.py) |
Agent config file |
--env_name |
cube-triple-play-singletask-task2-v0 |
Environment name (OGBench, RoboMimic, or D4RL) |
--offline_steps |
1000000 |
Number of offline training steps (main.py only) |
--online_steps |
1000000 |
Number of online training steps |
--sparse |
False |
Convert rewards to sparse: (r != 0) * -1.0 |
--discount |
0.99 |
Discount factor gamma |
--utd_ratio |
1 |
Update-to-data ratio for online training |
--eval_episodes |
50 |
Number of evaluation episodes |
--eval_interval |
100000 |
Steps between evaluations |
--buffer_size |
2000000 (main.py) / 1000000 (main_online.py) |
Replay buffer size |
--start_training |
5000 |
Online step to begin gradient updates |
--dataset_proportion |
1.0 |
Fraction of offline dataset to use |
--ogbench_dataset_dir |
None |
Path for large OGBench datasets (e.g., 100M) |
--dataset_replace_interval |
1000 |
Steps between cycling dataset shards (for large datasets) |
--agent.action_chunking |
True |
Enable action chunking (False = n-step returns only) |
--agent.actor_type |
distill-ddpg |
Actor type for ACFQL: distill-ddpg or best-of-n |
--agent.actor_num_samples |
32 |
Number of flow samples for best-of-N actor |
--agent.alpha |
100.0 (ACFQL) |
BC distillation coefficient for distill-ddpg actor |
--agent.bc_alpha |
0.0 (ACRLPD) |
BC regularization strength for SAC actor |
--agent.num_qs |
2 (ACFQL) / 10 (ACRLPD) |
Critic ensemble size |
--agent.flow_steps |
10 |
Euler integration steps for flow matching |
--agent.q_agg |
mean |
Target Q aggregation: min or mean |
--agent.batch_size |
256 |
Batch size |
--agent.lr |
3e-4 |
Learning rate (Adam/AdamW) |
--agent.tau |
0.005 |
Target network soft update rate |
--agent.layer_norm |
True |
Layer normalization in critic |
--agent.encoder |
None |
Visual encoder: impala, impala_small, impala_large, or None (state) |
--agent.weight_decay |
0.0 (ACFQL) |
AdamW weight decay (0 = plain Adam) |
--agent.use_fourier_features |
False |
Fourier time embedding in flow actor |
--agent.init_temp |
1.0 (ACRLPD) |
Initial entropy temperature |
--agent.target_entropy_multiplier |
0.5 (ACRLPD) |
Multiplied by -action_dim for auto target entropy |
Code Integration Guide
Minimal Imports
import sys
sys.path.append("/path/to/qc")
from agents.acfql import ACFQLAgent, get_config as get_acfql_config
from agents.acrlpd import ACRLPDAgent, get_config as get_acrlpd_config
from utils.datasets import Dataset, ReplayBuffer
from evaluation import evaluate
Agent Instantiation
import jax.numpy as jnp
# Get default config and customize
config = get_acfql_config()
config["horizon_length"] = 5
config["actor_type"] = "best-of-n"
config["actor_num_samples"] = 32
# Create agent from example data
# ex_observations: shape (obs_dim,) e.g. (23,) for RoboMimic lift
# ex_actions: shape (action_dim,) e.g. (7,)
agent = ACFQLAgent.create(
seed=0,
ex_observations=ex_observations,
ex_actions=ex_actions,
config=config,
)
Training Loop (single step)
import jax
# Offline update (single batch)
batch = dataset.sample_sequence(
batch_size=256,
sequence_length=5, # horizon_length
discount=0.99,
)
agent, info = agent.update(batch)
# Online update (batched UTD)
utd_ratio = 4
batch = replay_buffer.sample_sequence(
batch_size=256 * utd_ratio,
sequence_length=5,
discount=0.99,
)
batch = jax.tree.map(lambda x: x.reshape((utd_ratio, 256) + x.shape[1:]), batch)
agent, info = agent.batch_update(batch)
Action Sampling and Execution
import jax
import numpy as np
rng = jax.random.PRNGKey(0)
action = agent.sample_actions(observations=obs, rng=rng)
# action shape: (horizon_length * action_dim,) when action_chunking=True
# Reshape and execute sequentially:
action_chunk = np.array(action).reshape(-1, action_dim)
for a in action_chunk:
next_obs, reward, terminated, truncated, info = env.step(a)
Data Format
| Field |
Shape / Type |
Description |
observations |
(B, obs_dim) float32 |
Current state observation (first in sequence) |
actions |
(B, H, action_dim) float32 |
H-step action sequence |
rewards |
(B, H) float32 |
Cumulative discounted rewards: rewards[:,i] = sum(gamma^j * r_j, j=0..i) |
masks |
(B, H) float32 |
Running minimum of continuation masks (0 if any terminal seen) |
valid |
(B, H) float32 |
1.0 for valid steps (before any terminal), 0.0 after |
next_observations |
(B, H, obs_dim) float32 |
Observations at each step in the sequence |
full_observations |
(B, H, obs_dim) float32 |
Full obs sequence (state) or (B, h, w, H, c) (visual) |
terminals |
(B, H) float32 |
Running max of terminal flags |
Integration Notes
- The codebase uses JAX/Flax, not PyTorch. Agents are Flax PyTree nodes (immutable).
- Agent updates return a new agent:
agent, info = agent.update(batch). You must reassign.
ModuleDict in utils/flax_utils.py bundles all networks (actor, critic, target_critic) into a single TrainState with a shared optimizer.
TrainState.select(name) returns a partial call that routes to a specific sub-module.
- The
batch_update method uses jax.lax.scan over the UTD dimension for efficient multi-update.
- Config is passed as
ml_collections.ConfigDict via absl flags. Agent config files double as the config source (get_config() at module bottom).
MUJOCO_GL=egl is required for headless GPU rendering (cluster environments).
- ACFQL uses
utils/networks.py (MLP with GELU, ensemblize). ACRLPD uses rlpd_networks/ and agents/model.py (MLP with ReLU, nn.vmap Ensemble). They are not interchangeable.
- Save/restore via pickle:
save_agent(agent, dir, step) writes params_<step>.pkl, restore_agent_with_file(agent, path) loads it.
Core Architecture
ACFQL (Flow Q-Learning) ACRLPD (SAC-based)
~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~
Observation ----+---> ActorVectorField (BC flow) Observation ---> TanhNormal Actor
| | |
| [Euler integration, flow_steps=10] sample actions
| | |
| flow actions (H*action_dim) actions (H*action_dim)
| | |
| +----+---- (distill-ddpg) +-----+
| | ActorVectorField (one-step) |
| | | |
| +---- OR ---- (best-of-n) |
| | sample N, pick max Q |
| | |
+---> Value (critic ensemble, num_qs=2) Ensemble(StateActionValue, num_qs=10)
| | |
| Q(s, a_0:H) Q(s, a_0:H)
| | |
+---> Value (target critic, EMA tau=0.005) target critic (EMA tau=0.005)
| |
target_q = sum(gamma^i * r_i) target_q = sum(gamma^i * r_i)
+ gamma^H * mask * Q_target + gamma^H * mask * Q_target
|
+ alpha * entropy
+ bc_alpha * BC_loss
Dataset.sample_sequence(batch_size, sequence_length=H, discount=gamma)
-> {observations, actions[B,H,A], rewards[B,H], masks[B,H], valid[B,H], next_observations[B,H,...]}
Repo Structure
| Path |
Purpose |
main.py |
Offline-to-online training (ACFQL default). Runs offline phase then online phase with replay buffer seeded from offline data. |
main_online.py |
Pure online training (ACRLPD default). Mixes 50/50 offline dataset + replay buffer batches. |
agents/__init__.py |
Agent registry: {"acfql": ACFQLAgent, "acrlpd": ACRLPDAgent} |
agents/acfql.py |
ACFQLAgent: flow Q-learning + action chunking. Also serves as config via get_config(). |
agents/acrlpd.py |
ACRLPDAgent: SAC + action chunking + optional BC. Also serves as config via get_config(). |
agents/model.py |
Shared network defs for ACRLPD: MLP, Ensemble, Normal/TanhNormal, TD3Actor, MLPResNet, MLPResNetBlock. |
utils/networks.py |
Core network defs for ACFQL: MLP (GELU), Value, Actor, ActorVectorField, FourierFeatures, ensemblize. |
utils/datasets.py |
Dataset (frozen, offline) and ReplayBuffer classes. Key method: sample_sequence(). |
utils/encoders.py |
ImpalaEncoder (visual encoder) with variants: impala, impala_small, impala_large, impala_debug. |
utils/flax_utils.py |
ModuleDict, TrainState, save_agent(), restore_agent(), restore_agent_with_file(). |
evaluation.py |
evaluate(): runs episodes with action queue (commit full chunk, pop FIFO). |
log_utils.py |
CsvLogger, WandB setup (setup_wandb), get_exp_name, video utilities. |
envs/env_utils.py |
make_env_and_datasets(): dispatcher for OGBench, D4RL, RoboMimic. EpisodeMonitor, FrameStackWrapper. |
envs/robomimic_utils.py |
RobomimicLowdimWrapper, make_env(), get_dataset(). Loads HDF5 from ~/.robomimic/. |
envs/ogbench_utils.py |
make_ogbench_env_and_datasets(), load_dataset(). Loads NPZ files, supports custom dataset dirs. |
envs/d4rl_utils.py |
D4RL environment and dataset loading (AntMaze, Adroit). |
rlpd_networks/ |
RLPD network modules (MLP, Ensemble, StateActionValue, PixelMultiplexer) used by ACRLPD. |
rlpd_networks/encoders/ |
D4PGEncoder for pixel observations (used by ACRLPD). |
rlpd_distributions/ |
TanhNormal, TanhDeterministic, TanhTransformed distributions for ACRLPD. |
Tips & Gotchas
action_chunking=True vs False: When True, the actor outputs H*action_dim actions and the critic takes the concatenated chunk as input. When False, the actor outputs a single action but still uses n-step returns (the "n-step return baseline" in the paper).
horizon_length=1: Reduces to standard single-step RL (no chunking, no n-step return).
- Two different network stacks: ACFQL uses
utils/networks.py (MLP with GELU activation, ensemblize via nn.vmap). ACRLPD uses rlpd_networks/ and agents/model.py (MLP with ReLU, separate Ensemble class). They are not interchangeable.
- Reward adjustment: RoboMimic rewards are shifted by -1.0 (from [0,1] to [-1,0]). D4RL AntMaze rewards are also shifted by -1.0. Sparse mode further transforms to
(r != 0) * -1.0.
- Evaluation action queue: During eval, a new action chunk is only sampled when the queue is empty. Actions are popped FIFO. The queue is cleared on episode reset.
- WandB required by default: Both scripts initialize WandB at startup. Set
WANDB_MODE=disabled to run without logging.
- Memory for large datasets: Use
--ogbench_dataset_dir with --dataset_replace_interval=1000 to cycle through dataset shards (avoids loading entire 100M dataset into memory).
- EGL rendering: Set
MUJOCO_GL=egl on headless machines. The code also auto-sets EGL_DEVICE_ID and MUJOCO_EGL_DEVICE_ID from CUDA_VISIBLE_DEVICES.
- JAX JIT compilation: First training step will be slow due to JIT compilation. Subsequent steps are fast.
- Valid mask in sequence sampling: The
valid field marks whether each position in the H-step window is before any terminal. The critic loss is weighted by valid[..., -1] to ignore invalid target bootstraps.
main.py vs main_online.py: main.py has both offline and online phases (replay buffer initialized from offline data). main_online.py is online-only with an empty replay buffer, mixing 50/50 dataset batches + replay batches at each step.
batch_update vs update: update processes a single batch. batch_update takes a batch with an extra leading dimension (UTD ratio) and scans over it with jax.lax.scan, returning averaged info.
- RoboMimic env naming: Use
<task>-<dataset_type>-low_dim format, e.g., lift-mh-low_dim. The is_robomimic_env() check requires low_dim in the name. Max episode lengths: lift/can=300, square=400, transport=800, tool_hang=1000.
- Checkpoint format: Saved as
params_<step>.pkl via pickle with flax.serialization.to_state_dict().
- Online action queue reset: When an episode terminates during online training, the action queue is cleared and a new chunk is sampled at the next step.
1---2name: paper-rl-qc3description: Q-Chunking -- action chunking for TD-based offline-to-online RL on long-horizon sparse-reward tasks4---56# paper_rl__qc78Q-Chunking (QC) applies action chunking to temporal-difference RL methods, enabling agents to operate in a temporally extended action space with unbiased n-step backups. This improves exploration and online sample efficiency in offline-to-online settings, particularly on long-horizon, sparse-reward manipulation tasks.910## Paper Info1112| Field | Value |13|-------|-------|14| Title | Reinforcement Learning with Action Chunking |15| Authors | Qiyang Li, Zhiyuan Zhou, Sergey Levine |16| Year | 2025 |17| Venue | NeurIPS 2025 |18| Paper | https://arxiv.org/abs/2507.07969 |19| Code | https://github.com/ColinQiyangLi/qc |20| Website | https://colinqiyangli.github.io/qc/ |2122## Method Overview23241. **Action chunking**: The actor outputs `horizon_length` actions concatenated into a single vector. The critic evaluates `Q(s, a_0:H)` where `a_0:H` is the full chunk. During execution, the entire chunk is committed open-loop.252. **N-step returns**: With chunk length H, the Bellman target uses the cumulative discounted reward over H steps and bootstraps from the state H steps later, with discount raised to the power H: `r_0 + gamma*r_1 + ... + gamma^(H-1)*r_{H-1} + gamma^H * Q(s_H, a_H)`.263. **Offline-to-online**: Offline pre-training uses `main.py` (ACFQL agent). Online fine-tuning continues with `main.py` (same script, runs offline then online phases) or uses `main_online.py` (ACRLPD agent, pure online with replay buffer mixing).274. **Two agent variants**: ACFQL (flow Q-learning with flow-matching actor) and ACRLPD (SAC-based with TanhNormal actor).2829Key insight: Action chunking applied to TD-based RL provides both structured exploration (by committing to multi-step action sequences from offline data) and correct value learning (via unbiased n-step returns), bridging the gap between behavior cloning's temporal coherence and RL's optimality.3031## Paper-Code Mapping3233| Paper Concept | Code Location | Notes |34|---------------|---------------|-------|35| QC (ACFQL agent) | `agents/acfql.py:ACFQLAgent` | Flow Q-learning with action chunking; `action_chunking=True` reshapes actions to `(H*action_dim,)` |36| QC-RLPD (ACRLPD agent) | `agents/acrlpd.py:ACRLPDAgent` | SAC-based agent with action chunking and BC regularization |37| Action chunk critic | `agents/acfql.py:ACFQLAgent.critic_loss` | `Q(s, concat(a_0..a_H))` with `discount^H` bootstrap |38| Flow-matching actor | `agents/acfql.py:ACFQLAgent.actor_loss` | BC flow loss via conditional flow matching (CFM) + optional distill-ddpg Q loss |39| Flow action sampling (Euler) | `agents/acfql.py:ACFQLAgent.compute_flow_actions` | Euler integration over `flow_steps` steps from Gaussian noise |40| Best-of-N actor | `agents/acfql.py:ACFQLAgent.sample_actions` | `actor_type="best-of-n"`: sample N flow actions, pick highest Q |41| Distill-DDPG actor | `agents/acfql.py:ACFQLAgent.sample_actions` | `actor_type="distill-ddpg"`: one-step flow distillation + DDPG Q-gradient |42| SAC actor loss | `agents/acrlpd.py:ACRLPDAgent.actor_loss` | Entropy-regularized + optional BC loss (`bc_alpha`) |43| Sequence sampling | `utils/datasets.py:Dataset.sample_sequence` | Samples contiguous H-step windows; computes cumulative rewards, masks, validity |44| Chunked evaluation | `evaluation.py:evaluate` | Action queue: commit full chunk, pop one at a time |45| Offline-to-online training | `main.py:main` | Offline phase -> replay buffer init -> online phase |46| Pure online training | `main_online.py:main` | Online-only with 50/50 dataset/replay buffer mixing |47| Critic ensemble (ACFQL) | `utils/networks.py:Value` | `ensemblize` wrapper with `num_ensembles` (default 2) |48| Critic ensemble (ACRLPD) | `agents/model.py:Ensemble` + `rlpd_networks/state_action_value.py:StateActionValue` | `nn.vmap`-based ensemble with `num_qs` (default 10) |49| Actor vector field | `utils/networks.py:ActorVectorField` | Flow-matching velocity network; inputs: (obs, action, time) |50| Visual encoder | `utils/encoders.py:ImpalaEncoder` | IMPALA ResNet stack; variants: `impala`, `impala_small`, `impala_large` |51| Sparse reward transform | `main.py:process_train_dataset` | `(reward != 0) * -1.0`; controlled by `--sparse=True` |5253## Setup5455### Dependencies5657- Python 3.10+58- JAX 0.6.0+ (with CUDA 12 support)59- Flax 0.10.5+60- Key packages: `jax`, `flax`, `optax`, `ml_collections`, `distrax`, `tensorflow-probability`, `wandb`, `ogbench`, `mujoco`61- For RoboMimic tasks: `robomimic`, `h5py`62- For D4RL tasks: `d4rl`6364### Installation6566```bash67git clone https://github.com/ColinQiyangLi/qc.git68cd qc69pip install -r requirements.txt70```7172### Datasets7374**OGBench** (auto-downloaded):75- Standard datasets download automatically via `ogbench.make_env_and_datasets()`76- Large datasets (e.g., `cube-quadruple-play-100m-v0`): manually download and use `--ogbench_dataset_dir=<path>`7778```bash79# Download cube-quadruple 100M dataset80wget -r -np -nH --cut-dirs=2 -A "*.npz" https://rail.eecs.berkeley.edu/datasets/ogbench/cube-quadruple-play-100m-v0/81```8283**RoboMimic** (manual download):84- Place datasets at `~/.robomimic/<task>/<type>/low_dim_v15.hdf5`85- Download from https://robomimic.github.io/docs/datasets/robomimic_v0.1.html (Multi-Human MH links)86- Supported env name format: `<task>-<type>-low_dim` (e.g., `lift-mh-low_dim`, `can-mh-low_dim`, `square-mh-low_dim`)8788## Usage Scenarios8990### Offline-to-Online RL (QC with best-of-N actor)9192```bash93MUJOCO_GL=egl python main.py \94 --agent=agents/acfql.py \95 --agent.actor_type=best-of-n \96 --agent.actor_num_samples=32 \97 --env_name=cube-triple-play-singletask-task2-v0 \98 --horizon_length=5 \99 --offline_steps=1000000 \100 --online_steps=1000000 \101 --seed=0102```103104### Offline-to-Online RL (QC-FQL with distill-DDPG actor)105106```bash107MUJOCO_GL=egl python main.py \108 --agent=agents/acfql.py \109 --agent.actor_type=distill-ddpg \110 --agent.alpha=100 \111 --env_name=cube-triple-play-singletask-task2-v0 \112 --horizon_length=5 \113 --seed=0114```115116### Pure Online RL (QC-RLPD with BC regularization)117118```bash119MUJOCO_GL=egl python main_online.py \120 --agent=agents/acrlpd.py \121 --env_name=cube-triple-play-singletask-task2-v0 \122 --horizon_length=5 \123 --agent.bc_alpha=0.01 \124 --seed=0125```126127### Sparse Reward Tasks128129```bash130MUJOCO_GL=egl python main.py \131 --agent.actor_type=best-of-n \132 --agent.actor_num_samples=32 \133 --env_name=scene-play-singletask-task0-v0 \134 --sparse=True \135 --horizon_length=5 \136 --seed=0137```138139### Baselines (no action chunking)140141```bash142# FQL (horizon_length=1, no chunking, no n-step)143MUJOCO_GL=egl python main.py --agent.alpha=100 --env_name=cube-triple-play-singletask-task2-v0 --horizon_length=1144145# BFN-n (n-step returns without chunking, actor sees single action)146MUJOCO_GL=egl python main.py --agent.actor_type=best-of-n --agent.actor_num_samples=4 \147 --env_name=cube-triple-play-singletask-task2-v0 --horizon_length=5 --agent.action_chunking=False148149# RLPD (no chunking, standard SAC online)150MUJOCO_GL=egl python main_online.py --env_name=cube-triple-play-singletask-task2-v0 --horizon_length=1151```152153### Key Config Flags154155| Flag | Default | Description |156|------|---------|-------------|157| `--horizon_length` | `5` | Action chunk length H; controls both chunk size and n-step return horizon |158| `--agent` | `agents/acfql.py` (main.py) / `agents/acrlpd.py` (main_online.py) | Agent config file |159| `--env_name` | `cube-triple-play-singletask-task2-v0` | Environment name (OGBench, RoboMimic, or D4RL) |160| `--offline_steps` | `1000000` | Number of offline training steps (main.py only) |161| `--online_steps` | `1000000` | Number of online training steps |162| `--sparse` | `False` | Convert rewards to sparse: `(r != 0) * -1.0` |163| `--discount` | `0.99` | Discount factor gamma |164| `--utd_ratio` | `1` | Update-to-data ratio for online training |165| `--eval_episodes` | `50` | Number of evaluation episodes |166| `--eval_interval` | `100000` | Steps between evaluations |167| `--buffer_size` | `2000000` (main.py) / `1000000` (main_online.py) | Replay buffer size |168| `--start_training` | `5000` | Online step to begin gradient updates |169| `--dataset_proportion` | `1.0` | Fraction of offline dataset to use |170| `--ogbench_dataset_dir` | `None` | Path for large OGBench datasets (e.g., 100M) |171| `--dataset_replace_interval` | `1000` | Steps between cycling dataset shards (for large datasets) |172| `--agent.action_chunking` | `True` | Enable action chunking (False = n-step returns only) |173| `--agent.actor_type` | `distill-ddpg` | Actor type for ACFQL: `distill-ddpg` or `best-of-n` |174| `--agent.actor_num_samples` | `32` | Number of flow samples for best-of-N actor |175| `--agent.alpha` | `100.0` (ACFQL) | BC distillation coefficient for distill-ddpg actor |176| `--agent.bc_alpha` | `0.0` (ACRLPD) | BC regularization strength for SAC actor |177| `--agent.num_qs` | `2` (ACFQL) / `10` (ACRLPD) | Critic ensemble size |178| `--agent.flow_steps` | `10` | Euler integration steps for flow matching |179| `--agent.q_agg` | `mean` | Target Q aggregation: `min` or `mean` |180| `--agent.batch_size` | `256` | Batch size |181| `--agent.lr` | `3e-4` | Learning rate (Adam/AdamW) |182| `--agent.tau` | `0.005` | Target network soft update rate |183| `--agent.layer_norm` | `True` | Layer normalization in critic |184| `--agent.encoder` | `None` | Visual encoder: `impala`, `impala_small`, `impala_large`, or `None` (state) |185| `--agent.weight_decay` | `0.0` (ACFQL) | AdamW weight decay (0 = plain Adam) |186| `--agent.use_fourier_features` | `False` | Fourier time embedding in flow actor |187| `--agent.init_temp` | `1.0` (ACRLPD) | Initial entropy temperature |188| `--agent.target_entropy_multiplier` | `0.5` (ACRLPD) | Multiplied by -action_dim for auto target entropy |189190## Code Integration Guide191192### Minimal Imports193194```python195import sys196sys.path.append("/path/to/qc")197198from agents.acfql import ACFQLAgent, get_config as get_acfql_config199from agents.acrlpd import ACRLPDAgent, get_config as get_acrlpd_config200from utils.datasets import Dataset, ReplayBuffer201from evaluation import evaluate202```203204### Agent Instantiation205206```python207import jax.numpy as jnp208209# Get default config and customize210config = get_acfql_config()211config["horizon_length"] = 5212config["actor_type"] = "best-of-n"213config["actor_num_samples"] = 32214215# Create agent from example data216# ex_observations: shape (obs_dim,) e.g. (23,) for RoboMimic lift217# ex_actions: shape (action_dim,) e.g. (7,)218agent = ACFQLAgent.create(219 seed=0,220 ex_observations=ex_observations,221 ex_actions=ex_actions,222 config=config,223)224```225226### Training Loop (single step)227228```python229import jax230231# Offline update (single batch)232batch = dataset.sample_sequence(233 batch_size=256,234 sequence_length=5, # horizon_length235 discount=0.99,236)237agent, info = agent.update(batch)238239# Online update (batched UTD)240utd_ratio = 4241batch = replay_buffer.sample_sequence(242 batch_size=256 * utd_ratio,243 sequence_length=5,244 discount=0.99,245)246batch = jax.tree.map(lambda x: x.reshape((utd_ratio, 256) + x.shape[1:]), batch)247agent, info = agent.batch_update(batch)248```249250### Action Sampling and Execution251252```python253import jax254import numpy as np255256rng = jax.random.PRNGKey(0)257action = agent.sample_actions(observations=obs, rng=rng)258# action shape: (horizon_length * action_dim,) when action_chunking=True259# Reshape and execute sequentially:260action_chunk = np.array(action).reshape(-1, action_dim)261for a in action_chunk:262 next_obs, reward, terminated, truncated, info = env.step(a)263```264265### Data Format266267| Field | Shape / Type | Description |268|-------|-------------|-------------|269| `observations` | `(B, obs_dim)` float32 | Current state observation (first in sequence) |270| `actions` | `(B, H, action_dim)` float32 | H-step action sequence |271| `rewards` | `(B, H)` float32 | Cumulative discounted rewards: `rewards[:,i] = sum(gamma^j * r_j, j=0..i)` |272| `masks` | `(B, H)` float32 | Running minimum of continuation masks (0 if any terminal seen) |273| `valid` | `(B, H)` float32 | 1.0 for valid steps (before any terminal), 0.0 after |274| `next_observations` | `(B, H, obs_dim)` float32 | Observations at each step in the sequence |275| `full_observations` | `(B, H, obs_dim)` float32 | Full obs sequence (state) or `(B, h, w, H, c)` (visual) |276| `terminals` | `(B, H)` float32 | Running max of terminal flags |277278### Integration Notes279280- The codebase uses **JAX/Flax**, not PyTorch. Agents are Flax PyTree nodes (immutable).281- Agent updates return a **new** agent: `agent, info = agent.update(batch)`. You must reassign.282- `ModuleDict` in `utils/flax_utils.py` bundles all networks (actor, critic, target_critic) into a single `TrainState` with a shared optimizer.283- `TrainState.select(name)` returns a partial call that routes to a specific sub-module.284- The `batch_update` method uses `jax.lax.scan` over the UTD dimension for efficient multi-update.285- Config is passed as `ml_collections.ConfigDict` via absl flags. Agent config files double as the config source (`get_config()` at module bottom).286- `MUJOCO_GL=egl` is required for headless GPU rendering (cluster environments).287- ACFQL uses `utils/networks.py` (MLP with GELU, `ensemblize`). ACRLPD uses `rlpd_networks/` and `agents/model.py` (MLP with ReLU, `nn.vmap` Ensemble). They are **not interchangeable**.288- Save/restore via pickle: `save_agent(agent, dir, step)` writes `params_<step>.pkl`, `restore_agent_with_file(agent, path)` loads it.289290## Core Architecture291292```293 ACFQL (Flow Q-Learning) ACRLPD (SAC-based)294 ~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~295296Observation ----+---> ActorVectorField (BC flow) Observation ---> TanhNormal Actor297 | | |298 | [Euler integration, flow_steps=10] sample actions299 | | |300 | flow actions (H*action_dim) actions (H*action_dim)301 | | |302 | +----+---- (distill-ddpg) +-----+303 | | ActorVectorField (one-step) |304 | | | |305 | +---- OR ---- (best-of-n) |306 | | sample N, pick max Q |307 | | |308 +---> Value (critic ensemble, num_qs=2) Ensemble(StateActionValue, num_qs=10)309 | | |310 | Q(s, a_0:H) Q(s, a_0:H)311 | | |312 +---> Value (target critic, EMA tau=0.005) target critic (EMA tau=0.005)313 | |314 target_q = sum(gamma^i * r_i) target_q = sum(gamma^i * r_i)315 + gamma^H * mask * Q_target + gamma^H * mask * Q_target316 |317 + alpha * entropy318 + bc_alpha * BC_loss319320Dataset.sample_sequence(batch_size, sequence_length=H, discount=gamma)321 -> {observations, actions[B,H,A], rewards[B,H], masks[B,H], valid[B,H], next_observations[B,H,...]}322```323324## Repo Structure325326| Path | Purpose |327|------|---------|328| `main.py` | Offline-to-online training (ACFQL default). Runs offline phase then online phase with replay buffer seeded from offline data. |329| `main_online.py` | Pure online training (ACRLPD default). Mixes 50/50 offline dataset + replay buffer batches. |330| `agents/__init__.py` | Agent registry: `{"acfql": ACFQLAgent, "acrlpd": ACRLPDAgent}` |331| `agents/acfql.py` | `ACFQLAgent`: flow Q-learning + action chunking. Also serves as config via `get_config()`. |332| `agents/acrlpd.py` | `ACRLPDAgent`: SAC + action chunking + optional BC. Also serves as config via `get_config()`. |333| `agents/model.py` | Shared network defs for ACRLPD: `MLP`, `Ensemble`, `Normal`/`TanhNormal`, `TD3Actor`, `MLPResNet`, `MLPResNetBlock`. |334| `utils/networks.py` | Core network defs for ACFQL: `MLP` (GELU), `Value`, `Actor`, `ActorVectorField`, `FourierFeatures`, `ensemblize`. |335| `utils/datasets.py` | `Dataset` (frozen, offline) and `ReplayBuffer` classes. Key method: `sample_sequence()`. |336| `utils/encoders.py` | `ImpalaEncoder` (visual encoder) with variants: `impala`, `impala_small`, `impala_large`, `impala_debug`. |337| `utils/flax_utils.py` | `ModuleDict`, `TrainState`, `save_agent()`, `restore_agent()`, `restore_agent_with_file()`. |338| `evaluation.py` | `evaluate()`: runs episodes with action queue (commit full chunk, pop FIFO). |339| `log_utils.py` | `CsvLogger`, WandB setup (`setup_wandb`), `get_exp_name`, video utilities. |340| `envs/env_utils.py` | `make_env_and_datasets()`: dispatcher for OGBench, D4RL, RoboMimic. `EpisodeMonitor`, `FrameStackWrapper`. |341| `envs/robomimic_utils.py` | `RobomimicLowdimWrapper`, `make_env()`, `get_dataset()`. Loads HDF5 from `~/.robomimic/`. |342| `envs/ogbench_utils.py` | `make_ogbench_env_and_datasets()`, `load_dataset()`. Loads NPZ files, supports custom dataset dirs. |343| `envs/d4rl_utils.py` | D4RL environment and dataset loading (AntMaze, Adroit). |344| `rlpd_networks/` | RLPD network modules (`MLP`, `Ensemble`, `StateActionValue`, `PixelMultiplexer`) used by ACRLPD. |345| `rlpd_networks/encoders/` | `D4PGEncoder` for pixel observations (used by ACRLPD). |346| `rlpd_distributions/` | `TanhNormal`, `TanhDeterministic`, `TanhTransformed` distributions for ACRLPD. |347348## Tips & Gotchas349350- **`action_chunking=True` vs `False`**: When True, the actor outputs `H*action_dim` actions and the critic takes the concatenated chunk as input. When False, the actor outputs a single action but still uses n-step returns (the "n-step return baseline" in the paper).351- **`horizon_length=1`**: Reduces to standard single-step RL (no chunking, no n-step return).352- **Two different network stacks**: ACFQL uses `utils/networks.py` (MLP with GELU activation, `ensemblize` via `nn.vmap`). ACRLPD uses `rlpd_networks/` and `agents/model.py` (MLP with ReLU, separate `Ensemble` class). They are not interchangeable.353- **Reward adjustment**: RoboMimic rewards are shifted by -1.0 (from [0,1] to [-1,0]). D4RL AntMaze rewards are also shifted by -1.0. Sparse mode further transforms to `(r != 0) * -1.0`.354- **Evaluation action queue**: During eval, a new action chunk is only sampled when the queue is empty. Actions are popped FIFO. The queue is cleared on episode reset.355- **WandB required by default**: Both scripts initialize WandB at startup. Set `WANDB_MODE=disabled` to run without logging.356- **Memory for large datasets**: Use `--ogbench_dataset_dir` with `--dataset_replace_interval=1000` to cycle through dataset shards (avoids loading entire 100M dataset into memory).357- **EGL rendering**: Set `MUJOCO_GL=egl` on headless machines. The code also auto-sets `EGL_DEVICE_ID` and `MUJOCO_EGL_DEVICE_ID` from `CUDA_VISIBLE_DEVICES`.358- **JAX JIT compilation**: First training step will be slow due to JIT compilation. Subsequent steps are fast.359- **Valid mask in sequence sampling**: The `valid` field marks whether each position in the H-step window is before any terminal. The critic loss is weighted by `valid[..., -1]` to ignore invalid target bootstraps.360- **`main.py` vs `main_online.py`**: `main.py` has both offline and online phases (replay buffer initialized from offline data). `main_online.py` is online-only with an empty replay buffer, mixing 50/50 dataset batches + replay batches at each step.361- **`batch_update` vs `update`**: `update` processes a single batch. `batch_update` takes a batch with an extra leading dimension (UTD ratio) and scans over it with `jax.lax.scan`, returning averaged info.362- **RoboMimic env naming**: Use `<task>-<dataset_type>-low_dim` format, e.g., `lift-mh-low_dim`. The `is_robomimic_env()` check requires `low_dim` in the name. Max episode lengths: lift/can=300, square=400, transport=800, tool_hang=1000.363- **Checkpoint format**: Saved as `params_<step>.pkl` via `pickle` with `flax.serialization.to_state_dict()`.364- **Online action queue reset**: When an episode terminates during online training, the action queue is cleared and a new chunk is sampled at the next step.