85742ab165
Deploy Documentation / deploy (push) Has been cancelled
CPU Test / Test (Utilities, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (LLM proxy, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (Others, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (Store, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (Utilities, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (Weave, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (AgentOps, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (LLM proxy, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (Others, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (Weave, latest, Python 3.13) (push) Has been cancelled
Dashboard / Chromatic (push) Has been cancelled
CPU Test / Lint - fast (push) Has been cancelled
CPU Test / Lint - next (push) Has been cancelled
CPU Test / Lint - slow (push) Has been cancelled
CPU Test / Lint - JavaScript (push) Has been cancelled
CPU Test / Build documentation (push) Has been cancelled
CPU Test / Test (AgentOps, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (LLM proxy, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (Others, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (Store, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (Weave, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (AgentOps, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (Store, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (Utilities, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (Weave, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (AgentOps, latest, Python 3.13) (push) Has been cancelled
CPU Test / Test (LLM proxy, latest, Python 3.13) (push) Has been cancelled
CPU Test / Test (Others, latest, Python 3.13) (push) Has been cancelled
CPU Test / Test (Store, latest, Python 3.13) (push) Has been cancelled
CPU Test / Test (Utilities, latest, Python 3.13) (push) Has been cancelled
CPU Test / Test (JavaScript) (push) Has been cancelled
550 lines
25 KiB
Python
550 lines
25 KiB
Python
# Copyright (c) Microsoft. All rights reserved.
|
||
|
||
# type: ignore
|
||
|
||
from __future__ import annotations
|
||
|
||
import random
|
||
from contextlib import contextmanager
|
||
from copy import deepcopy
|
||
from pprint import pprint
|
||
from typing import Dict, Tuple, Type
|
||
|
||
import numpy as np
|
||
import torch
|
||
import verl
|
||
from codetiming import Timer
|
||
from omegaconf import OmegaConf
|
||
from tqdm import tqdm
|
||
from verl import DataProto
|
||
from verl.protocol import pad_dataproto_to_divisor, unpad_dataproto
|
||
from verl.trainer.ppo.core_algos import agg_loss
|
||
from verl.trainer.ppo.metric_utils import (
|
||
_compute_response_info,
|
||
compute_throughout_metrics,
|
||
compute_timing_metrics,
|
||
)
|
||
from verl.trainer.ppo.ray_trainer import (
|
||
AdvantageEstimator,
|
||
RayPPOTrainer,
|
||
apply_kl_penalty,
|
||
compute_advantage,
|
||
compute_response_mask,
|
||
)
|
||
from verl.utils.metric import reduce_metrics
|
||
from verl.utils.tracking import Tracking
|
||
|
||
from agentlightning.adapter import TraceAdapter, TraceToTripletBase
|
||
from agentlightning.llm_proxy import LLMProxy
|
||
from agentlightning.store.base import LightningStore
|
||
|
||
from .daemon import AgentModeDaemon
|
||
|
||
__all__ = [
|
||
"AgentLightningTrainer",
|
||
]
|
||
|
||
|
||
@contextmanager
|
||
def _timer(name: str, timing_raw: Dict[str, float]):
|
||
with Timer(name=name, logger=None) as timer:
|
||
yield
|
||
if name not in timing_raw:
|
||
timing_raw[name] = 0
|
||
timing_raw[name] += timer.last
|
||
|
||
|
||
# This function is adapted from verl.
|
||
# We introduce a new parameter `suffix` to distinguish between metrics computed
|
||
# before and after AgentLightning’s post-processing.
|
||
# - "Before" refers to raw reward and advantage values.
|
||
# - "After" refers to values computed following post-processing, which involves:
|
||
# (1) Dropping prompts that exceed the maximum allowed length.
|
||
# (2) Adjusting the batch size to be a multiple of the mini PPO size.
|
||
# Different suffixes are used to label these two stages accordingly.
|
||
def compute_data_metrics(batch: DataProto, use_critic: bool = True, suffix: str = "") -> Dict[str, Any]:
|
||
"""
|
||
Computes various metrics from a batch of data for PPO training.
|
||
|
||
This function calculates metrics related to scores, rewards, advantages, returns, values,
|
||
and sequence lengths from a batch of data. It provides statistical information (mean, max, min)
|
||
for each metric category.
|
||
|
||
Args:
|
||
batch: A DataProto object containing batch data with token-level scores, rewards, advantages, etc.
|
||
use_critic: Whether to include critic-specific metrics. Defaults to True.
|
||
|
||
Returns:
|
||
A dictionary of metrics including:
|
||
- critic/score/mean, max, min: Statistics about sequence scores
|
||
- critic/rewards/mean, max, min: Statistics about sequence rewards
|
||
- critic/advantages/mean, max, min: Statistics about advantages
|
||
- critic/returns/mean, max, min: Statistics about returns
|
||
- critic/values/mean, max, min: Statistics about critic values (if use_critic=True)
|
||
- critic/vf_explained_var: Explained variance of the value function (if use_critic=True)
|
||
- response_length/mean, max, min, clip_ratio: Statistics about response lengths
|
||
- prompt_length/mean, max, min, clip_ratio: Statistics about prompt lengths
|
||
"""
|
||
sequence_score = batch.batch["token_level_scores"].sum(-1)
|
||
sequence_reward = batch.batch["token_level_rewards"].sum(-1)
|
||
|
||
advantages = batch.batch["advantages"]
|
||
returns = batch.batch["returns"]
|
||
|
||
max_response_length = batch.batch["responses"].shape[-1]
|
||
|
||
prompt_mask = batch.batch["attention_mask"][:, :-max_response_length].bool()
|
||
response_mask = batch.batch["attention_mask"][:, -max_response_length:].bool()
|
||
|
||
max_prompt_length = prompt_mask.size(-1)
|
||
|
||
response_info = _compute_response_info(batch)
|
||
prompt_length = response_info["prompt_length"]
|
||
response_length = response_info["response_length"]
|
||
|
||
valid_adv = torch.masked_select(advantages, response_mask)
|
||
valid_returns = torch.masked_select(returns, response_mask)
|
||
|
||
if use_critic:
|
||
values = batch.batch["values"]
|
||
valid_values = torch.masked_select(values, response_mask)
|
||
return_diff_var = torch.var(valid_returns - valid_values)
|
||
return_var = torch.var(valid_returns)
|
||
|
||
metrics = {
|
||
# score
|
||
"critic/score/mean" + suffix: torch.mean(sequence_score).detach().item(),
|
||
"critic/score/max" + suffix: torch.max(sequence_score).detach().item(),
|
||
"critic/score/min" + suffix: torch.min(sequence_score).detach().item(),
|
||
# reward
|
||
"critic/rewards/mean" + suffix: torch.mean(sequence_reward).detach().item(),
|
||
"critic/rewards/max" + suffix: torch.max(sequence_reward).detach().item(),
|
||
"critic/rewards/min" + suffix: torch.min(sequence_reward).detach().item(),
|
||
# adv
|
||
"critic/advantages/mean" + suffix: torch.mean(valid_adv).detach().item(),
|
||
"critic/advantages/max" + suffix: torch.max(valid_adv).detach().item(),
|
||
"critic/advantages/min" + suffix: torch.min(valid_adv).detach().item(),
|
||
# returns
|
||
"critic/returns/mean" + suffix: torch.mean(valid_returns).detach().item(),
|
||
"critic/returns/max" + suffix: torch.max(valid_returns).detach().item(),
|
||
"critic/returns/min" + suffix: torch.min(valid_returns).detach().item(),
|
||
**(
|
||
{
|
||
# values
|
||
"critic/values/mean" + suffix: torch.mean(valid_values).detach().item(),
|
||
"critic/values/max" + suffix: torch.max(valid_values).detach().item(),
|
||
"critic/values/min" + suffix: torch.min(valid_values).detach().item(),
|
||
# vf explained var
|
||
"critic/vf_explained_var" + suffix: (1.0 - return_diff_var / (return_var + 1e-5)).detach().item(),
|
||
}
|
||
if use_critic
|
||
else {}
|
||
),
|
||
# response length
|
||
"response_length/mean" + suffix: torch.mean(response_length).detach().item(),
|
||
"response_length/max" + suffix: torch.max(response_length).detach().item(),
|
||
"response_length/min" + suffix: torch.min(response_length).detach().item(),
|
||
"response_length/clip_ratio"
|
||
+ suffix: torch.mean(torch.eq(response_length, max_response_length).float()).detach().item(),
|
||
# prompt length
|
||
"prompt_length/mean" + suffix: torch.mean(prompt_length).detach().item(),
|
||
"prompt_length/max" + suffix: torch.max(prompt_length).detach().item(),
|
||
"prompt_length/min" + suffix: torch.min(prompt_length).detach().item(),
|
||
"prompt_length/clip_ratio"
|
||
+ suffix: torch.mean(torch.eq(prompt_length, max_prompt_length).float()).detach().item(),
|
||
}
|
||
return metrics
|
||
|
||
|
||
class AgentLightningTrainer(RayPPOTrainer):
|
||
"""
|
||
Specialized PPO trainer for agent-based reinforcement learning.
|
||
|
||
This trainer is designed specifically for scenarios where the model interacts with
|
||
external environments, tools, or APIs through an AgentLightningServer. It simplifies
|
||
the training loop by removing the complex conditional logic present in the original
|
||
RayPPOTrainer and focusing on the agent mode workflow.
|
||
|
||
Key differences from RayPPOTrainer:
|
||
|
||
1. Uses AgentModeDaemon for server communication
|
||
2. Simplified data flow without pop/union operations
|
||
3. Direct batch processing through agent daemon
|
||
4. Streamlined validation using agent_mode validation
|
||
"""
|
||
|
||
def __init__(
|
||
self,
|
||
store: LightningStore | None,
|
||
llm_proxy: LLMProxy | None,
|
||
adapter: TraceAdapter | None,
|
||
daemon_cls: Type[AgentModeDaemon],
|
||
**kwargs,
|
||
):
|
||
super().__init__(**kwargs)
|
||
self.store = store
|
||
self.llm_proxy = llm_proxy
|
||
self.adapter = adapter
|
||
self.daemon_cls = daemon_cls
|
||
|
||
def _validate(self):
|
||
assert len(self.val_dataloader) == 1, "Please set val_batch_size to None for better throughput."
|
||
|
||
test_data = next(iter(self.val_dataloader))
|
||
test_batch = DataProto.from_single_dict(test_data)
|
||
|
||
self.async_rollout_manager.wake_up()
|
||
self.agent_mode_daemon.set_up_data_and_server(
|
||
test_batch.non_tensor_batch,
|
||
self.async_rollout_manager.server_addresses,
|
||
is_train=False,
|
||
)
|
||
self.agent_mode_daemon.run_until_all_finished()
|
||
test_metrics = self.agent_mode_daemon.get_test_metrics()
|
||
self.agent_mode_daemon.clear_data_and_server()
|
||
self.async_rollout_manager.sleep()
|
||
return test_metrics
|
||
|
||
def _compute_reference_log_prob(self, batch: DataProto) -> DataProto:
|
||
"""Compute reference log probability using the correct worker based on LoRA configuration.
|
||
|
||
In verl 0.6.0+, when LoRA is detected (indicated by ref_in_actor=True),
|
||
the reference policy is computed by the actor rollout worker instead of a separate
|
||
ref policy worker. This method handles both scenarios by checking the ref_in_actor flag.
|
||
Note: verl sets ref_in_actor=True when it detects LoRA configuration (e.g., lora_rank > 0 or lora_adapter_path is set).
|
||
|
||
Args:
|
||
batch: The data batch to compute reference log probabilities for.
|
||
|
||
Returns:
|
||
DataProto with reference log probabilities added.
|
||
|
||
Raises:
|
||
RuntimeError: If the required worker is not available.
|
||
"""
|
||
if getattr(self, "ref_in_actor", False):
|
||
actor_worker = getattr(self, "actor_rollout_wg", None)
|
||
if actor_worker is None:
|
||
raise RuntimeError("actor_rollout_wg is required when ref_in_actor is True.")
|
||
return actor_worker.compute_ref_log_prob(batch)
|
||
|
||
ref_worker = getattr(self, "ref_policy_wg", None)
|
||
if ref_worker is None:
|
||
raise RuntimeError(
|
||
"Reference policy worker was not initialized. "
|
||
"Ensure `use_reference_policy` is enabled and the VERL config exposes the ref worker."
|
||
)
|
||
return ref_worker.compute_ref_log_prob(batch)
|
||
|
||
def _train_step(self, batch_dict: dict) -> dict:
|
||
# Isolate in a separate method to automatically recycle the variables before validation.
|
||
batch: DataProto = DataProto.from_single_dict(batch_dict)
|
||
metrics = {}
|
||
timing_raw = {}
|
||
|
||
with _timer("step", timing_raw):
|
||
|
||
# When agent mode is enabled, we read the batch as it is.
|
||
gen_batch = batch
|
||
|
||
# generate a batch
|
||
with _timer("gen", timing_raw):
|
||
self.async_rollout_manager.wake_up()
|
||
self.agent_mode_daemon.set_up_data_and_server(
|
||
gen_batch.non_tensor_batch, self.async_rollout_manager.server_addresses
|
||
)
|
||
self.agent_mode_daemon.run_until_all_finished()
|
||
batch, agent_metrics = self.agent_mode_daemon.get_train_data_batch(
|
||
max_prompt_length=(
|
||
self.config.agentlightning.trace_aggregator.trajectory_max_prompt_length
|
||
if self.config.agentlightning.trace_aggregator.level.startswith("trajectory")
|
||
else self.config.data.max_prompt_length
|
||
),
|
||
max_response_length=(
|
||
self.config.agentlightning.trace_aggregator.trajectory_max_response_length
|
||
if self.config.agentlightning.trace_aggregator.level.startswith("trajectory")
|
||
else self.config.data.max_response_length
|
||
),
|
||
device=gen_batch.batch["fake_ids"].device,
|
||
global_steps=self.global_steps,
|
||
)
|
||
metrics.update(agent_metrics)
|
||
self.agent_mode_daemon.clear_data_and_server()
|
||
self.async_rollout_manager.sleep()
|
||
|
||
if self.config.algorithm.adv_estimator == AdvantageEstimator.REMAX:
|
||
with _timer("gen_max", timing_raw):
|
||
gen_baseline_batch = deepcopy(gen_batch)
|
||
gen_baseline_batch.meta_info["do_sample"] = False
|
||
gen_baseline_output = self.async_rollout_manager.generate_sequences(gen_baseline_batch)
|
||
|
||
batch = batch.union(gen_baseline_output)
|
||
reward_baseline_tensor = self.reward_fn(batch)
|
||
reward_baseline_tensor = reward_baseline_tensor.sum(dim=-1)
|
||
|
||
batch.pop(batch_keys=list(gen_baseline_output.batch.keys()))
|
||
|
||
batch.batch["reward_baselines"] = reward_baseline_tensor
|
||
|
||
del gen_baseline_batch, gen_baseline_output
|
||
|
||
# uid is used for algorithm like GRPO, should be aligned to data id
|
||
batch.non_tensor_batch["uid"] = batch.non_tensor_batch["data_id_list"]
|
||
|
||
if "response_mask" not in batch.batch:
|
||
batch.batch["response_mask"] = compute_response_mask(batch)
|
||
|
||
# compute global_valid tokens
|
||
batch.meta_info["global_token_num"] = torch.sum(batch.batch["attention_mask"], dim=-1).tolist()
|
||
|
||
with _timer("reward", timing_raw):
|
||
# compute reward model score
|
||
if self.use_rm:
|
||
reward_tensor = self.rm_wg.compute_rm_score(batch)
|
||
batch = batch.union(reward_tensor)
|
||
|
||
reward_extra_infos_dict = {}
|
||
|
||
# for agent mode, pad the lengths to calculate old log prob, ref, and values
|
||
batch, pad_size = pad_dataproto_to_divisor(batch, self.actor_rollout_wg.world_size)
|
||
|
||
# recompute old_log_probs
|
||
with _timer("old_log_prob", timing_raw):
|
||
old_log_prob = self.actor_rollout_wg.compute_log_prob(batch)
|
||
entropys = old_log_prob.batch["entropys"]
|
||
response_masks = batch.batch["response_mask"]
|
||
loss_agg_mode = self.config.actor_rollout_ref.actor.loss_agg_mode
|
||
entropy_loss = agg_loss(loss_mat=entropys, loss_mask=response_masks, loss_agg_mode=loss_agg_mode)
|
||
old_log_prob_metrics = {"actor/entropy_loss": entropy_loss.detach().item()}
|
||
metrics.update(old_log_prob_metrics)
|
||
old_log_prob.batch.pop("entropys")
|
||
batch = batch.union(old_log_prob)
|
||
|
||
if self.use_reference_policy:
|
||
# compute reference log_prob
|
||
with _timer("ref", timing_raw):
|
||
ref_log_prob = self._compute_reference_log_prob(batch)
|
||
batch = batch.union(ref_log_prob)
|
||
|
||
# compute values
|
||
if self.use_critic:
|
||
with _timer("values", timing_raw):
|
||
values = self.critic_wg.compute_values(batch)
|
||
batch = batch.union(values)
|
||
|
||
# for agent mode, unpad to calculate adv
|
||
# it is important, as adv should be based on the raw traces
|
||
batch = unpad_dataproto(batch, pad_size=pad_size)
|
||
|
||
with _timer("adv", timing_raw):
|
||
# if agent_mode is enabled, there is already token_level_scores
|
||
# token_level_scores is not needed to compute here
|
||
|
||
# compute rewards. apply_kl_penalty if available
|
||
if self.config.algorithm.use_kl_in_reward:
|
||
batch, kl_metrics = apply_kl_penalty(
|
||
batch, kl_ctrl=self.kl_ctrl_in_reward, kl_penalty=self.config.algorithm.kl_penalty
|
||
)
|
||
metrics.update(kl_metrics)
|
||
else:
|
||
batch.batch["token_level_rewards"] = batch.batch["token_level_scores"]
|
||
|
||
# compute advantages, executed on the driver process
|
||
|
||
norm_adv_by_std_in_grpo = self.config.algorithm.get(
|
||
"norm_adv_by_std_in_grpo", True
|
||
) # GRPO adv normalization factor
|
||
|
||
batch = compute_advantage(
|
||
batch,
|
||
adv_estimator=self.config.algorithm.adv_estimator,
|
||
gamma=self.config.algorithm.gamma,
|
||
lam=self.config.algorithm.lam,
|
||
num_repeat=self.config.actor_rollout_ref.rollout.n,
|
||
norm_adv_by_std_in_grpo=norm_adv_by_std_in_grpo,
|
||
config=self.config.algorithm,
|
||
)
|
||
|
||
# Calculate the metrics before processing. Refer to the comments of function `compute_data_metrics` for details.
|
||
metrics.update(compute_data_metrics(batch=batch, use_critic=self.use_critic, suffix="_before_processing"))
|
||
|
||
# after advantages are assigned, we begin to drop (1) long prompt (2) floor to ppo minisize
|
||
keep_indices = (~batch.batch["is_drop_mask"]).nonzero(as_tuple=True)[0]
|
||
metrics["training/n_triplets_prompt_too_long"] = (
|
||
batch.batch["is_drop_mask"].shape[0] - keep_indices.shape[0]
|
||
)
|
||
batch = batch[keep_indices]
|
||
# next, round to minibatch size
|
||
mini_batch_size = self.config.actor_rollout_ref.actor.ppo_mini_batch_size
|
||
n_transition = len(batch)
|
||
random_indices = list(range(n_transition))
|
||
random.shuffle(random_indices)
|
||
batch.reorder(torch.tensor(random_indices).type(torch.int32))
|
||
n_remained_transition = n_transition // mini_batch_size * mini_batch_size
|
||
batch = batch[list(range(n_remained_transition))]
|
||
metrics["training/n_triplets_dropped_remainder"] = n_transition - n_remained_transition
|
||
|
||
# Agent mode note: Change the order of balance batch;
|
||
# 1. first calculate advantage
|
||
# 2. then drop the samples (too long prompt & floor to ppo minisize)
|
||
# 3. balance
|
||
# balance the number of valid tokens on each dp rank.
|
||
# Note that this breaks the order of data inside the batch.
|
||
# Please take care when you implement group based adv computation such as GRPO and rloo
|
||
if self.config.trainer.balance_batch:
|
||
self._balance_batch(batch, metrics=metrics)
|
||
|
||
# update critic
|
||
if self.use_critic:
|
||
with _timer("update_critic", timing_raw):
|
||
critic_output = self.critic_wg.update_critic(batch)
|
||
critic_output_metrics = reduce_metrics(critic_output.meta_info["metrics"])
|
||
metrics.update(critic_output_metrics)
|
||
|
||
# implement critic warmup
|
||
if self.config.trainer.critic_warmup <= self.global_steps:
|
||
# update actor
|
||
with _timer("update_actor", timing_raw):
|
||
batch.meta_info["multi_turn"] = self.config.actor_rollout_ref.rollout.multi_turn.enable
|
||
actor_output = self.actor_rollout_wg.update_actor(batch)
|
||
actor_output_metrics = reduce_metrics(actor_output.meta_info["metrics"])
|
||
metrics.update(actor_output_metrics)
|
||
|
||
# Log rollout generations if enabled
|
||
rollout_data_dir = self.config.trainer.get("rollout_data_dir", None)
|
||
if rollout_data_dir:
|
||
with _timer("dump_rollout_generations", timing_raw):
|
||
print(batch.batch.keys())
|
||
inputs = self.tokenizer.batch_decode(batch.batch["prompts"], skip_special_tokens=True)
|
||
outputs = self.tokenizer.batch_decode(batch.batch["responses"], skip_special_tokens=True)
|
||
scores = batch.batch["token_level_scores"].sum(-1).cpu().tolist()
|
||
self._dump_generations(
|
||
inputs=inputs,
|
||
outputs=outputs,
|
||
scores=scores,
|
||
reward_extra_infos_dict=reward_extra_infos_dict,
|
||
dump_path=rollout_data_dir,
|
||
)
|
||
|
||
# compute training metrics
|
||
metrics.update(compute_data_metrics(batch=batch, use_critic=self.use_critic, suffix="_after_processing"))
|
||
metrics.update(compute_timing_metrics(batch=batch, timing_raw=timing_raw))
|
||
# TODO: implement actual tflpo and theoretical tflpo
|
||
n_gpus = self.resource_pool_manager.get_n_gpus()
|
||
metrics.update(compute_throughout_metrics(batch=batch, timing_raw=timing_raw, n_gpus=n_gpus))
|
||
|
||
return metrics
|
||
|
||
def fit(self):
|
||
logger = Tracking(
|
||
project_name=self.config.trainer.project_name,
|
||
experiment_name=self.config.trainer.experiment_name,
|
||
default_backend=self.config.trainer.logger,
|
||
config=OmegaConf.to_container(self.config, resolve=True),
|
||
)
|
||
|
||
self.global_steps = 0
|
||
|
||
# load checkpoint before doing anything
|
||
self._load_checkpoint()
|
||
|
||
assert self.async_rollout_mode, "If agent mode is enabled, async server must be enabled"
|
||
if self.adapter is not None and not isinstance(self.adapter, TraceToTripletBase):
|
||
raise ValueError("Adapter must be a TraceToTripletBase for currently VERL implementation.")
|
||
verl_version = verl.__version__
|
||
if verl_version == "0.5.0":
|
||
# Note (Zhiyuan): To avoid further patch into vllm async server, using the same sentence to get the naming here.
|
||
# However, it is possible that verl updates the naming and causes incompatibility.
|
||
# Reference: https://github.com/volcengine/verl/blob/5b5e09d9cc20625e436d01f69d9cc739ff681c54/verl/workers/rollout/vllm_rollout/vllm_async_server.py#L217
|
||
model = "/".join(self.config.actor_rollout_ref.model.path.split("/")[-2:])
|
||
else:
|
||
# For other versions (e.g., 0.6.0), we use the full path to the model.
|
||
model = self.config.actor_rollout_ref.model.path
|
||
self.agent_mode_daemon = self.daemon_cls(
|
||
self.config.agentlightning.port,
|
||
self.config.actor_rollout_ref.rollout.n,
|
||
train_information={
|
||
"model": model,
|
||
"temperature": self.config.actor_rollout_ref.rollout.temperature,
|
||
},
|
||
tokenizer=self.tokenizer,
|
||
mini_batch_size=self.config.actor_rollout_ref.actor.ppo_mini_batch_size,
|
||
pad_token_id=self.tokenizer.pad_token_id,
|
||
mode="v1" if self.store is not None else "v0",
|
||
store=self.store,
|
||
llm_proxy=self.llm_proxy,
|
||
adapter=self.adapter,
|
||
processor=self.processor, # For Qwen2-VL mrope position_ids
|
||
image_base_dir=getattr(self.config.data, "image_base_dir", None),
|
||
trace_aggregator=self.config.agentlightning.trace_aggregator,
|
||
)
|
||
self.agent_mode_daemon.start()
|
||
|
||
# perform validation before training
|
||
# currently, we only support validation using the reward_function.
|
||
if self.val_reward_fn is not None and self.config.trainer.get("val_before_train", True):
|
||
val_metrics = self._validate()
|
||
assert val_metrics, f"{val_metrics=}"
|
||
pprint(f"Initial validation metrics: {val_metrics}")
|
||
logger.log(data=val_metrics, step=self.global_steps)
|
||
if self.config.trainer.get("val_only", False):
|
||
return
|
||
|
||
# add tqdm
|
||
progress_bar = tqdm(total=self.total_training_steps, initial=self.global_steps, desc="Training Progress")
|
||
|
||
# we start from step 1
|
||
self.global_steps += 1
|
||
last_val_metrics = None
|
||
|
||
for epoch in range(self.config.trainer.total_epochs):
|
||
for batch_dict in self.train_dataloader:
|
||
metrics = {}
|
||
timing_raw = {}
|
||
is_last_step = self.global_steps >= self.total_training_steps
|
||
|
||
# train step
|
||
metrics = self._train_step(batch_dict)
|
||
|
||
# validate
|
||
if (
|
||
self.val_reward_fn is not None
|
||
and self.config.trainer.test_freq > 0
|
||
and (is_last_step or self.global_steps % self.config.trainer.test_freq == 0)
|
||
):
|
||
with _timer("validate", timing_raw):
|
||
val_metrics: dict = self._validate()
|
||
if is_last_step:
|
||
last_val_metrics = val_metrics
|
||
metrics.update(val_metrics)
|
||
|
||
if self.config.trainer.save_freq > 0 and (
|
||
is_last_step or self.global_steps % self.config.trainer.save_freq == 0
|
||
):
|
||
with _timer("save_checkpoint", timing_raw):
|
||
self._save_checkpoint()
|
||
|
||
# step metrics
|
||
metrics.update(
|
||
{
|
||
"training/global_step": self.global_steps,
|
||
"training/epoch": epoch,
|
||
}
|
||
)
|
||
|
||
# TODO: make a canonical logger that supports various backend
|
||
logger.log(data=metrics, step=self.global_steps)
|
||
|
||
if is_last_step:
|
||
pprint(f"Final validation metrics: {last_val_metrics}")
|
||
progress_bar.close()
|
||
|
||
# This exit logic is to ensure a robust CI.
|
||
pprint(f"Flush the logger...")
|
||
del logger # Make sure the loggers are flushed and closed properly
|
||
pprint(f"Training finished at step {self.global_steps}.")
|
||
return
|
||
|
||
progress_bar.update(1)
|
||
self.global_steps += 1
|