Skip to content

Feature/refactor appo - #37

Merged
TATP-233 merged 33 commits into
mainfrom
feature/refactor-appo
Mar 15, 2026
Merged

TATP-233 merged 33 commits into
mainfrom
feature/refactor-appo

Conversation

@TATP-233

Copy link
Copy Markdown
Collaborator

No description provided.

TATP-233 and others added 30 commits March 15, 2026 04:30
Implements async PPO using adapter pattern to reuse rsl-rl PPO:
- OnPolicyReplayBuffer: device-adaptive storage (CUDA/MPS/CPU)
- AsyncPPOLearner: adapts buffer data to RolloutStorage
- async_ppo_collector_fn: collects rollouts using PPO.act()
- AsyncPPORunner: orchestrates async training

Total: 420 lines (5 files)
100% rsl-rl code reuse via thin adapter layer

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add async_ppo_config() to locomotion_params.py
- Create scripts/train_async_ppo.py training entry
- Enhance AsyncPPORunner with full logging and checkpointing
- Support TensorBoard integration via OffPolicyLogger

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Use pinned memory for faster H2D transfer (CUDA)
- Add dedicated CUDA stream for async data transfer
- Reduce data copies in worker (zero-copy when possible)
- Improve buffer sync efficiency with non_blocking=True

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Create HardwareMonitor utility for CPU/GPU/memory tracking
- Integrate monitoring into AsyncPPORunner
- Log hardware metrics to TensorBoard every 10 iterations
- Support NVML for detailed GPU utilization

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix distribution_cfg with correct class name and parameters
- Add multi_gpu config field for rsl_rl v4
- Add config conversion in worker process
- Fix SharedWeightSync attribute names (name, not shm_name)
- Add num_actions attribute to env for PPO construction

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Support None lock in subprocess for lock-free read/write
- Fix worker to properly initialize combined state dict

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add actor/critic prefixes to weight keys to prevent overwriting
- Manually initialize distribution_params in learner by calling actor forward
- Clone distribution params to avoid view-of-view gradient issues
- Remove stochastic_output parameter from PPO.act() call
- Add lock-free weight sync support in worker subprocess
…r pipeline

- Add initialization wait for collector before starting training
- Reset buffer count after learner consumes rollout to avoid reuse
- Add debug logging for collector and buffer operations
- Increase first iteration timeout to 300s for collector initialization
- Fix distribution_params initialization by manually calling actor forward
- Successfully tested: collector collects data, learner consumes and trains
- Calculate env steps from rollout dimensions in learner
- Accumulate total env steps in runner
- Update logger with env steps via log_collector method
- Successfully displays total env steps in training UI
- Add weight_sync_lock parameter to collector function
- Pass lock from runner to worker via _start_collector
- Use lock when creating SharedWeightSync in subprocess
- Fixes potential deadlock in weight synchronization
- Create metrics_queue and pass to collector
- Add _drain_metrics method to process collector metrics
- Separate wait_time from collect_time in logging
- Send periodic metrics from collector worker

Note: Collector timing metrics remain 0 due to async architecture,
but key metrics (env steps, train time, buffer) work correctly
- Add detailed timing collection in worker (mlp_infer_ms, env_step_total_ms, step_core_ms, update_state_ms, reset_done_ms)
- Fix collect_time calculation in runner to match off-policy standard
- Optimize learner by passing distribution params from collector, avoiding redundant actor forward pass
- Update buffer to store and transfer action_mean and action_sigma
- Reduces learner computation and improves GPU utilization

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The transition.action_mean/action_sigma attributes are not set in collector context.
Use actor.output_mean and actor.output_std properties instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…port)

rsl-rl 5.0.1's RolloutStorage doesn't have mu/sigma fields.
PPO.update() internally recomputes distribution params, causing slow training.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Major performance fix:
- Add MPS device support in _get_default_device()
- Set distribution_params in learner by running actor forward pass
- Upgrade local rsl-rl to 5.0.1 (matching PyPI version)

Performance improvement:
- Before: 10+ seconds (CUDA/CPU fallback)
- After: 2.3 seconds (MPS)
- Sync PPO baseline: 0.8-1.0 seconds

Remaining 2-3x overhead is due to:
- H2D data transfer from collector to learner
- Recomputing distribution_params (collector on CPU)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add logger_type parameter support (tensorboard/wandb/none/no_print)
- Collect and display reward components from state.info["log"]
- Pass reward_components to logger.log_step()
- Update _drain_metrics to handle reward_components

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add device_utils.py with get_default_device() and get_env_dims()
- Add SharedBufferBase for common buffer initialization
- Add BaseCollector for shared collector patterns
- Update all runners to use device_utils
- Update buffers to inherit from SharedBufferBase
- Fix async_ppo worker to send metrics every rollout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tion

- runner.py/worker.py: use is_rsl_rl_v5()+convert_config_v5 for rsl-rl 5.x
  instead of always using convert_config_v3_to_v4; add multi_gpu=None fallback
  required by PPO.construct_algorithm
- buffer.py: add dist_params_dim param; extend packed storage (MPS/CPU) and
  named tensors (CUDA) to carry distribution_params per rollout
- worker.py: save ppo.transition.distribution_params each step into
  dist_params_buf; wrap all init+loop in try/except so crashes are forwarded
  to main process via metrics_queue instead of silently hanging
- learner.py: _fill_storage reads dist_params from buffer directly (split into
  mean/std), removing the extra full-batch actor forward pass that was
  dispatched asynchronously and inflating ppo_update_ms timing
The collector process holds a stale copy of the critic weights. Using
stale st.values from the buffer mixed with a fresh last_values from
compute_returns produced inconsistent GAE estimates, inflating advantages
and causing the surrogate loss to diverge. The policy never converged
(reward stayed negative, entropy stuck at init).

Fix: after filling storage from the buffer rollout, recompute st.values
for all T*N transitions using the current learner critic before calling
compute_returns. This ensures a consistent value baseline throughout GAE.

Result: surrogate stabilizes near 0 (was diverging), entropy decreases
(15.9 vs 17 stuck), mean reward improves (+0.319 vs -0.38) over 151 iters.
… PPO worker

Previously dones = state.terminated only. When an episode ends by timeout
(truncated=True, terminated=False), the env auto-resets internally but
current_episode_lengths/rewards were never reset, causing episode lengths
to accumulate unboundedly across episode boundaries (reaching 2500+).

Also fixed: timeout_rate was never counted because the timeout check was
gated on state.terminated > 0.

Fix:
- dones_buf (for GAE) stays as state.terminated — truncated episodes
  correctly bootstrap V(s) at the boundary
- episode boundary detection uses state.done (terminated | truncated)
- timeout/terminate classification uses state.truncated[i] directly
…PO worker

rsl-rl adds γ·V(s_T) to rewards at truncated episode boundaries so GAE
sees an infinite-horizon return. async PPO was missing this: the env
auto-resets on truncation, so obs_{t+1} in the buffer is the reset obs.
With done=0 (correct for GAE), compute_returns bootstraps V(reset obs)
instead of V(s_T), systematically underestimating truncated returns.

Fix: after computing rewards, add γ·V(s_T) for any truncated env.
V(s_T) is already available as ppo.transition.values from the act() call
just before env.step(). γ is read from ppo.gamma.

Also fix episode reward tracking to use raw env reward (state.reward)
rather than the bootstrapped reward, so logged mean_reward reflects
true undiscounted episode return.
- Remove async_ppo module and train_async_ppo.py
- Restore appo module from origin/refactor/appo (V-trace, target network,
  IMPACT IS correction, clean worker-only-actor architecture)
- Add appo_config() to locomotion_params.py
- Fix rsl-rl version check in runner.py, worker.py, train_appo.py:
  is_rsl_rl_v5() now short-circuits before convert_config_v3_to_v4 so
  appo_config (already v5-native actor/critic format) is not corrupted
…tribution

- Delete APPOActorWrapper: MLPModel (stochastic=True) has identical API
  (get_output_log_prob, output_mean, output_std, output_entropy)
- Update appo_config actor to use distribution_cfg v5 format instead of
  legacy init_noise_std/noise_std_type/stochastic (fixes TypeError on v5)
- runner._build_learner / worker: deepcopy actor_cfg so MLPModel.__init__'s
  distribution_cfg.pop("class_name") doesn't mutate shared rl_cfg
- learner.update: fix stochastic_output=False → True for target_actor KL prep
  (MLPModel only populates output_mean/std on the stochastic forward path)
- Export SharedOnPolicyStorage from unilab.ipc.__init__
- Add untracked shared_onpolicy_storage.py to git

Benefits: weight format unified with rsl-rl PPO checkpoints, JIT/ONNX
export compatible, no more hand-rolled std param or torch.distributions.Normal.
…g, drain_metrics)

worker.py:
- Track ep_timeouts / ep_terminates, report timeout_rate + terminated_rate per window
- EMA timing for MLP inference (mlp_infer_ms) and env.step (env_step_total_ms)
- Send collector_timing_ms dict in every metrics message

runner.py:
- Add _drain_metrics() static method (mirrors OffPolicyRunner) — drains full queue,
  calls update_ep_length / update_done_rates / update_collector_timing / log_collector
- Split wait_time (blocking wait for shared storage) from collect_time (read latency)
- Pass wait_time to log_step() so terminal shows learner wait colored red if > 1ms
- Maintain reward_history deque (200), proper rolling mean for terminal display
- Remove dead-code deque()/time.time() assignments

learner.py:
- Rename return keys: surrogate→surrogate_loss, value_function→value_loss
  so OffPolicyLogger terminal sorts them under "Losses" section correctly

TensorBoard coverage now matches FastSAC:
  train/surrogate_loss, train/value_loss, train/entropy, train/kl
  episode/timeout_rate, episode/terminated_rate, episode/length
  timing/learner_collect_ms, timing/learner_train_ms
  timing/collector_mlp_infer_ms, timing/collector_env_step_ms
  perf/steps_per_sec, perf/iter_ms, perf/collect_train_ratio
- APPORunner: add sync_collection (default True) with collection_ready_queue /
  trainer_done_queue handshake mirroring FastSAC; collector waits for
  trainer_done before each rollout, signals collection_ready after write
- OffPolicyLogger: replace 5 hardcoded _collector_*_ms fields with a single
  _collector_timing dict; update_collector_timing does a plain dict.update();
  TB/W&B emit timing/collector_{key} for whatever the collector reports;
  terminal table renders rows dynamically in pairs — no more zero pollution
  from algo-mismatched fields; add timing/learner_wait_ms to TB and W&B
- train_appo.py: CLI numeric args default to None and fall back to appo_config
  values, so --max_iterations etc. only override when explicitly specified
- learner: simplify surrogate to standard PPO ratio (drop IMPACT IS term);
  V-trace handles off-policy correction in advantages, not the surrogate
- learner: vectorize vtrace_advantages (numpy backward scan, cat next_values/next_vs)
- learner: cache target_actor mu/sigma in process_batch, skip duplicate forward in update
- learner: remove unnecessary rewards.clone() (combined is already a fresh cat tensor)
- runner: drain all available ring-buffer slots per iteration to keep GPU busy
- runner: epoch adjustment increase-only (5% threshold); removing reduce branch
- runner: default epoch_adjust_interval 10→50, EMA α 0.05→0.02, warmup 2×interval
- worker: pre-alloc obs TensorDict; update in-place, remove redundant astype(float32)
- train_appo: add --load_run argument for play mode
Replace standard PPO ratio with min(π_current/π_target, π_current/π_b)
= clamp(π_b/π_target, max=1) * π_current/π_b.

Fresh data (π_b≈π_target): reduces to standard PPO.
Stale data: clipped_rho < 1 downweights the update without amplifying it,
matching the V-trace policy gradient (ρ_t * ∇log π * A_vtrace).
tatp-yf added 3 commits March 16, 2026 04:43
epochs 固定由 config 控制,不再运行时自动调整。
同步删除 min_epochs/max_epochs/epoch_adjust_interval 参数及 --max_epochs CLI flag。
新增 available_on_arrive 到 metrics 方便观察 ring buffer 积压情况。
… queue

train_rsl_rl.py play_only fixes:
- suppress tensorboard logger in play mode (runner["logger"] = "none")
- pass log_dir=None to OnPolicyRunner (avoids persistent play_temp dir)
- call env.close() after training before play starts

APPO:
- runner: call logger.update_replay_queue() each iteration
- offpolicy_logger: add replay queue occupancy row to timing panel

Env configs:
- go1/go2/g1 joystick: expand vel_limit from [0.5,0,0] to [-0.6/-0.4/-0.8, 1.0/0.4/0.8]
- locomotion_params: reduce default APPO max_iterations 500→150, save_interval 100→50
- locomotion_params: consolidate G1/G1WalkTask into shared config block
…xist

- get_latest_run: skip dirs whose name doesn't start with a digit
  (filters out 'appo-...', 'play_temp', etc. accidentally in rsl_rl_train/)
- play_rsl_rl: validate checkpoint has actor_state_dict before runner.load();
  print clear error instead of cryptic KeyError if format mismatch
@TATP-233
TATP-233 merged commit fb7ceb7 into main Mar 15, 2026
9 checks passed
@TATP-233
TATP-233 deleted the feature/refactor-appo branch March 15, 2026 21:49
TATP-233 added a commit that referenced this pull request May 4, 2026
TATP-233 added a commit that referenced this pull request Sep 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant