Files
wehub-resource-sync a203934033
Lint test / lint (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:34:58 +08:00

73 lines
3.0 KiB
Python

# Copyright (c) ModelScope Contributors. All rights reserved.
"""Shared resample logic for HF / Megatron / Megatron-Ray trainers.
When ``truncation_strategy='delete'`` (or dynamic sampling), samples whose
``template.encode`` fails (e.g. exceed ``max_length``, or multimodal processing
errors) must be replaced with fresh ones drawn from a backup iterator until we
have ``len(inputs)`` valid samples. The backends previously each carried a
near-identical copy of this loop; they only differed in the iterator they pull
from and whether the assistant response is stripped before encoding (prompt-only
algorithms like GRPO strip; GKD off-policy distillation keeps it).
"""
from typing import Iterator, List
from swift.template.base import Template
from swift.utils import get_logger, remove_response
logger = get_logger()
def resample_encode_failed_inputs(
template: Template,
data_iterator: Iterator,
inputs: List[dict],
max_resample_rounds: int = 10,
strip_response: bool = True,
) -> List[dict]:
"""Replace samples whose encode fails with fresh ones from ``data_iterator``.
Caps the TOTAL encode attempts (fail-fast): a systematic failure
(e.g. ``max_length`` too small so every prompt is over-length) raises quickly
instead of churning through the iterator, and an empty batch breaks the loop
instead of spinning forever.
Args:
template: Template used to encode (and thereby validate) a sample.
data_iterator: Backup iterator yielding batches (lists) of fresh samples.
inputs: The current batch; its length is the required valid count.
max_resample_rounds: Resample budget; total attempts == required * (rounds + 1).
strip_response: Remove the assistant response (in place) before encoding.
Returns:
A list of valid samples with the same length as ``inputs``.
Raises:
RuntimeError: If not enough valid samples are collected after the budget.
"""
required = len(inputs)
max_attempts = required * (max_resample_rounds + 1)
valid, pending = [], list(inputs)
attempts = n_dropped = 0
while len(valid) < required and attempts < max_attempts:
if not pending:
batch = list(next(data_iterator))
if not batch: # guard: an empty batch would otherwise spin forever
break
pending.extend(batch)
item = pending.pop(0)
attempts += 1
try:
if strip_response:
remove_response(item['messages'])
template.encode(item)
valid.append(item)
except Exception as e:
n_dropped += 1
logger.info(f'Encoding failed for one sample; will resample. {e}')
if len(valid) < required:
raise RuntimeError(f'resample: only collected {len(valid)}/{required} valid samples after {attempts} '
f'attempts ({n_dropped} failed). Increase `max_length` or adjust `truncation_strategy`.')
return valid[:required]