108 lines
3.5 KiB
Python
108 lines
3.5 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
# DeepSpeed Team
|
|
"""Rollout engine interface.
|
|
|
|
The trainer talks to its rollout engine through three small dataclasses
|
|
(``RolloutRequest`` in / ``RolloutBatch`` out / ``SamplingConfig``) and one
|
|
ABC. This keeps engine-specific concerns out of the trainer loop.
|
|
"""
|
|
|
|
from abc import ABC, abstractmethod
|
|
from dataclasses import dataclass
|
|
|
|
import torch
|
|
|
|
|
|
@dataclass
|
|
class RolloutConfig:
|
|
"""Configuration for the rollout engine."""
|
|
engine: str = "hybrid_engine"
|
|
|
|
# Use CUDA graph capture for decode acceleration.
|
|
use_graph_capture: bool = False
|
|
|
|
|
|
@dataclass
|
|
class SamplingConfig:
|
|
"""Sampling knobs that the trainer passes to ``generate`` each step."""
|
|
|
|
max_new_tokens: int
|
|
temperature: float = 1.0
|
|
top_p: float = 1.0
|
|
top_k: int = -1
|
|
n_samples_per_prompt: int = 1
|
|
|
|
|
|
@dataclass
|
|
class RolloutRequest:
|
|
"""Input to ``RolloutEngine.generate``.
|
|
|
|
Prompts arrive *left-padded* (i.e. real tokens at the right edge) so that
|
|
causal generation appends naturally after them.
|
|
"""
|
|
|
|
prompt_ids: torch.Tensor # [B, T_p] left-padded with pad_token_id
|
|
prompt_attention_mask: torch.Tensor # [B, T_p], 1 on real prompt tokens
|
|
|
|
def __post_init__(self) -> None:
|
|
if self.prompt_ids.dim() != 2:
|
|
raise ValueError(f"prompt_ids must be 2-D [B, T_p]; got {tuple(self.prompt_ids.shape)}")
|
|
if self.prompt_attention_mask.shape != self.prompt_ids.shape:
|
|
raise ValueError(f"prompt_attention_mask shape {tuple(self.prompt_attention_mask.shape)} "
|
|
f"does not match prompt_ids {tuple(self.prompt_ids.shape)}")
|
|
|
|
|
|
@dataclass
|
|
class RolloutBatch:
|
|
"""Output of ``RolloutEngine.generate``.
|
|
|
|
``input_ids`` holds the *concatenation* of (left-padded) prompt and
|
|
response, right-padded to the longest sequence in the batch.
|
|
"""
|
|
|
|
input_ids: torch.Tensor # [B', T_p + T_r]; B' = B * n_samples_per_prompt
|
|
attention_mask: torch.Tensor # [B', T_p + T_r]
|
|
response_start_idx: torch.Tensor # [B'] int
|
|
|
|
def __post_init__(self) -> None:
|
|
if self.input_ids.dim() != 2:
|
|
raise ValueError(f"input_ids must be 2-D; got {tuple(self.input_ids.shape)}")
|
|
if self.attention_mask.shape != self.input_ids.shape:
|
|
raise ValueError(f"attention_mask shape {tuple(self.attention_mask.shape)} does not "
|
|
f"match input_ids {tuple(self.input_ids.shape)}")
|
|
B = self.input_ids.shape[0]
|
|
if self.response_start_idx.shape != (B, ):
|
|
raise ValueError(f"response_start_idx must be 1-D of length {B}; got "
|
|
f"{tuple(self.response_start_idx.shape)}")
|
|
|
|
@property
|
|
def batch_size(self) -> int:
|
|
return int(self.input_ids.shape[0])
|
|
|
|
@property
|
|
def seq_len(self) -> int:
|
|
return int(self.input_ids.shape[1])
|
|
|
|
|
|
class RolloutEngine(ABC):
|
|
"""Abstract base for rollout engines."""
|
|
|
|
name: str = "base"
|
|
|
|
@abstractmethod
|
|
def generate(self, request: RolloutRequest, sampling: SamplingConfig) -> RolloutBatch:
|
|
"""Run generation, return prompt+response in one tensor."""
|
|
|
|
@abstractmethod
|
|
def sync_weights(self, step: int) -> None:
|
|
"""Push updated weights into the rollout backend.
|
|
|
|
No-op when the rollout engine is co-located with the training engine
|
|
(e.g. hybrid engine shares weights directly).
|
|
"""
|
|
|
|
def shutdown(self) -> None:
|
|
"""Release any backend resources. Default no-op."""
|
|
return None
|