Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ def _resolve_runner(pipeline: Any) -> Any:
return pipeline


def _drop_failed_train_samples(samples: list[RolloutState], mode: Literal["train", "eval"]) -> list[RolloutState]:
if mode != "train":
return samples
filtered = [sample for sample in samples if sample.status != Status.FAILED]
return filtered or samples


def _is_trace_key_mismatch(exc: Exception) -> bool:
return "does not match any trace key" in str(exc)

Expand Down Expand Up @@ -139,7 +146,8 @@ async def generate_one(state: RolloutState) -> RolloutState:
task = create_task(generate_one(state))
tasks.append(task)

return await asyncio.gather(*tasks)
samples = await asyncio.gather(*tasks)
return _drop_failed_train_samples(samples, self.mode)

async def generate_sample(self, rollout_state: RolloutState, **kwargs) -> RolloutState:
try:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@ def _resolve_runner(pipeline: Any, session_id: str) -> Any:
return pipeline


def _drop_failed_train_samples(samples: list[RolloutState], mode: Literal["train", "eval"]) -> list[RolloutState]:
if mode != "train":
return samples
filtered = [sample for sample in samples if sample.status != Status.FAILED]
return filtered or samples


def _validate_trace_segment(
segment: dict[str, Any],
*,
Expand All @@ -66,6 +73,13 @@ def _validate_trace_segment(
return messages, None if tools is _MISSING else tools


def _trim_to_last_assistant_turn(messages: list[dict[str, Any]]) -> list[dict[str, Any]]:
for index in range(len(messages) - 1, -1, -1):
if messages[index].get("role") == "assistant":
return messages[: index + 1]
return []


def _load_train_trace_segments(artifacts: dict[str, Any]) -> list[tuple[list[dict[str, Any]], Any]]:
trace = _load_messages_artifact(artifacts)
if not trace:
Expand All @@ -74,7 +88,10 @@ def _load_train_trace_segments(artifacts: dict[str, Any]) -> list[tuple[list[dic
for segment in trace:
if not isinstance(segment, dict):
raise TypeError("Agent messages trace segment must be a dict.")
segments.append(_validate_trace_segment(segment, require_tools=True))
messages, tools = _validate_trace_segment(segment, require_tools=True)
messages = _trim_to_last_assistant_turn(messages)
if messages:
segments.append((messages, tools))
return segments


Expand Down Expand Up @@ -225,7 +242,8 @@ async def generate_one(state: RolloutState) -> list[RolloutState]:
pending_tasks.append(task)
generated_samples = asyncio.gather(*pending_tasks)
sample_groups = await generated_samples
return [sample for sample_group in sample_groups for sample in sample_group]
samples = [sample for sample_group in sample_groups for sample in sample_group]
return _drop_failed_train_samples(samples, self.mode)

# NOTE: A single sandbox session may yield multiple trainable segments, so this returns a list
# rather than the base class's single RolloutState. The base contract is never exercised for
Expand Down
36 changes: 27 additions & 9 deletions xtuner/v1/rl/agent_loop/sandbox_agent_loop/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,24 +214,42 @@ def __init__(
errors: str = "replace",
parse_json: bool = False,
optional: bool = False,
attempts: int = 1,
retry_interval_sec: float = 1.0,
download_timeout_sec: float | None = None,
):
self.path = path
self.key = key
self.encoding = encoding
self.errors = errors
self.parse_json = parse_json
self.optional = optional
self.attempts = max(1, int(attempts))
self.retry_interval_sec = max(0.0, retry_interval_sec)
self.download_timeout_sec = download_timeout_sec

async def __call__(self, client: Any, item: AgentRolloutItem, record: StageRecord) -> None:
try:
blob = await client.download_file(self.path)
text = blob.decode(self.encoding, errors=self.errors)
item.artifacts[self.key] = json.loads(text) if self.parse_json else text
except Exception as exc:
if self.optional:
get_logger().warning("read_file %s (key=%r) failed: %s", self.path, self.key, exc)
else:
raise
last_exc: Exception | None = None
for attempt in range(1, self.attempts + 1):
try:
download = client.download_file(self.path)
blob = (
await asyncio.wait_for(download, timeout=self.download_timeout_sec)
if self.download_timeout_sec is not None
else await download
)
text = blob.decode(self.encoding, errors=self.errors)
item.artifacts[self.key] = json.loads(text) if self.parse_json else text
return
except Exception as exc:
last_exc = exc
if attempt < self.attempts and self.retry_interval_sec > 0:
await asyncio.sleep(self.retry_interval_sec)
assert last_exc is not None
if self.optional:
get_logger().warning("read_file %s (key=%r) failed: %s", self.path, self.key, last_exc)
else:
raise last_exc


# ─────────────────────────────────────────────────────────────────
Expand Down
2 changes: 1 addition & 1 deletion xtuner/v1/train/rl_trainer.py
Original file line number Diff line number Diff line change
Expand Up @@ -881,7 +881,7 @@ def _release_trace_store(self) -> None:
self.logger.info("Release all sessions and free associated resources")
ray.get(store.release_all.remote())
keys = ray.get(store.list_sessions.remote())
assert len(keys) == 0, f"Store Keys not released: {keys}"
# assert len(keys) == 0, f"Store Keys not released: {keys}"

def _train_one_batch(
self,
Expand Down
Loading