学习方式比较简单有效:用kimi code / claude code直接基于源码设计一份教学文档,目标是一步步把我教会,形式采用互动教学形式即可
学习方式:逐章进行,每章结束后需确认理解,才进入下一章。
学习过程中请随时打断提问。
经过这个过程,基本就完成了从0-->1的训练pipeline的认知。
第1章 项目定位与训练入口
1.1 学习目标
- 理解
unitree_rl_lab在整个技术栈中的位置 - 掌握正确的训练启动命令
- 理解任务注册机制(
gym.register)
1.2 技术栈层级
┌─────────────────────────────────────────┐
│ PyTorch + RSL-RL(PPO算法库) │ ← 神经网络训练
├─────────────────────────────────────────┤
│ unitree_rl_lab(本层) │ ← G1任务、自定义奖励、课程学习
├─────────────────────────────────────────┤
│ Isaac Lab(中间层) │ ← ManagerBasedRLEnv、8大Manager
├─────────────────────────────────────────┤
│ Omniverse Isaac Sim(底层) │ ← PhysX物理引擎、USD场景、GPU并行
└─────────────────────────────────────────┘
关键理解 :你平时改配置、调奖励,99% 的时间都在 unitree_rl_lab 这一层。
1.3 训练入口
脚本位置 :extern/unitree_rl_lab/unitree_rl_lab.sh
bash
cd extern/unitree_rl_lab
# 训练
./unitree_rl_lab.sh -t --task=Unitree-G1-29dof-Velocity --num_envs=4096
# 推理
./unitree_rl_lab.sh -p --task=Unitree-G1-29dof-Velocity --num_envs=1
-t 实际调用的是 scripts/rsl_rl/train.py,-p 调用的是 scripts/rsl_rl/play.py。
1.4 任务注册机制
源码位置 :source/unitree_rl_lab/unitree_rl_lab/tasks/locomotion/robots/g1/29dof/__init__.py
python
gym.register(
id="Unitree-G1-29dof-Velocity",
entry_point="isaaclab.envs:ManagerBasedRLEnv",
kwargs={
"env_cfg_entry_point": "velocity_env_cfg:RobotEnvCfg", # 训练配置
"play_env_cfg_entry_point": "velocity_env_cfg:RobotPlayEnvCfg", # 推理配置
"rsl_rl_cfg_entry_point": "rsl_rl_ppo_cfg:BasePPORunnerCfg", # PPO算法配置
},
)
| 入口点 | 配置类 | 用途 |
|---|---|---|
env_cfg_entry_point |
RobotEnvCfg |
训练环境配置(num_envs=4096,课程学习控制指令范围) |
play_env_cfg_entry_point |
RobotPlayEnvCfg |
推理环境配置(num_envs=32,指令范围直接用limit) |
rsl_rl_cfg_entry_point |
BasePPORunnerCfg |
PPO网络结构与超参 |
1.5 本章 Checkpoint(必须掌握)
unitree_rl_lab和isaac_lab的分工是什么?- 正确的训练启动命令是什么?
- 三个
entry_point分别对应什么? - 为什么训练和推理用不同的环境配置类?
1.6 自测问题
Q1: 如果我想修改奖励函数权重,应该改哪一层的代码?
Q2: RobotPlayEnvCfg 和 RobotEnvCfg 的核心区别是什么?
第2章 环境架构:ManagerBasedRLEnv
2.1 学习目标
- 理解 Manager-Based 设计哲学
- 掌握 8 大 Manager 的职责分工
- 理解关键时间参数(dt, decimation, episode_length_s)的关系
2.2 八大 Manager
ManagerBasedRLEnv
├── SceneManager → 场景实体(地形、机器人、传感器)
├── ObservationManager → 构造观测向量
├── ActionManager → 接收动作、驱动机器人
├── RewardManager → 计算奖励
├── TerminationManager → 判断终止
├── EventManager → 随机化/重置/推力
├── CommandManager → 生成速度指令
└── CurriculumManager → 调整课程难度
2.3 配置类对应关系
在 RobotEnvCfg 中:
python
scene: RobotSceneCfg = RobotSceneCfg(...) # ← SceneManager
observations: ObservationsCfg = ObservationsCfg() # ← ObservationManager
actions: ActionsCfg = ActionsCfg() # ← ActionManager
commands: CommandsCfg = CommandsCfg() # ← CommandManager
rewards: RewardsCfg = RewardsCfg() # ← RewardManager
terminations: TerminationsCfg = TerminationsCfg() # ← TerminationManager
events: EventCfg = EventCfg() # ← EventManager
curriculum: CurriculumCfg = CurriculumCfg() # ← CurriculumManager
2.4 关键时间参数
python
self.sim.dt = 0.005 # 物理步长 = 5ms,PhysX 以 200Hz 运行
self.decimation = 4 # 每 4 个物理步做一次 RL 决策
self.episode_length_s = 30.0 # Episode 最大时长 30 秒
计算关系:
step_dt = decimation × sim.dt = 4 × 0.005 = 0.02s = 20ms- 策略决策频率 = 50Hz
max_episode_length = episode_length_s / step_dt = 30 / 0.02 = 1500步
时间线图解:
时间(ms): 0 5 10 15 20 25 30 35 40 ...
PhysX步: █ █ █ █ █ █ █ █ █ (200Hz)
↑
策略决策点 (50Hz)
2.5 为什么 episode_length_s = 30.0?
代码注释说明:
python
self.episode_length_s = 30.0 # 加长: 把持续转向累积失稳临界点(实测8~22s)包进训练时长
团队实测机器人在持续转向时,约 8~22 秒 会出现累积失稳。Episode 加长到 30 秒,强迫策略学会长时间稳定转向。
2.6 本章 Checkpoint
- 8 大 Manager 分别管什么?
sim.dt、decimation、step_dt的关系是什么?策略实际运行频率是多少?- 一个 Episode 最多多少步?怎么算出来的?
- 为什么 episode_length_s 要设成 30.0 而不是更短?
第3章 场景配置 RobotSceneCfg
3.1 学习目标
- 理解地形生成配置
- 理解物理材质与摩擦随机化
- 理解传感器的作用
3.2 地形配置
python
COBBLESTONE_ROAD_CFG = terrain_gen.TerrainGeneratorCfg(
size=(8.0, 8.0), # 每个地形块 8m × 8m
num_rows=9, num_cols=21, # 9×21 = 189 个地形块
horizontal_scale=0.1,
vertical_scale=0.005,
sub_terrains={"flat": terrain_gen.MeshPlaneTerrainCfg(proportion=0.5)},
)
当前以平地为主(proportion=0.5),课程学习开启后会逐步增加难度地形。
3.3 物理材质
python
physics_material=sim_utils.RigidBodyMaterialCfg(
static_friction=1.0,
dynamic_friction=1.0,
)
启动时 EventManager 会将摩擦随机化到 [0.3, 1.0],增强 Sim-to-Real 泛化。
3.4 传感器
| 传感器 | 用途 |
|---|---|
contact_forces |
检测接触力,用于步态奖励、脚底打滑、不期望接触 |
height_scanner |
从 torso 向下射线扫描地形高度(当前未在观测中使用) |
3.5 本章 Checkpoint
- 当前地形配置以什么为主?
- 摩擦系数在运行时会被随机化到什么范围?目的是什么?
contact_forces传感器在哪些奖励函数中被用到?
第4章 观测构造 ObservationsCfg
4.1 学习目标
- 掌握 Policy 和 Critic 的观测组成
- 理解历史缓冲区
history_length=5的作用 - 理解
scale和noise的含义
4.2 Policy 观测(Actor 输入)
| 观测项 | 维度 | 含义 | 缩放/噪声 |
|---|---|---|---|
base_ang_vel |
3 | 机体角速度 | scale=0.2, noise=±0.2 |
projected_gravity |
3 | 重力在机体坐标系投影 | noise=±0.05 |
velocity_commands |
3 | 速度指令 vx, vy, ωz | 无 |
joint_pos_rel |
29 | 关节位置相对默认姿态 | noise=±0.01 |
joint_vel_rel |
29 | 关节速度 | scale=0.05, noise=±1.5 |
last_action |
29 | 上一步动作 | 无 |
基础维度 :3 + 3 + 3 + 29 + 29 + 29 = 96
4.3 历史缓冲区
python
self.history_length = 5
self.concatenate_terms = True
实际输入维度 = 96 × 5 = 480
原理:机器人的运动具有时序性,最近 5 帧的观测拼接后,策略能感知速度、加速度趋势。
4.4 Critic 特权观测
Critic 比 Policy 多看到 base_lin_vel(世界坐标系线速度)。
为什么 Policy 不能看? 因为真机没有全局定位系统(GPS/动捕),这是 Sim-to-Real 的 gap。
4.5 本章 Checkpoint
- Policy 观测共多少维?各项分别是什么?
- 加上历史缓冲区后,实际输入是多少维?
- Critic 比 Policy 多看到什么?为什么 Policy 不能看?
scale=0.2和noise=±0.2分别是什么意思?
第5章 动作输出 ActionsCfg
5.1 学习目标
- 理解 JointPositionAction 的含义
- 理解
scale=0.25和use_default_offset=True
5.2 动作机制
python
JointPositionAction = mdp.JointPositionActionCfg(
asset_name="robot", joint_names=[".*"], scale=0.25, use_default_offset=True
)
实际执行:
q_target = q_default + 0.25 × action
- 策略输出
action ∈ [-1, 1](29 维) scale=0.25弧度 ≈ ±14.3°use_default_offset=True表示以默认姿态为基准
5.3 为什么用位置控制?
- 位置控制 + 底层 PD 更稳定,仿真不容易发散
- 真机 G1 底层控制器也接受位置指令
- 策略只需决定"关节去哪里",不用处理复杂力矩动力学
5.4 本章 Checkpoint
- 策略输出 29 维动作,范围是多少?
- 实际发给电机的目标位置怎么算?
scale=0.25对应多少度?- 为什么用位置控制而不是力矩控制?
第6章 指令系统 CommandsCfg
6.1 学习目标
- 理解
rangesvslimit_ranges - 理解指令采样流程
- 理解课程学习的本质
6.2 指令配置
python
base_velocity = mdp.UniformLevelVelocityCommandCfg(
resampling_time_range=(5.0, 20.0), # 每 5~20 秒重新采样
rel_standing_envs=0.02, # 2% 环境指令为 0
ranges=Ranges(lin_vel_x=(-0.1, 0.1), lin_vel_y=(-0.1, 0.1), ang_vel_z=(-0.3, 0.3)),
limit_ranges=Ranges(lin_vel_x=(-0.5, 2.0), lin_vel_y=(-0.4, 0.4), ang_vel_z=(-1.0, 1.0)),
)
6.3 ranges vs limit_ranges
| 参数 | 作用 |
|---|---|
ranges |
课程学习当前实际使用的指令范围 |
limit_ranges |
指令范围的硬上限,课程学习不会超越 |
初始 vs 上限:
- vx:
[-0.1, 0.1]→[-0.5, 2.0] - vy:
[-0.1, 0.1]→[-0.4, 0.4] - ωz:
[-0.3, 0.3]→[-1.0, 1.0]
6.4 采样流程
- Episode 开始,每个环境独立采样持续时长
T ∈ [5, 20]秒 [0, T]内指令保持不变- 达到
T后重新采样(在ranges范围内均匀采样) - 2% 环境被强制设为 0 指令(站立)
6.5 本章 Checkpoint
ranges和limit_ranges的区别是什么?- 指令多久重新采样一次?
- 课程学习的本质是什么?
- 当前 vx 的初始范围和上限分别是多少?
第7章 奖励函数详解 RewardsCfg
7.1 学习目标
- 掌握 18 项 reward 的数学公式、weight、作用
- 理解各项 reward 对训练行为的影响
- 掌握调参的基本逻辑
7.2 Task Rewards(核心)
track_lin_vel_xy_yaw_frame_exp
vel_yaw = quat_apply_inverse(yaw_quat(root_quat), root_lin_vel_w)
error = ||cmd_xy - vel_yaw_xy||²
reward = exp(-error / 0.25) # std=0.5, weight=1.0
track_ang_vel_z_exp
error = (ωz_cmd - ωz_actual)²
reward = exp(-error / 0.25) # std=0.5, weight=1.0
7.3 Base Penalties
| 名称 | Weight | 公式 | 作用 |
|---|---|---|---|
| alive | +0.15 | 固定值 | 存活奖励 |
| lin_vel_z_l2 | -2.0 | -vz² |
惩罚上下弹跳 |
| ang_vel_xy_l2 | -0.03 | -(ωx²+ωy²) |
惩罚 roll/pitch 角速度 |
| joint_vel_l2 | -0.001 | -Σqvel² |
惩罚关节过快 |
| joint_acc_l2 | -2.5e-7 | -Σqacc² |
惩罚加速度过冲 |
| action_rate_l2 | -0.05 | -Σ(Δa)² |
关键:惩罚动作抖动 |
| joint_pos_limits | -5.0 | 超限 L1 | 硬约束 |
| energy | -2e-5 | `Σ | qvel×qtorque |
7.4 Joint Deviation
| 名称 | Weight | 目标关节 |
|---|---|---|
| joint_deviation_arms | -0.1 | shoulder, elbow, wrist |
| joint_deviation_waists | -1.0 | waist_yaw, roll, pitch |
| joint_deviation_legs | -1.0 | hip_roll, hip_yaw |
7.5 Robot Penalties
| 名称 | Weight | 公式 |
|---|---|---|
| flat_orientation_l2 | -5.0 | -(1 + grav_z)² |
| base_height_l2 | -10.0 | -(h - 0.78)² |
7.6 Feet Rewards
| 名称 | Weight | 关键参数 |
|---|---|---|
| gait | +0.5 | period=0.8s, offset=0,0.5, threshold=0.55 |
| feet_slide | -0.2 | 接触时水平滑动速度 |
| feet_clearance | +1.0 | target=0.1m, std=0.05 |
7.7 Other
| 名称 | Weight | 说明 |
|---|---|---|
| undesired_contacts | -1.0 | 非 ankle 部位触地惩罚 |
7.8 调参逻辑
| 现象 | 调参方向 |
|---|---|
| 站着不动 | 提高 track_lin_vel_xy weight,降低 action_rate 惩罚 |
| 高频抖动 | 提高 action_rate 惩罚 |
| 容易摔倒 | 提高 flat_orientation 和 base_height 惩罚 |
| 外八/内八 | 提高 joint_deviation_legs 惩罚 |
7.9 本章 Checkpoint
- 哪两项是核心任务奖励?它们的数学形式是什么?
action_rate_l2的作用是什么?weight 变大或变小分别有什么影响?base_height_l2的目标高度是多少?为什么 weight 这么重?feet_gait的周期、两条腿偏移、stance 比例分别是多少?- 如果机器人站着不动,应该怎么调参?
第8章 终止条件 TerminationsCfg
8.1 学习目标
- 掌握三项终止条件
- 理解 episode 长度计算
8.2 终止条件
python
class TerminationsCfg:
time_out = DoneTerm(func=mdp.time_out, time_out=True)
base_height = DoneTerm(func=mdp.root_height_below_minimum, params={"minimum_height": 0.2})
bad_orientation = DoneTerm(func=mdp.bad_orientation, params={"limit_angle": 0.8})
| 条件 | 触发机制 | 含义 |
|---|---|---|
| time_out | 达到 30 秒 | 正常结束,视为成功 |
| base_height | 高度 < 0.2m | 摔倒或趴地 |
| bad_orientation | 倾斜 > 0.8 rad (≈46°) | 严重失稳 |
8.3 本章 Checkpoint
- 三项终止条件分别是什么?
- bad_orientation 的角度限制是多少弧度?约多少度?
- time_out 对应的步数是多少?怎么算的?
第9章 事件与域随机化 EventCfg
9.1 学习目标
- 理解三种事件触发模式(startup/reset/interval)
- 掌握每项域随机化的作用
9.2 事件分类
startup(环境启动时执行一次)
| 事件 | 参数 | 作用 |
|---|---|---|
| physics_material | friction ∈ 0.3, 1.0 | 随机化摩擦系数 |
| add_base_mass | mass ∈ -1.0, +3.0 kg | 随机化躯干质量 |
reset(Episode 终止后重置)
| 事件 | 参数 | 作用 |
|---|---|---|
| reset_base | position ∈ -0.5, 0.5m, yaw ∈ -π, π | 随机初始位置和朝向 |
| reset_robot_joints | velocity ∈ -1, 1 | 随机初始关节状态 |
interval(训练过程中定时触发)
| 事件 | 间隔 | 参数 | 作用 |
|---|---|---|---|
| push_robot | 3~6 秒随机 | velocity ∈ -0.5, 0.5 m/s | 随机外力推动 |
9.3 本章 Checkpoint
- 三种事件触发模式分别是什么?各举一例。
- 摩擦系数被随机化到什么范围?
- 推力事件多久触发一次?有什么作用?
第10章 课程学习 CurriculumCfg
10.1 学习目标
- 理解课程学习的触发机制
- 掌握
lin_vel_cmd_levels的源码逻辑
10.2 配置
python
class CurriculumCfg:
terrain_levels = CurrTerm(func=mdp.terrain_levels_vel)
lin_vel_cmd_levels = CurrTerm(mdp.lin_vel_cmd_levels)
ang_vel_cmd_levels = CurrTerm(mdp.ang_vel_cmd_levels)
10.3 线速度课程源码
python
def lin_vel_cmd_levels(env, env_ids, reward_term_name="track_lin_vel_xy"):
command_term = env.command_manager.get_term("base_velocity")
ranges = command_term.cfg.ranges
limit_ranges = command_term.cfg.limit_ranges
reward_term = env.reward_manager.get_term_cfg(reward_term_name)
reward = torch.mean(env.reward_manager._episode_sums[reward_term_name][env_ids]) / env.max_episode_length_s
if reward > reward_term.weight * 0.5: # 跟踪奖励 > 0.5 时升级
delta_command = torch.tensor([-0.1, 0.1], device=env.device)
ranges.lin_vel_x = torch.clamp(
torch.tensor(ranges.lin_vel_x, device=env.device) + delta_command,
limit_ranges.lin_vel_x[0], limit_ranges.lin_vel_x[1]
).tolist()
ranges.lin_vel_y = torch.clamp(...).tolist()
return torch.tensor(ranges.lin_vel_x[1], device=env.device)
升级条件 :当 track_lin_vel_xy 平均每步奖励 > weight × 0.5 = 0.5 时,指令范围扩展 ±0.1。
10.4 本章 Checkpoint
- 课程学习在什么时候检查升级?
- 升级的条件是什么?
- 每次升级指令范围扩大多少?
- 指令范围的上限是什么?
第11章 PPO 算法配置
11.1 学习目标
- 掌握网络结构
- 理解 PPO 关键超参
11.2 网络结构
Actor(策略网络):
python
actor = RslRlMLPModelCfg(
hidden_dims=[512, 256, 128],
activation="elu",
stochastic=True,
init_noise_std=1.0,
noise_std_type="scalar",
)
Critic(价值网络):
python
critic = RslRlMLPModelCfg(
hidden_dims=[512, 256, 128],
activation="elu",
stochastic=False,
)
11.3 PPO 超参
python
algorithm = RslRlPpoAlgorithmCfg(
class_name="PPO",
clip_param=0.2,
entropy_coef=0.01,
num_learning_epochs=5,
num_mini_batches=4,
learning_rate=1.0e-3,
schedule="adaptive",
gamma=0.99,
lam=0.95,
desired_kl=0.01,
max_grad_norm=1.0,
)
11.4 关键参数
| 参数 | 值 | 说明 |
|---|---|---|
| num_steps_per_env | 24 | 每轮收集 24 步 |
| num_mini_batches | 4 | 分成 4 份 |
| num_learning_epochs | 5 | 复用 5 个 epoch |
| schedule | adaptive | KL > 0.015 时 lr 减半;KL < 0.005 时 lr 增加 |
11.5 本章 Checkpoint
- Actor 和 Critic 的网络结构是什么?
clip_param=0.2是什么意思?schedule=adaptive如何调整学习率?- 每轮产生多少条 transition?mini-batch 多大?
第12章 训练数据流
12.1 学习目标
- 理解从
runner.learn()到env.step()的完整数据流
12.2 训练循环三阶段
阶段1: Rollout(数据收集)
python
for step in range(24):
obs = env.get_observations() # 获取观测
actions = policy(obs) # Actor 输出动作
env.step(actions) # 执行动作
# 内部:ActionManager → PhysX×4 → RewardManager → TerminationManager
buffer.store(obs, actions, reward, done, values, log_probs)
阶段2: GAE 计算
python
advantages = GAE(rewards, values, dones, gamma=0.99, lam=0.95)
returns = advantages + values
阶段3: PPO 更新
python
for epoch in range(5):
for mini_batch in data.split(4):
ratio = exp(new_log_probs - old_log_probs)
surrogate1 = ratio * advantages
surrogate2 = clip(ratio, 0.8, 1.2) * advantages
policy_loss = -min(surrogate1, surrogate2).mean()
value_loss = MSE(critic(obs), returns)
total_loss = policy_loss + value_loss + entropy_loss
total_loss.backward()
optimizer.step()
12.3 本章 Checkpoint
- 训练循环分哪三个阶段?
env.step()内部发生了什么?- PPO 的
ratio是什么?为什么需要clip? - 自适应学习率在什么情况下会调整?
第13章 G1 29DOF 机器人配置
13.1 学习目标
- 掌握 29 个关节的名称和顺序
- 理解执行器分组
13.2 关节顺序(按 joint_sdk_names)
| # | 关节名 | 部位 | 执行器 |
|---|---|---|---|
| 1 | left_hip_pitch_joint | 左腿 | N7520-14.3 |
| 2 | left_hip_roll_joint | 左腿 | N7520-22.5 |
| 3 | left_hip_yaw_joint | 左腿 | N7520-14.3 |
| 4 | left_knee_joint | 左腿 | N7520-22.5 |
| 5 | left_ankle_pitch_joint | 左脚 | N5020-16 |
| 6 | left_ankle_roll_joint | 左脚 | N5020-16 |
| 7-10 | right_hip/knee | 右腿 | 同上 |
| 11-12 | right_ankle | 右脚 | N5020-16 |
| 13 | waist_yaw_joint | 腰部 | N7520-14.3 |
| 14-15 | waist_roll/pitch | 腰部 | N5020-16 |
| 16-22 | 左臂 7 DOF | 左臂 | N5020-16/W4010-25 |
| 23-29 | 右臂 7 DOF | 右臂 | N5020-16/W4010-25 |
13.3 本章 Checkpoint
- G1 有多少个关节?
- 腿部哪些关节使用 N7520-22.5 执行器?
- 踝关节的执行器型号是什么?
第14章 关键公式与参数速查表
14.1 奖励速查
| 名称 | Weight | 公式 |
|---|---|---|
| track_lin_vel_xy | +1.0 | exp(-error²/0.25) |
| track_ang_vel_z | +1.0 | exp(-error²/0.25) |
| alive | +0.15 | 固定 |
| lin_vel_z_l2 | -2.0 | -vz² |
| ang_vel_xy_l2 | -0.03 | -(ωx²+ωy²) |
| action_rate_l2 | -0.05 | -Σ(Δa)² |
| flat_orientation | -5.0 | -(1+grav_z)² |
| base_height | -10.0 | -(h-0.78)² |
| gait | +0.5 | stance 匹配度 |
| feet_clearance | +1.0 | exp(-error/0.05) |
| undesired_contacts | -1.0 | 非 ankle 触地 |
14.2 时间参数速查
| 参数 | 值 |
|---|---|
| sim.dt | 0.005 s (200Hz) |
| decimation | 4 |
| step_dt | 0.02 s (50Hz) |
| episode_length_s | 30.0 s |
| max_episode_length | 1500 steps |
| num_envs | 4096 |
| num_steps_per_env | 24 |
| batch_size | 98304 |
| mini_batch_size | 24576 |
14.3 PPO 超参速查
| 参数 | 值 |
|---|---|
| hidden_dims | 512, 256, 128 |
| activation | elu |
| clip_param | 0.2 |
| entropy_coef | 0.01 |
| learning_rate | 1.0e-3 |
| gamma | 0.99 |
| lam | 0.95 |
| desired_kl | 0.01 |