WebShop GRPO 训练代码架构分析
本文档梳理 run_webshop_skills.sh 启动后,代码从入口到最终策略更新的完整执行流程,
涵盖动作模拟、环境交互、奖励计算、技能注入和动态更新等关键环节。
目录
- 整体架构概览
- 训练入口与初始化
- 核心训练循环 (fit)
- [多轮交互 Rollout 流程](#多轮交互 Rollout 流程)
- [WebShop 环境实现](#WebShop 环境实现)
- 奖励计算
- [GRPO 优势估计](#GRPO 优势估计)
- 策略更新 (Actor Update)
- 技能库系统
- 动态技能更新(教师模型)
- 验证流程与技能触发
- 端到端数据流汇总
1. 整体架构概览
run_webshop_skills.sh
|
v
main_ppo.py (Hydra 入口)
|
v
TaskRunner.run() (Ray 远程 actor)
| ├── make_envs() → WebshopEnvironmentManager (训练/验证环境)
| ├── EpisodeRewardManager → 奖励计算器
| ├── TrajectoryCollector → 多轮交互循环
| └── RayPPOTrainer → 核心训练器
| └── fit() → 训练循环
| ├── 多轮 rollout → 收集轨迹
| ├── 奖励计算 → 稀疏 episode 奖励
| ├── GRPO 优势 → 组内相对优势
| ├── Actor 更新 → PPO clipped loss + KL loss
| ├── 验证 → 评估 + 动态技能更新
| └── 保存 checkpoint
关键设计决策
| 决策 | 说明 |
|---|---|
| GRPO 分组方式 | 标准 verl 用 rollout.n 重复生成;SkillRL 用 env.rollout.n 启动多个环境 worker,同一任务分配 n 个 worker 并行交互 |
| 单步独立 chat | 每个环境步骤,模型看到的是一个全新的单轮对话,历史信息由环境管理器嵌入在文本观察中 |
| 稀疏奖励 | 只有 episode 结束时才有奖励(win=10, lose=0),放在最后 token 上 |
| 技能注入 | 通过模板模式,按任务类型关键词匹配检索技能,注入到 prompt 顶部 |
2. 训练入口与初始化
2.1 文件:verl/trainer/main_ppo.py
调用链:
main() (Hydra 装饰器加载 ppo_trainer.yaml)
→ run_ppo(config)
→ ray.init(...)
→ TaskRunner.remote() # Ray 远程 actor,运行在专用 CPU 上
→ TaskRunner.run.remote(config)
TaskRunner.run() 内部初始化顺序:
| 步骤 | 操作 | 对应代码位置 |
|---|---|---|
| 1 | copy_to_local() 下载模型 checkpoint |
main_ppo.py:90 |
| 2 | make_envs(config) 创建训练/验证环境 |
main_ppo.py:95 |
| 3 | hf_tokenizer() 实例化分词器 |
main_ppo.py:100 |
| 4 | 选择 worker 策略 (FSDP/Megatron) | main_ppo.py:120 |
| 5 | 创建 EpisodeRewardManager 作为 reward_fn |
main_ppo.py:152 |
| 6 | 创建 TrajectoryCollector |
main_ppo.py:162 |
| 7 | 创建 RayPPOTrainer |
main_ppo.py:169 |
| 8 | trainer.init_workers() 初始化所有 worker |
main_ppo.py:183 |
| 9 | trainer.fit() 开始训练 |
main_ppo.py:186 |
2.2 GRPO 与 PPO 的关系
文件 :verl/trainer/ppo/ray_trainer.py:445-458
python
if self.config.algorithm.adv_estimator == AdvantageEstimator.GAE:
self.use_critic = True
elif self.config.algorithm.adv_estimator in [
AdvantageEstimator.GRPO, AdvantageEstimator.RLOO, ...
]:
self.use_critic = False # GRPO 不需要 Critic 网络
GRPO 是 PPO 框架下的一个变体。main_ppo.py 是通用入口,通过 algorithm.adv_estimator=grpo 切换到 GRPO 模式,跳过 Critic 网络的创建和更新。
3. 核心训练循环 (fit)
文件:verl/trainer/ppo/ray_trainer.py:1209-1506
每个 training step 的完整执行序列:
for epoch in total_epochs:
for batch in train_dataloader:
|
| ① Pop generation keys → gen_batch
| ② 多轮 Rollout (multi_turn_loop)
| ③ Post-processing (pad, response_mask)
| ④ 奖励计算 (EpisodeRewardManager)
| ⑤ 重算旧策略 log_prob
| ⑥ 参考策略 log_prob (KL loss 用)
| ⑦ 优势估计 (compute_grpo_outcome_advantage)
| ⑧ Actor 策略更新 (PPO clipped loss + KL loss)
| ⑨ 日志记录 & checkpoint
| ⑩ 每 test_freq 步执行验证 + 动态技能更新
v
各阶段详解
| 阶段 | 输入 | 输出 | 说明 |
|---|---|---|---|
| ① Pop keys | batch (DataProto) |
gen_batch + batch (剩余) |
将 input_ids、raw_prompt 等弹出生成所需字段 |
| ② Rollout | gen_batch |
gen_batch_output |
多轮环境交互,生成完整轨迹 |
| ③ Post-process | gen_batch_output + batch |
合并的 batch |
计算 response_mask、平衡 batch |
| ④ Reward | batch |
reward_tensor |
在最后 token 位置放置 episode 奖励 |
| ⑤ Old log prob | batch |
old_log_prob |
用当前 actor 权重重算 log prob |
| ⑥ Ref log prob | batch |
ref_log_prob |
用初始(冻结的)策略算 log prob |
| ⑦ Advantage | batch |
advantages, returns |
GRPO 组内标准化 |
| ⑧ Actor update | batch |
loss metrics | PPO dual-clip + KL loss + entropy |
| ⑨ Logging | metrics | wandb/console | 汇报训练指标 |
4. 多轮交互 Rollout 流程
文件:agent_system/multi_turn_rollout/rollout_loop.py
4.1 入口:multi_turn_loop()
python
def multi_turn_loop(self, gen_batch, actor_rollout_wg, envs, is_train=True):
# 训练时:将每个任务复制 n 份(GRPO 分组)
if is_train:
gen_batch = gen_batch.repeat(repeat_times=self.config.env.rollout.n, interleave=True)
if self.config.algorithm.filter_groups.enable and is_train:
result = self.dynamic_multi_turn_loop(...) # DAPO 动态采样
else:
result = self.vanilla_multi_turn_loop(...) # 标准采样
gen_batch_output = self.gather_rollout_data(...)
return gen_batch_output
GRPO 分组原理 :env.rollout.n=8 时,parquet 中的 16 个条目被复制为 128 个,每连续 8 个共享相同的 uid(任务标识),对应 WebShop 中的同一个购物目标。训练时 8 个环境 worker 对同一任务各自独立探索,产生 8 条不同轨迹,GRPO 用这些轨迹的相对好坏来计算优势。
4.2 核心循环:vanilla_multi_turn_loop()
python
def vanilla_multi_turn_loop(self, gen_batch, actor_rollout_wg, envs, ...):
# === 阶段 A:环境初始化 ===
obs, infos = envs.reset() # 每个 worker 得到初始观察(含任务描述)
batch_size = len(obs['text'])
# 为 GRPO 分组分配 uid
for i in range(batch_size):
if i % self.config.env.rollout.n == 0:
uid = str(uuid.uuid4()) # 新任务 → 新 uid
uid_batch.append(uid)
# === 阶段 B:逐步交互循环 ===
for _step in range(self.config.env.max_steps): # WebShop: 15步
active_masks = np.logical_not(is_done)
# B1: 将观察转为 token(应用 chat template)
batch = self.preprocess_batch(gen_batch=gen_batch, obs=obs)
# B2: 模型生成动作
batch_output = actor_rollout_wg.generate_sequences(batch_input)
text_actions = tokenizer.batch_decode(batch_output.batch['responses'],
skip_special_tokens=True)
# B3: 环境执行动作
next_obs, rewards, dones, infos = envs.step(text_actions)
# B4: 累积奖励
episode_rewards[active_masks] += rewards[active_masks]
episode_lengths[active_masks] += 1
# B5: 存储轨迹数据
batch.non_tensor_batch['rewards'] = rewards
batch.non_tensor_batch['active_masks'] = active_masks
for i in range(batch_size):
total_batch_list[i].append(batch_list[i])
# B6: 更新终止状态
is_done = np.logical_or(is_done, dones)
obs = next_obs
if is_done.all():
break
# === 阶段 C:评估和汇总 ===
success = envs.success_evaluator(...)
return total_batch_list, episode_rewards, episode_lengths, success, ...
4.3 观察预处理:preprocess_single_sample()
每个环境步骤,观察被转化为模型输入:
环境文本观察 (含任务、历史、可用动作)
|
v
构建单轮 chat: [{"role": "user", "content": obs_text}]
|
v
应用 chat template: tokenizer.apply_chat_template()
| 示例 (Qwen): <|im_start|>user\n{obs_text}<|im_start|>assistant\n
v
Tokenize → input_ids, attention_mask, position_ids
|
v
存入 DataProto (同时存 raw_prompt 供训练时重算 log_prob)
关键点 :模型在每个步骤看到的是一个全新的 user 消息 ,不包含之前自己的回复。
所有历史信息(之前的观察和动作)已经由 WebshopEnvironmentManager.build_text_obs() 编排进了 obs_text 中。
4.4 模型推理
文件 :verl/workers/rollout/vllm_rollout/vllm_rollout.py
通过 vLLM 推理引擎批量生成:
python
output = self.inference_engine.generate(
prompt_token_ids=idx_list,
sampling_params=self.sampling_params, # temperature=1.0 for train
)
response = output[0] # (bs, response_length) 生成的 token IDs
log_probs = output[1] # (bs, response_length) 每个 token 的 log 概率
生成的文本格式要求:
<think...>推理过程...</think...><action>search[blue running shoe]</action>
5. WebShop 环境实现
5.1 环境创建
文件 :agent_system/environments/env_manager.py:865-888
python
elif "webshop" in config.env.env_name.lower():
from agent_system.environments.env_package.webshop import build_webshop_envs, webshop_projection
# env_num = train_batch_size (16)
# group_n = env.rollout.n (8)
# 总 worker 数 = 16 × 8 = 128 个 Ray actor
_envs = build_webshop_envs(env_num=..., group_n=..., is_train=True, ...)
envs = WebshopEnvironmentManager(_envs, webshop_projection, config)
5.2 向量化环境:WebshopMultiProcessEnv
文件 :agent_system/environments/env_package/webshop/envs.py
| 组件 | 说明 |
|---|---|
WebshopWorker |
Ray 远程 actor,每个 worker 持有一个 WebAgentTextEnv 实例 |
WebshopMultiProcessEnv |
向量化封装,管理 env_num × group_n 个 worker |
训练/验证数据划分:
- 训练集:goal 索引
[500, len(goals)) - 验证集:goal 索引
[0, 500)
Reset(分配任务):
python
def reset(self):
# 从训练集中随机采样 env_num 个不重复的 goal
# 每个 goal 分配给 group_n 个 worker(同一任务,不同探索)
indices = random.sample(train_indices, env_num) # 不放回
repeated_indices = [idx for idx in indices for _ in range(group_n)] # 复制
# 每个 worker reset 到各自的 goal
obs, info = worker.reset(session=repeated_indices[i])
Step(执行动作):
python
def step(self, action):
obs, reward, done, info = self.env.step(action)
info['available_actions'] = self.env.get_available_actions()
info['task_score'] = reward # 保存原始分数
# 二值化奖励
if done and reward == 1.0:
info['won'] = True
reward = 10.0 # 成功购买正确商品
else:
info['won'] = False
reward = 0.0 # 其他所有情况
return obs, reward, done, info
5.3 动作投影:webshop_projection()
文件 :agent_system/environments/env_package/webshop/projection.py
将模型文本输出解析为环境可执行的动作,同时验证格式:
python
def webshop_projection(actions: List[str]):
valids = [0] * len(actions)
for i, action in enumerate(actions):
# 1. 提取 <action>...</action> 之间的内容
start = action.lower().find("<action>")
end = action.lower().find("</action>")
if start == -1 or end == -1:
actions[i] = action[-20:] # 回退:取最后20字符
continue # valids[i] 保持为 0
extracted = action[start+8:end].strip().lower()
actions[i] = extracted
valids[i] = 1
# 2. 检查 <think...></think...> 推理标签
if "<think" not in action or "</think" not in action:
valids[i] = 0
# 3. 检查中文字符
if re.search(r'[\u4e00-\u9fff]', action):
valids[i] = 0
return actions, valids
验证规则:
- 必须包含
<action>...</action>标签 - 必须包含
<think...></think...>推理标签 - 不能包含中文字符
- 无效动作仍会发送到环境(使用截断的回退字符串),但
is_action_valid=0会触发惩罚
5.4 环境管理器:WebshopEnvironmentManager
文件 :agent_system/environments/env_manager.py:538-718
这是环境系统的核心协调器,负责:
Reset 流程
envs.reset() → 原始 WebShop 观察文本
|
v
extract_task(obs) → 从 "[SEP] Instruction: [SEP] Find me a..." 中提取任务描述
|
v
format_obs(obs) → 重新格式化观察文本
|
v
retrieval_memory.retrieve(task) → 检索相关技能(如启用)
|
v
build_text_obs(obs, infos, init=True) → 组装完整 prompt
|
v
memory.reset() → 重置动作历史
|
v
返回 {'text': [prompt_1, ...], 'image': None, 'anchor': [...]}
Step 流程
text_actions (模型输出文本)
|
v
projection_f(text_actions) → (actions, valids) # 解析动作
|
v
envs.step(actions) → (obs, rewards, dones, infos)
|
v
format_obs(obs) → 格式化
|
v
memory.store({text_obs: 上一观察, action: 执行的动作}) # 存入历史
|
v
build_text_obs(obs, infos) → 用模板组装下一步 prompt(含历史)
|
v
返回 (next_observations, rewards, dones, infos)
Prompt 模板
文件 :agent_system/environments/prompts/webshop.py
| 模板 | 使用时机 | 关键字段 |
|---|---|---|
WEBSHOP_TEMPLATE_NO_HIS |
首步 / 观察过长时回退 | task_description, current_observation, available_actions |
WEBSHOP_TEMPLATE |
后续步骤(有历史) | + step_count, action_history, current_step |
WEBSHOP_TEMPLATE_WITH_MEMORY |
启用技能检索时 | + retrieved_memories |
带历史的模板示例:
You are an expert autonomous agent operating in the WebShop e-commerce environment.
Your task is to: Find me a blue running shoe under $50.
## Retrieved Relevant Experience
### General Principles
- **Prioritize Core Keywords**: Include product type, 1-2 key functional attributes...
### Mistakes to Avoid
- **Don't**: Buying without verifying all constraints.
## Current Progress
Prior to this step, you have already taken 3 step(s).
Below are the most recent 4 observations and the corresponding actions you took:
[Observation 1: '...', Action 1: 'search[blue running shoe]']
[Observation 2: '...', Action 2: 'click[Nike Air Max 90]']
You are now at step 4 and your current observation is: '[WebShop page content]'.
Your admissible actions of the current situation are: [
search[<your query>]
click[Back to Search]
click[Buy Now]
].
Now it's your turn to take one action for the current step.
You should first reason step-by-step within <think ...> tags.
Once you've finished your reasoning, present your action within <action> </action> tags.
安全机制 :如果组装后的 prompt 超过 13000 字符,自动回退到 WEBSHOP_TEMPLATE_NO_HIS(丢弃历史)。
可用动作格式化
python
def format_avail_actions(self, avail):
actions = []
if avail["has_search_bar"]:
actions.append("search[<your query>]")
for txt in avail["clickables"]:
actions.append(f"click[{txt}]")
return actions
6. 奖励计算
6.1 环境级奖励(二值化)
文件 :agent_system/environments/env_package/webshop/envs.py:40-55
| 情况 | reward | 说明 |
|---|---|---|
| 成功购买正确商品 | 10.0 | done=True 且原始 reward=1.0 |
| 其他所有情况 | 0.0 | 错误商品、超时、导航错误等 |
原始 WebShop 的连续评分(属性匹配度)保存在 info['task_score'] 中,但不用于 RL 训练。
6.2 Episode 级奖励管理器
文件 :agent_system/reward_manager/episode.py
python
class EpisodeRewardManager:
def __call__(self, data_item):
episode_rewards = data_item.non_tensor_batch['episode_rewards']
episode_lengths = data_item.non_tensor_batch['episode_lengths']
# 默认不按长度归一化
score = episode_rewards # (batch_size,) 每个 episode 的总奖励
# 将奖励放置在 response 的最后一个有效 token 上(稀疏奖励)
reward_tensor[i, valid_response_length - 1] = score[i]
return reward_tensor
设计要点:
- 奖励是稀疏的------只有 episode 最后一个 token 有值
- 中间步骤全部为 0
- 对于 WebShop,episode_reward 只能是 0 或 10(单步成功即 10,否则累计为 0)
6.3 无效动作惩罚
文件 :verl/trainer/ppo/ray_trainer.py:200-224
当 use_invalid_action_penalty=True 时:
python
# 在 GRPO 优势计算之前应用
apply_invalid_action_penalty(batch, penalty_coef=0.1)
# → 对于 is_action_valid=0 的步骤,从 token_level_scores 中减去 penalty
7. GRPO 优势估计
文件:verl/trainer/ppo/core_algos.py:113-174
算法步骤
输入:
token_level_rewards: (bs, response_length) --- 每个 token 的奖励
response_mask: (bs, response_length) --- 有效 token 掩码
index: (bs,) --- 组标识 uid(同一任务的轨迹共享)
traj_index: (bs,) --- 轨迹标识 traj_uid(区分组内不同轨迹)
Step 1: 归约为 outcome 分数
scores = token_level_rewards.sum(dim=-1) # (bs,) 每条轨迹的总奖励
Step 2: 按 uid 分组
id2score[uid] = [score_1, score_2, ..., score_n] # 同一任务的 n 条轨迹
Step 3: 计算组统计量
group_mean = mean(id2score[uid])
group_std = std(id2score[uid])
# 单条轨迹的组:mean=0, std=1(避免除零)
Step 4: 标准化
advantage[i] = (scores[i] - group_mean[uid[i]]) / (group_std[uid[i]] + ε)
Step 5: 广播到 token 级别
advantages = advantage.unsqueeze(-1) * response_mask # (bs, response_length)
两种模式
| 模式 | 公式 | 配置 |
|---|---|---|
| 原始 GRPO | A = (R - mean) / std |
norm_adv_by_std_in_grpo=True (默认) |
| Dr.GRPO | A = R - mean (不除以 std) |
norm_adv_by_std_in_grpo=False |
WebShop 示例(8 条轨迹,1 条成功):
轨迹奖励: [0, 0, 0, 10, 0, 0, 0, 0]
mean = 10/8 = 1.25
std = 3.24
优势: [-0.39, -0.39, -0.39, 2.70, -0.39, -0.39, -0.39, -0.39]
↑ 抑制失败动作 ↑ 强化成功动作 ↑ 抑制失败动作
8. 策略更新 (Actor Update)
文件:verl/workers/actor/dp_actor.py:317-447
8.1 更新流程
对于每个 ppo_epoch (默认 1):
对于每个 mini_batch (64):
对于每个 micro_batch (每 GPU 4):
① _forward_micro_batch() → new_log_prob, [entropy]
② 计算 PPO clipped loss
③ 计算 KL loss (如启用)
④ 计算 entropy loss
⑤ loss.backward()
optimizer_step() → grad_clip + step()
8.2 损失函数
PPO Dual-Clip Policy Loss:
python
ratio = exp(new_log_prob - old_log_prob)
surr1 = -advantages * ratio
surr2 = -advantages * clip(ratio, 1-ε, 1+ε) # ε = 0.2
policy_loss = max(surr1, surr2)
# 下界约束 (当优势为负时)
policy_loss = min(policy_loss, -advantages * c) # c 默认为负的较大值
KL Loss (use_kl_loss=True 时):
python
# kl_loss_type = "low_var_kl"
kl = kl_penalty(new_log_prob, ref_log_prob, type="low_var_kl")
total_loss = policy_loss + kl_loss_coef * kl
Entropy Loss (entropy_coeff=0.001 时):
python
total_loss = total_loss - entropy_coeff * entropy_loss
8.3 完整损失
L = L_policy + 0.01 * L_kl - 0.001 * H
其中:
L_policy:PPO dual-clip 策略损失L_kl:与初始策略的 KL 散度(防止策略偏移过大)H:策略熵(防止策略坍塌)
9. 技能库系统
文件:agent_system/memory/skills_only_memory.py
9.1 技能库 JSON 结构
json
{
"general_skills": [
{
"skill_id": "gen_001",
"title": "Short Title",
"principle": "Core insight described in 1-2 sentences.",
"when_to_apply": "Trigger condition description."
}
],
"task_specific_skills": {
"apparel": [...],
"footwear": [...],
"electronics": [...],
"home_decor": [...],
"accessories": [...],
"beauty_health": [...],
"other": [...]
},
"common_mistakes": [
{
"mistake_id": "err_001",
"description": "What went wrong",
"why_it_happens": "Root cause",
"how_to_avoid": "How to prevent it"
}
]
}
9.2 任务类型检测
通过关键词匹配判断购物任务的类别:
| 类别 | 关键词 |
|---|---|
| apparel | shirt, dress, jacket, pant, coat, sweater, blouse, t-shirt |
| footwear | shoe, boot, sneaker, sandal, heel, slipper |
| electronics | laptop, phone, computer, tablet, charger, headphone, camera |
| accessories | necklace, ring, bracelet, earring, watch, jewelry, bag, wallet |
| home_decor | furniture, lamp, curtain, pillow, bedding, decor, vase, rug |
| beauty_health | cream, lotion, shampoo, makeup, vitamin, supplement |
| other | 以上均不匹配时的回退类别 |
9.3 Template 模式检索(默认)
python
def retrieve(self, task_description, top_k=6):
task_type = self._detect_task_type(task_description)
# 分离动态技能和静态技能
dynamic_skills = [s for s in general_skills if s['skill_id'].startswith('dyn_')]
static_skills = [s for s in general_skills if not s['skill_id'].startswith('dyn_')]
# 动态技能始终注入,静态技能填充剩余 top_k 配额
n_static = max(0, top_k - len(dynamic_skills))
result = dynamic_skills + static_skills[:n_static]
# 全量添加该类别的 task-specific 技能
result += task_specific_skills[task_type]
# 添加前 5 条常见错误
result += common_mistakes[:5]
return result
动态技能优先级 :dyn_ 前缀的技能(教师模型生成)始终被注入,不受 top_k 限制。
9.4 技能注入格式
### General Principles
- **Prioritize Core Keywords**: Include product type, 1-2 key functional attributes...
- **Verify Price Early**: Always check the product price before proceeding...
### Footwear Skills
- **Check Size Chart First**: Always verify size availability before selecting options...
_Apply when: The task involves footwear with specific size requirements_
### Mistakes to Avoid
- **Don't**: Repeating the same action after it fails.
**Instead**: Check the admissible actions list and try an alternative.
10. 动态技能更新(教师模型)
文件:agent_system/memory/skill_updater.py
10.1 触发条件
在每次验证(每 test_freq=5 个 epoch)后检查:
对于每个任务类别的 success_rate:
如果 ANY category_success_rate < update_threshold (0.4):
触发动态更新
10.2 执行流程
① 收集失败轨迹
- 从验证数据中筛选 score <= 0 的轨迹
- 提取任务描述、任务类型、对话步骤
- 限制最多 10 条失败轨迹
② 调用 Azure OpenAI o3 模型
- 发送 prompt 包含:失败轨迹示例 + 已有技能标题
- 要求生成 1-3 条新技能
- 返回 JSON 格式的技能列表
③ 添加到训练环境
- 新技能使用 dyn_NNN 编号
- 仅添加到训练环境(train_envs),不添加到验证环境(避免数据泄漏)
④ 保存到磁盘
- 路径: {default_local_dir}/updated_skills_step{N}.json
10.3 教师 Prompt 示例
Analyze these failed agent trajectories and suggest NEW skills.
FAILED TRAJECTORIES:
Example 1:
Task: Find me a blue running shoe under $50
Task Type: footwear
Trajectory (last 5 steps):
Action: search[blue running shoe]
Observation: [first 200 chars truncated...]
EXISTING SKILL TITLES (avoid duplicating):
- Prioritize Core Keywords
- Verify Price Early
- Check Size Chart First
...
Generate 1-3 NEW actionable skills. Each must have:
skill_id, title (3-5 words), principle (1-2 sentences), when_to_apply.
Use skill_ids: "dyn_001", "dyn_002", "dyn_003".
Return ONLY a JSON array.
10.4 动态技能的持久化
初始化时加载:
skills.json → SkillsOnlyMemory
↓
验证触发更新:
o3 生成新技能 → 添加 dyn_NNN → 仅训练环境
↓
保存到磁盘:
updated_skills_step5.json (step 5 验证后)
updated_skills_step10.json (step 10 验证后)
11. 验证流程与技能触发
文件:verl/trainer/ppo/ray_trainer.py:689-918
11.1 验证执行序列
_validate():
for test_batch in val_dataloader:
① 多轮 Rollout (is_train=False, val_envs)
- 使用验证环境(goal 索引 [0, 500))
- group_n=1(每个任务只跑 1 次)
② 评分 (val_reward_fn)
③ 聚合指标
- test_score: 平均 episode 奖励
- success_rate: 各类别成功率
- tool_call_count: 工具调用统计
④ 动态技能更新(如启用且满足阈值条件)
- _update_skills_from_validation()
11.2 成功率计算
文件 :agent_system/environments/env_manager.py:709-718
python
def _process_batch(self, ...):
# 找到最后一个活跃步骤
for i in reversed(range(len(batch_list))):
if active_masks[i]:
won = infos[i]['won']
score = infos[i]['task_score']
success['success_rate'].append(won)
success['webshop_task_score (not success_rate)'].append(score)
break
12. 端到端数据流汇总
单个训练 Step 的完整数据流
┌─────────────────────────────────────────────────────────────────────┐
│ 1. BATCH LOADING │
│ train_dataloader → batch_dict → gen_batch (16 条) │
│ gen_batch.repeat(n=8) → 128 条 (每任务 8 份) │
└──────────────────────────┬──────────────────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────────────────┐
│ 2. ENVIRONMENT RESET │
│ 128 个 WebShop Worker 各自 reset 到 16 个不同购物任务 │
│ 提取任务 → 检索技能 → 组装 prompt (WEBSHOP_TEMPLATE_WITH_MEMORY) │
└──────────────────────────┬──────────────────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────────────────┐
│ 3. MULTI-TURN LOOP (最多 15 步) │
│ │
│ ┌── Step N ────────────────────────────────────────────────────┐ │
│ │ a. obs → chat template → tokenize → input_ids │ │
│ │ b. vLLM generate → response tokens → decode → text_action │ │
│ │ c. webshop_projection → parse <action> → validate tags │ │
│ │ d. env.step(action) → next_obs, reward(0/10), done, info │ │
│ │ e. 存储历史 → 组装下一步 prompt │ │
│ │ f. done? → is_done[i] = True → 该 worker 不再参与 │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
│ 输出: 128 条轨迹,每条包含最多 15 个 step 的数据 │
└──────────────────────────┬──────────────────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────────────────┐
│ 4. REWARD COMPUTATION │
│ EpisodeRewardManager: │
│ episode_reward ∈ {0, 10} 放在最后有效 token 上 │
│ 无效动作惩罚: token_score -= 0.1 * (1 - is_action_valid) │
└──────────────────────────┬──────────────────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────────────────┐
│ 5. GRPO ADVANTAGE │
│ 按 uid 分组 (同任务 8 条轨迹): │
│ A[i] = (R[i] - mean(R_group)) / std(R_group) │
│ 成功轨迹 advantage > 0 → 强化 │
│ 失败轨迹 advantage < 0 → 抑制 │
└──────────────────────────┬──────────────────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────────────────┐
│ 6. ACTOR UPDATE │
│ L = L_ppo_clip + 0.01 * L_kl(ref_policy) - 0.001 * H(entropy) │
│ FSDP 分布式更新 → 梯度裁剪 → 优化器 step │
└──────────────────────────┬──────────────────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────────────────┐
│ 7. VALIDATION (每 5 个 step) │
│ 64 个验证 Worker,每任务 1 次探索 │
│ 计算各类别 success_rate │
│ 如果 success_rate < 0.4: │
│ 收集失败轨迹 → Azure o3 分析 → 生成新技能 → 注入训练环境 │
│ 保存 updated_skills_step{N}.json │
└─────────────────────────────────────────────────────────────────────┘
轨迹数据结构
每个 step 的单条数据 (DataProto 中的一个元素):
| 字段 | 类型 | 说明 |
|---|---|---|
input_ids |
Tensor | prompt 的 token IDs |
responses |
Tensor | 模型生成的 action token IDs |
rollout_log_probs |
Tensor | 生成时每个 token 的 log 概率 |
attention_mask |
Tensor | 注意力掩码 |
position_ids |
Tensor | 位置编码 |
episode_rewards |
scalar | 该 episode 的总奖励 (0 或 10) |
episode_lengths |
scalar | 该 episode 的实际步数 |
uid |
str | GRPO 组标识(同任务共享) |
traj_uid |
str | 轨迹唯一标识(组内区分) |
is_action_valid |
int | 动作格式是否合法 (0/1) |
rewards |
scalar | 该步的环境奖励 |
active_masks |
bool | 该步是否还在活跃状态 |
关键文件速查
| 模块 | 文件 | 核心内容 |
|---|---|---|
| 训练入口 | verl/trainer/main_ppo.py |
Hydra 启动、环境/Worker 创建 |
| 训练循环 | verl/trainer/ppo/ray_trainer.py |
fit(), _validate(), GRPO 分发 |
| GRPO 算法 | verl/trainer/ppo/core_algos.py |
compute_grpo_outcome_advantage() |
| 策略更新 | verl/workers/actor/dp_actor.py |
update_policy(), PPO loss |
| 模型推理 | verl/workers/rollout/vllm_rollout/vllm_rollout.py |
generate_sequences() |
| 多轮循环 | agent_system/multi_turn_rollout/rollout_loop.py |
vanilla_multi_turn_loop() |
| 环境管理 | agent_system/environments/env_manager.py |
WebshopEnvironmentManager |
| WebShop 环境 | agent_system/environments/env_package/webshop/envs.py |
WebshopWorker, WebshopMultiProcessEnv |
| 动作解析 | agent_system/environments/env_package/webshop/projection.py |
webshop_projection() |
| Prompt 模板 | agent_system/environments/prompts/webshop.py |
三套 prompt 模板 |
| 奖励管理 | agent_system/reward_manager/episode.py |
EpisodeRewardManager |
| 技能检索 | agent_system/memory/skills_only_memory.py |
SkillsOnlyMemory, 任务分类, 模板检索 |
| 动态更新 | agent_system/memory/skill_updater.py |
SkillUpdater, Azure o3 调用 |
| 动作历史 | agent_system/memory/memory.py |
SimpleMemory |
| 默认配置 | verl/trainer/config/ppo_trainer.yaml |
所有超参数默认值 |
| WebShop 脚本 | examples/grpo_trainer/run_webshop_skills.sh |
完整启动命令 |