第 4 章 物理管线与向量化仿真¶
向量化并行是 Isaac Lab 相对 CPU 物理引擎的根本差异:一次 step 同时推进成千上万个独立环境,物理状态以 (num_envs, …) 形状的张量整批存于 GPU。本章讲清这条管线的运行语义、num_envs 与步长/显存的预算关系(附一组在真实卡上跑出的数字),并交代 Newton 与 PhysX 两后端在接触、摩擦上的特性边界,以及与 MuJoCo 的差异。
1. GPU 并行物理与向量化语义¶
1.1 一次 step 推进整个 batch¶
仿真的心脏是 SimulationContext.step()——它推进一个物理步,作用于场景里克隆出的全部 num_envs 个环境。与之配套,物理量以 GPU 张量整批暴露:robot.data.joint_pos 形状为 (num_envs, num_joints),rigid_object.data.root_pos_w 为 (num_envs, 3)。写任务时不应在 Python 层对环境做 for 循环——那样会把 GPU batch 退化回串行,抹掉向量化的收益;所有 per-env 计算都应以张量运算表达。
这正是 Manager-Based 的 term 函数返回"每环境标量/向量张量"的原因(第 3 章):reward 函数返回 (num_envs,),termination 返回 (num_envs,) 布尔,框架整批 reduce,不落到 Python。
一次 step 的向量化语义,以及 NaN 的两条处置路径:
flowchart LR
step["SimulationContext.step()<br/>推进一个物理步"] --> batch[("全部 num_envs 的状态<br/>形状 (num_envs, …)")]
batch --> terms["term 函数整批计算<br/>返回 (num_envs,) 张量"]
terms --> step
nan["一个环境出现 NaN / Inf"] -.->|"沿 batch 传播,整批 loss 变 nan"| terms
nan -.->|"按环境单独 reset,不整批重启"| step
1.2 两个物理后端¶
Isaac Lab 3.0 的向量化物理有两个后端,physics 参数在运行时选:
| 后端 | 实现 | 特点 | 依赖 |
|---|---|---|---|
| Newton / MJWarp | NVIDIA Warp GPU kernel(physics=newton_mjwarp) |
MuJoCo 同源的接触模型;不装 Isaac Sim 即可 headless | 仅 isaaclab_newton |
| PhysX | ovphysx(physics=isaacsim_physx) |
TGS 软约束求解、GPU 大规模并行、与渲染/传感器一体 | 需 isaacsim extra |
属性覆写类按后端分套——PhysxRigidBodyPropertiesCfg、NewtonRigidBodyPropertiesCfg、MujocoRigidBodyPropertiesCfg 各一套字段。同一份建模意图在不同后端下要挂对应的属性类,混用会静默失效或报错。这是"配一次场景"背后的一条隐含约束。
同一份建模意图,属性类必须按后端分套:
flowchart LR
cfg["场景配置:同一份建模意图"] --> nt["Newton 后端<br/>NewtonRigidBodyPropertiesCfg"]
cfg --> px["PhysX 后端<br/>PhysxRigidBodyPropertiesCfg"]
cfg --> mj["MJWarp 接触语义<br/>MujocoRigidBodyPropertiesCfg"]
nt --> note["混用属性类:静默失效或报错"]
px --> note
mj --> note
1.3 数值卫生¶
向量化把风险也放大了:一个环境算出 NaN/Inf,会沿 batch 张量污染整个训练。
常见的触发点是过大的 sim.dt、穿透深碰撞产生的巨力、或未设上限的关节速度。应对不是逐环境检查,而是在源头设界:物理步长取合理值、用 max_angular_velocity / max_linear_velocity 限幅、接触冲程用 bounce_threshold 之类属性抑制。这与 mujoco-textbook 求解篇 里"求解器迭代与时间步决定接触稳定性"的经验同源,只是 Isaac Lab 把它放大到 batch 级——一处发散、全批中毒。
出问题后的处理要分两条路:定位用 torch.isfinite 掩码找出第一批失效环境,再按时间步回溯它们的动作与接触;恢复按环境单独 reset,而不是整批重启,否则一条坏轨迹会把几千个环境已积累的进度一起丢掉。反向传播同样会被污染——前向的 Inf 一旦进入损失,梯度就是 NaN,优化器一步就把全网权重写坏,这也是梯度裁剪与有限性检查要放在 loss 之前的原因。
坑:一个环境 NaN 会污染整批
批均值与标准差在出现一个非有限值后整体变 nan;定位按环境维筛、沿步维回溯,恢复按环境单独 reset,不整批重启。
2. num_envs 与步长预算¶
num_envs 是训练吞吐的主旋钮。下面的数字在运行环境 Ubuntu 22.04 + RTX 5070 Ti(16 GB 显存)、Isaac-Cartpole-Direct-v0、Newton/MJWarp 后端、rsl_rl PPO(每环境每代 16 步)跑出:
| num_envs | 每代总步数 | 稳态吞吐(steps/s) | 一代显存峰值 |
|---|---|---|---|
| 4,096 | 65,536 | ≈ 370,000 | 约 2.1 GB(含桌面基线约 1.4 GB) |
| 16,384 | 262,144 | ≈ 1,370,000 | 约 2.1 GB(净增约 0.7 GB) |
两个结论:
- 吞吐近乎线性增长(4× 环境带来约 3.7× steps/s),说明在这种轻量任务上 GPU 远未饱和,瓶颈不在并行规模。真正先撞上限的是带相机/点云观测或高自由度机器人的任务——那时单环境的显存占用才是约束,而非 batch 大小。
- 显存主要被任务复杂度支配:16,384 个 cartpole 环境净占不到 1 GB,
num_envs上调空间很大;换成每环境挂多路相机的场景,同样的num_envs会因图像张量而显存陡增。选型时按"每环境显存足迹 × num_envs ≤ 可用显存"估预算,而非记死一个环境数。
首代吞吐(4096 时约 3.3 万、16384 时约 13 万)显著低于稳态,因为第一代含 CUDA graph 编译与 kernel 预热——这也是评估吞吐要读稳态代而非首代的原因。
步长侧,两个配置共同决定每代耗时:sim.dt(物理步长)与 decimation(多少个物理步刷新一次动作,第 3 章)。每代总步数 = num_envs × n_steps_per_env,而墙钟时间 ≈ 总步数 / 吞吐。dt 越小、decimation 越大,单步物理越贵,要在"接触精度"与"训练速度"间权衡。
两者的分工要说清:sim.dt 服务接触稳定(越小越稳),decimation 决定策略看到的控制频率(物理步长 × decimation 的倒数)。控制频率要与真机伺服一致才谈得上迁移——仿真里 200 Hz、实机 50 Hz 的策略,动作序列本身的时间尺度就错位了,域随机化补不回来。
判据:属性类按后端分套
PhysX、Newton、MJWarp 各有一套刚体属性类;同一份建模意图换后端要换对应的类,混用会静默失效或报错。
3. 接触与摩擦的特性边界¶
两后端对接触与摩擦的处理不同,且都与 MuJoCo 有差异,构成从 Isaac 迁移到真机或其他仿真器时的"特性边界":
- Newton / MJWarp:接触求解承袭 MuJoCo 的软约束模型,与
mujoco-textbook的直觉可直接平移——理解成本最低,适合把 CPU 侧调好的接触参数搬到 GPU。 - PhysX(TGS):用位置/速度迭代 + 软约束处理接触与关节限制,摩擦采用各向异性 Coulomb 模型;
solver_position_iteration_count/solver_velocity_iteration_count越高越稳但越贵。其收敛行为与 MuJoCo 的nospring/soft_constraint不同,不能假设一套接触参数在两引擎间直接通用。
一个常被误记的点是"向量化环境数越大越好":结合第 2 节的预算关系,正确结论是——在接触/观测复杂度固定的前提下,num_envs 越大采样吞吐越高;但一旦单环境显存足迹大(相机、多关节),过高的 num_envs 会 OOM 或迫使降相机分辨率,反噬有效采样质量。环境数应设为"填满显存且不牺牲单环境观测分辨率"的上界,而非盲目取大。
4. 要点¶
SimulationContext.step()一次推进全部num_envs个环境;物理量以(num_envs, …)GPU 张量整批暴露,任务逻辑须写成张量运算、不得在 Python 层按环境循环。- 两个物理后端(Newton/MJWarp 与 PhysX)属性类各一套(
NewtonRigidBodyPropertiesCfgvsPhysxRigidBodyPropertiesCfg),跨后端切换要换对应属性类,混用会失效。 - 数值卫生在 batch 级被放大:一处 NaN/Inf 会污染全批;靠
sim.dt、速度上限、接触阈值在源头设界,而非逐环境检查。 - 稳态吞吐随
num_envs近线性增长(运行环境跑出 4096→约 37 万、16384→约 137 万 steps/s,轻任务下 GPU 未饱和);首代含编译预热,读数要看稳态代。 - 显存由任务复杂度(相机/自由度)主导而非环境数;预算式为"每环境足迹 × num_envs ≤ 可用显存",
num_envs取该上界。 - 接触/摩擦上,MJWarp 与 MuJoCo 直觉可平移,PhysX 的 TGS 迭代/软约束行为不同;"环境数越大越好"不成立,过高会因单环境足迹触发 OOM 或降观测质量。