class ThinkingBudgetState:
"""Model Runner V2 state for per-request thinking token budgets."""
def __init__(
self,
req_states: RequestState,
reasoning_config: "ReasoningConfig | None",
):
self.req_states = req_states
self.max_num_reqs = req_states.max_num_reqs
self.device = req_states.device
start_ids = (
[]
if reasoning_config is None
else reasoning_config.reasoning_start_token_ids or []
)
end_ids = (
[]
if reasoning_config is None
else reasoning_config.reasoning_end_token_ids or []
)
natural_end_ids = (
[]
if reasoning_config is None
else reasoning_config.natural_reasoning_end_token_ids or []
)
self.enabled = bool(start_ids and end_ids and natural_end_ids)
if not self.enabled:
return
self.thinking_token_budget = UvaBackedTensor(
self.max_num_reqs, dtype=torch.int32
)
self.thinking_token_budget.np.fill(-1)
self.thinking_token_budget.copy_to_uva()
self.use_thinking_budget = np.zeros(self.max_num_reqs, dtype=bool)
self.cached_last_start = torch.full(
(self.max_num_reqs,), -1, dtype=torch.int32, device=self.device
)
self.cached_last_end = torch.full(
(self.max_num_reqs,), -1, dtype=torch.int32, device=self.device
)
self.cached_scan_pos = torch.zeros(
self.max_num_reqs, dtype=torch.int32, device=self.device
)
self._reset_reqs: list[int] = []
self._budget_dirty = False
self.reasoning_start_token_ids = torch.tensor(
start_ids, dtype=torch.int32, device=self.device
)
self.natural_reasoning_end_token_ids = torch.tensor(
natural_end_ids, dtype=torch.int32, device=self.device
)
self.reasoning_end_token_ids = torch.tensor(
end_ids, dtype=torch.int32, device=self.device
)
def add_request(self, req_idx: int, sampling_params: SamplingParams) -> None:
if not self.enabled:
return
budget = sampling_params.thinking_token_budget
self.use_thinking_budget[req_idx] = budget is not None
if budget is None:
budget = -1
else:
budget = min(budget, _INT32_MAX)
self._reset_reqs.append(req_idx)
if self.thinking_token_budget.np[req_idx] != budget:
self.thinking_token_budget.np[req_idx] = budget
self._budget_dirty = True
def apply_staged_writes(self) -> None:
if not self.enabled:
return
if self._reset_reqs:
idx = async_tensor_h2d(
self._reset_reqs, dtype=torch.int64, device=self.device
)
self.cached_last_start.index_fill_(0, idx, -1)
self.cached_last_end.index_fill_(0, idx, -1)
self.cached_scan_pos.index_fill_(0, idx, 0)
self._reset_reqs.clear()
if self._budget_dirty:
self.thinking_token_budget.copy_to_uva()
self._budget_dirty = False
def apply(
self,
logits: torch.Tensor,
expanded_idx_mapping: torch.Tensor,
idx_mapping: torch.Tensor,
idx_mapping_np: np.ndarray,
input_ids: torch.Tensor,
expanded_local_pos: torch.Tensor,
) -> None:
if not self.enabled or not np.any(self.use_thinking_budget[idx_mapping_np]):
return
apply_thinking_budget(
logits,
idx_mapping,
expanded_idx_mapping,
self.thinking_token_budget.gpu,
self.req_states.all_token_ids.gpu,
self.req_states.total_len.gpu,
input_ids,
expanded_local_pos,
self.cached_last_start,
self.cached_last_end,
self.cached_scan_pos,
self.reasoning_start_token_ids,
self.natural_reasoning_end_token_ids,
self.reasoning_end_token_ids,
)