一个 RL step 里发生了什么#

学习目标

读完本页你能:

  • 按顺序说出一次 env.step(action) 内部发生的事,以及每一步由哪个类负责

  • 区分物理步长、控制周期、渲染间隔三个时间尺度,知道改哪个参数影响什么

  • 解释为什么一次 step 同时推进几千个环境,而不是逐个循环

  • 指出 Direct 环境在这张图上替换了哪些环节

前置知识

本页的时序图是全站的锚点图。之后讲仿真循环(3.1)、各个 Manager(4.9)、源码导读(9.1)时都会回到这里。本页以 Isaac Lab v2.3.2 的 Manager-based 环境 ManagerBasedRLEnv 为准。

一圈时序图#

这一节回答:策略给出一个动作,到它拿回下一帧观测,中间经过了谁?

下图按源码中 ManagerBasedRLEnv.step() 的实际执行顺序画出[1]。虚线 loop 框是 decimation 循环:一个 RL step 内执行多次物理步。

        %%{init: {"sequence": {"mirrorActors": false, "width": 96, "actorMargin": 16, "height": 40, "messageMargin": 12, "boxMargin": 4, "noteMargin": 4, "boxTextMargin": 2, "diagramMarginY": 4, "wrap": true}}}%%
sequenceDiagram
    participant P as 策略<br/>RL 库
    participant W as Wrapper
    participant E as Env.step
    participant A as Action<br/>Manager
    participant S as Scene / 资产
    participant X as Simulation<br/>Context
    participant M as 其他 Manager

    P->>W: step(actions)
    W->>E: step(actions)
    E->>A: process_action
    loop decimation 次
        E->>A: apply_action
        A->>S: 设关节目标
        E->>S: write_data_to_sim
        E->>X: sim.step(PhysX 前进 dt)
        E->>S: scene.update(dt)
    end
    E->>M: 终止 → 奖励 → _reset_idx → 指令 → 事件 → 观测
    E-->>W: obs, rew, 终止标志
    W-->>P: obs, rew, dones
    Note over A,M: Direct 环境:Action Manager 与其他 Manager 换成子类方法
    

图 1:一次 env.step() 的调用时序(Isaac Lab 2.3.2,Manager-based)。实线是调用,虚线是返回;loop 框内的步骤每个 RL step 执行 decimation 次。渲染没有画进图里,它只在满足条件时才发生,见下文"三个时间尺度"。

读这张图时,可以用 loop 框把一个 step 切成三段:

  • loop 之前:只有 process_action 一步,纯 Isaac Lab 的张量运算,不碰仿真器。

  • loop 之内:一个 step 里唯一碰到仿真器的地方。write_data_to_sim 把数据交给 PhysX,sim.step 让 Isaac Sim 驱动 PhysX 前进,scene.update 再把时间戳推进到新状态。

  • loop 之后:又回到 Isaac Lab 内部,终止、奖励、重置、指令、观测依次在 GPU 张量上计算,最后原路返回给 RL 库。

所以排查性能或行为问题时,先判断它发生在哪一段:物理不稳定看 loop 之内,奖励或观测算错看 loop 之后。

逐步说明#

这一节回答:图上每一步具体是哪个类的哪个方法,落在哪一层?

"层"一列对应 0.2 分层依赖图 的层次。行号均指向 v2.3.2 源码。

#

发生什么

层

类与方法

源码

1

RL 库把一批动作交给适配层(wrapper);wrapper 按需裁剪后调用环境

RL 库 / Isaac Lab

RslRlVecEnvWrapper.step

[2]

2

动作按 Term 切分,每个 Term 做缩放、偏移等处理,结果缓存起来

Isaac Lab

ActionManager.process_action → ActionTerm.process_actions

[1] L173;[3] L372;[4] L169

3

进入 decimation 循环;每一轮把处理好的动作写成关节目标(例如 JointPositionAction 调用 set_joint_position_target)

Isaac Lab

ActionManager.apply_action → ActionTerm.apply_actions

[1] L182、L185;[3] L395;[4] L197

4

资产经执行器模型(actuator)算出实际下发的目标或力矩,写入 PhysX 缓冲区

Isaac Lab → PhysX

InteractiveScene.write_data_to_sim → Articulation.write_data_to_sim

[1] L187;[5] L465;[6] L218、L258

5

只推进物理,不渲染;Isaac Sim 的 PhysicsContext 调用 PhysX 的 simulate 与 fetch_results

Isaac Lab → Isaac Sim → PhysX

SimulationContext.step(render=False)

[1] L189;[7] L541;[8] L713;[9] L563

6

资产数据的时间戳前进一个 dt;Articulation 还会在这里读取关节速度,按差分算出关节加速度。其余量在被读取时才从 PhysX 取回(惰性读取)

Isaac Lab

InteractiveScene.update → ArticulationData.update

[1] L197;[5] L479;[10] L97–L103、L763、L772

7

循环结束,回合计数加一,计算终止条件(分为 terminated 与 time_outs)

Isaac Lab

TerminationManager.compute

[1] L204

8

计算奖励,各项按权重乘以 step_dt 求和

Isaac Lab

RewardManager.compute(dt=step_dt)

[1] L208

9

对已结束的环境执行课程更新、场景重置、reset 事件,并清零各 Manager 的状态

Isaac Lab

ManagerBasedRLEnv._reset_idx

[1] L221、L349–L394

10

更新目标指令,执行 interval 类事件(例如周期性推一下机器人)

Isaac Lab

CommandManager.compute、EventManager.apply(mode="interval")

[1] L232、L235

11

计算观测,此时读取的是步 6 之后的最新物理状态;对刚重置的环境,则是步 9 写入的新初始状态

Isaac Lab

ObservationManager.compute(update_history=True)

[1] L238

12

返回观测、奖励、terminated、truncated、extras;rsl_rl wrapper 把后两者合并成 dones

Isaac Lab / RL 库

ManagerBasedRLEnv.step 返回值

[1] L241;[2]

注意两个顺序上的细节:

  • 观测在重置之后计算(步 9 在步 11 之前)。已结束环境返回的观测是新回合的第一帧,源码注释也说明了这一点[1]。

  • 奖励在重置之前计算(步 8 在步 9 之前)。所以结束那一步的奖励仍按旧回合的状态计算。

三个时间尺度#

这一节回答:dt、decimation、render_interval 分别管什么?

名称

定义

默认值(源码)

官方 reach 任务的取值

改它影响什么

物理步长 physics_dt

cfg.sim.dt

1/60 s[11]

1/60 s(60 Hz)[12]

仿真精度与稳定性;越小越准,但同样的仿真时长要算更多步

控制周期 step_dt

cfg.sim.dt × cfg.decimation

decimation 无默认值,必须由任务设置[13]

1/60 × 2 = 1/30 s(30 Hz)

策略多久出一次动作;决定每个 RL step 内执行几次物理步

渲染间隔

每 cfg.sim.render_interval 个物理步渲染一次

1[11]

设为等于 decimation,即每个 RL step 渲染一次

只在有 GUI 或 RTX 传感器时生效;影响画面刷新率与相机数据的新鲜度

physics_dt 和 step_dt 在环境基类里定义为两个属性[14]。回合长度按控制周期换算:reach 任务 episode_length_s = 12.0[12],即 12 ÷ (1/30) = 360 个 RL step。

渲染的条件写在循环里:物理步计数是 render_interval 的整数倍,并且 has_gui() 或 has_rtx_sensors() 为真,才调用 sim.render()[1](L179、L194–L195)。headless 训练、场景里又没有相机时,整个训练过程不渲染。

调参时的对应关系:

  • 机器人抖动、穿模:先看 sim.dt 和 PhysX 求解参数,属于物理层,见 3.3。

  • 想让策略控制更频繁:减小 decimation;训练会变慢,因为同样的仿真时长要做更多次策略推理。

  • 改了 decimation 之后,记得同步检查 render_interval 和 episode_length_s 的含义是否仍符合预期。

数据在哪里#

这一节回答:几千个环境是怎么同时推进的?

step() 的输入和所有输出都是张量,第一维是环境数:动作的形状是 (num_envs, action_dim)[1],奖励、终止标志是 (num_envs,),观测按组是 (num_envs, …)。它们默认在 GPU 上(SimulationCfg.device = "cuda:0"[11])。

所以 step() 里没有"对每个环境循环"的代码。decimation 循环是时间上的循环,每一轮都一次性推进全部环境。PhysX 的读写也是批量的:Articulation 通过 root_physx_view 一次性设置或读取所有环境的关节数据[6][10]。

为什么要这样设计,以及它和强化学习样本效率的关系,见 5.4 为什么需要几千个并行环境。

Direct 环境的差异#

这一节回答:不用 Manager 的写法,这张图哪里会变?

DirectRLEnv.step() 保留了相同的骨架:decimation 循环、write_data_to_sim、sim.step(render=False)、scene.update 都还在[15]。变化是图 1 中 ActionManager 和"其他 Manager"那两列(图末的 Note 标出了这个替换点),换成了你在子类里实现的方法:

Manager-based

Direct(用户实现)

源码

ActionManager.process_action

_pre_physics_step(actions)

[15] L363

ActionManager.apply_action

_apply_action()

[15] L373

TerminationManager.compute

_get_dones()

[15] L391

RewardManager.compute

_get_rewards()

[15] L393

ObservationManager.compute

_get_observations()

[15] L410

若配置了 events,interval 事件在 Direct 环境中仍由 EventManager 处理[15](L405–L407)。两种写法怎么选,见 4.14 Direct 环境。

常见误解#

误解一:一个 step 就是一次物理步

一个 RL step 包含 decimation 次物理步[1]。reach 任务中是 2 次[12]。日志里的"步数"通常指 RL step;要换算成仿真时间,得乘以 step_dt,而不是 sim.dt。

误解二:reset 会重新加载场景

不会。_reset_idx 只对结束的那部分环境(env_ids)调用 scene.reset,再用 reset 事件写入新的初始状态,并清零各 Manager 的缓冲[1](L349–L394)。USD 场景和其他环境都不受影响,也不会打断同一批次里还在运行的环境。

误解三:渲染每步都发生

sim.step 调用时固定传 render=False。渲染只在满足 render_interval 且有 GUI 或 RTX 传感器时才发生[1]。反过来,如果你加了相机,却把 render_interval 设得比 decimation 大,相机数据就会比控制周期旧(推断:依据源码注释,渲染间隔被假定为最短可接受的渲染间隔,需要更高频率渲染的相机会出现意外行为[1],L191–L192)。

误解四:资产的 data.* 每个物理步都会全部从 PhysX 拷一遍

大部分 data.* 不会每步拷贝:joint_pos、body 位姿等在被读取、且时间戳过期时才从 PhysX 视图取数据。例外是 Articulation 的关节速度:为了用差分算关节加速度,ArticulationData.update 每个物理步都会读一次[10]。

延伸阅读#

版本说明#

  • Isaac Lab 2.x 其他小版本:本页只对照了 v2.3.2 源码,未逐一核对其他 2.x 版本。图中省略了 recorder_manager 的调用(用于录制示教数据,默认没有 Term 时不影响流程)以及重置后按 num_rerenders_on_reset 补渲染的分支[1]。

  • Isaac Lab 3.0.0-EA:资产与传感器的 .data.* 返回 ProxyArray 而非 torch.Tensor;写入方法拆分为 _index 与 _mask 两种;物理后端可选 Newton,此时步 5 不再经过 Isaac Sim 与 PhysX[16]。详见 8.1 3.0 改了什么、为什么改。