[Scheduler] Defer prefill input_ids H2D to forward stream, unify resolve via future_map (#25945)
This commit is contained in:
@@ -151,7 +151,7 @@ class ScheduleBatchDisaggregationDecodeMixin:
|
|||||||
if spec_info is not None:
|
if spec_info is not None:
|
||||||
self.spec_info = spec_info
|
self.spec_info = spec_info
|
||||||
else:
|
else:
|
||||||
# Non-spec: positive last token feeds decode directly. No FutureMap
|
# Non-spec: stash last token into the relay so the first DECODE's
|
||||||
# bootstrap needed (SB self-maintains seq_lens; resolve_future is
|
# resolve_forward_inputs gathers it like any other decode iter.
|
||||||
# a no-op on positive input_ids).
|
future_map.stash(self.req_pool_indices, last_tokens_tensor)
|
||||||
self.input_ids = last_tokens_tensor
|
self.input_ids = None
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
|
||||||
from typing import TYPE_CHECKING, Optional, Sequence, Union
|
from typing import TYPE_CHECKING, Optional, Sequence, Union
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
|
from sglang.srt.environ import envs
|
||||||
from sglang.srt.speculative.spec_utils import spec_need_hidden_states
|
from sglang.srt.speculative.spec_utils import spec_need_hidden_states
|
||||||
from sglang.srt.utils import is_cuda, is_hip, is_npu
|
from sglang.srt.utils import is_cuda, is_hip, is_npu
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ _is_npu = is_npu()
|
|||||||
# Token-buf consume tracking: init to -1, assert non-negative on gather,
|
# Token-buf consume tracking: init to -1, assert non-negative on gather,
|
||||||
# write -1 back. Catches "gather without intermediate stash" bugs. CI enables
|
# write -1 back. Catches "gather without intermediate stash" bugs. CI enables
|
||||||
# via the existing SGLANG_IS_IN_CI; off in production.
|
# via the existing SGLANG_IS_IN_CI; off in production.
|
||||||
_DEBUG_ASSERT = os.getenv("SGLANG_IS_IN_CI", "").lower() == "true"
|
_DEBUG_ASSERT = envs.SGLANG_IS_IN_CI.get()
|
||||||
|
|
||||||
|
|
||||||
@torch.compile(dynamic=True, disable=_is_npu)
|
@torch.compile(dynamic=True, disable=_is_npu)
|
||||||
@@ -78,34 +78,42 @@ def _gather_spec_extras(
|
|||||||
return topk_p, topk_index, bonus_tokens, hidden_states
|
return topk_p, topk_index, bonus_tokens, hidden_states
|
||||||
|
|
||||||
|
|
||||||
def _resolve_future_token_ids_native(input_ids, future_token_ids_map):
|
def resolve_forward_inputs(batch: ScheduleBatch, future_map: FutureMap) -> None:
|
||||||
input_ids[:] = torch.where(
|
"""Materialize input_ids at forward entry. Two sources:
|
||||||
input_ids < 0,
|
|
||||||
future_token_ids_map[torch.clamp(-input_ids, min=0)],
|
|
||||||
input_ids,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
- Prefill: H2D copy from pinned CPU staging (prefill_input_ids_cpu).
|
||||||
|
- Decode/spec_v2: gather from FutureMap (last iter's sampled token).
|
||||||
|
"""
|
||||||
|
if batch.prefill_input_ids_cpu is not None:
|
||||||
|
prefill_gpu = batch.prefill_input_ids_cpu.to(batch.device, non_blocking=True)
|
||||||
|
if batch.mix_running_indices is not None:
|
||||||
|
decode_gpu = future_map.output_tokens_buf[batch.mix_running_indices]
|
||||||
|
if _DEBUG_ASSERT:
|
||||||
|
_assert_nonneg_and_invalidate(
|
||||||
|
decode_gpu,
|
||||||
|
future_map.output_tokens_buf,
|
||||||
|
batch.mix_running_indices,
|
||||||
|
)
|
||||||
|
batch.input_ids = torch.cat([prefill_gpu, decode_gpu])
|
||||||
|
else:
|
||||||
|
batch.input_ids = prefill_gpu
|
||||||
|
batch.prefill_input_ids_cpu = None
|
||||||
|
batch.mix_running_indices = None
|
||||||
|
elif batch.input_ids is None and future_map.spec_algo.is_none():
|
||||||
|
batch.input_ids = future_map.output_tokens_buf[batch.req_pool_indices]
|
||||||
|
if _DEBUG_ASSERT:
|
||||||
|
_assert_nonneg_and_invalidate(
|
||||||
|
batch.input_ids, future_map.output_tokens_buf, batch.req_pool_indices
|
||||||
|
)
|
||||||
|
|
||||||
if _is_cuda or _is_hip:
|
# spec_v1 (non-overlap spec) doesn't relay extras; only spec_v2 does.
|
||||||
from sglang.jit_kernel.resolve_future_token_ids import (
|
if batch.is_spec_v2:
|
||||||
resolve_future_token_ids_cuda,
|
future_map._resolve_spec_extras(batch)
|
||||||
)
|
|
||||||
|
|
||||||
_resolve_future_token_ids = resolve_future_token_ids_cuda
|
|
||||||
else:
|
|
||||||
_resolve_future_token_ids = _resolve_future_token_ids_native
|
|
||||||
|
|
||||||
|
|
||||||
class FutureMap:
|
class FutureMap:
|
||||||
"""Cross-iter relay buffer for values the next iter's schedule cannot
|
"""Always-on pool-indexed relay for cross-iter values. Forward writes via
|
||||||
compute locally (e.g. spec_v2 seq_lens after accept_lens, sampled tokens).
|
publish/stash; next iter reads via resolve_forward_inputs / resolve_seq_lens_cpu.
|
||||||
|
|
||||||
Forward stream publishes into a buf; next iter's schedule pulls lazily.
|
|
||||||
Schedule-deterministic values (e.g. non-spec seq_lens via +1) stay
|
|
||||||
maintained by SB directly and do not need the relay.
|
|
||||||
|
|
||||||
SB.seq_lens GPU is always a faithful seq_lens_cpu mirror; forward path
|
|
||||||
treats it as read-only, spec mutations land on forward_batch.seq_lens.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -174,19 +182,6 @@ class FutureMap:
|
|||||||
device=self.device,
|
device=self.device,
|
||||||
)
|
)
|
||||||
|
|
||||||
def resolve_future(self, batch: ScheduleBatch):
|
|
||||||
# seq_lens is already real on entry (SB +1 for non-spec;
|
|
||||||
# resolve_seq_lens_cpu pulled from buf for spec_v2). Only resolve
|
|
||||||
# input_ids tokens / spec extras here.
|
|
||||||
if self.spec_algo.is_none():
|
|
||||||
_resolve_future_token_ids(batch.input_ids, self.output_tokens_buf)
|
|
||||||
if _DEBUG_ASSERT:
|
|
||||||
_assert_nonneg_and_invalidate(
|
|
||||||
batch.input_ids, self.output_tokens_buf, batch.req_pool_indices
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self._resolve_spec_extras(batch)
|
|
||||||
|
|
||||||
def _resolve_spec_extras(self, batch: ScheduleBatch) -> None:
|
def _resolve_spec_extras(self, batch: ScheduleBatch) -> None:
|
||||||
draft_input: EagleDraftInput = batch.spec_info
|
draft_input: EagleDraftInput = batch.spec_info
|
||||||
if draft_input is None:
|
if draft_input is None:
|
||||||
@@ -218,14 +213,6 @@ class FutureMap:
|
|||||||
draft_input.bonus_tokens, self.output_tokens_buf, indices
|
draft_input.bonus_tokens, self.output_tokens_buf, indices
|
||||||
)
|
)
|
||||||
|
|
||||||
def set_input_ids_sentinel(
|
|
||||||
self, batch: ScheduleBatch, future_indices: torch.Tensor
|
|
||||||
) -> None:
|
|
||||||
# Sentinel for the decode portion so mixed batches can cat extend
|
|
||||||
# (positive real tokens) + decode (negative sentinels) into one
|
|
||||||
# input_ids; resolve_future translates negatives via output_tokens_buf.
|
|
||||||
batch.input_ids = -future_indices
|
|
||||||
|
|
||||||
def resolve_seq_lens_cpu(self, batch: ScheduleBatch) -> None:
|
def resolve_seq_lens_cpu(self, batch: ScheduleBatch) -> None:
|
||||||
# seq_lens_cpu may be needed on the host for kernel-launch prep (some backends).
|
# seq_lens_cpu may be needed on the host for kernel-launch prep (some backends).
|
||||||
# Run this D2H on a standalone stream to avoid chain-blocking forward_n ->
|
# Run this D2H on a standalone stream to avoid chain-blocking forward_n ->
|
||||||
@@ -285,7 +272,11 @@ class FutureMap:
|
|||||||
if indices.shape[0] == 0:
|
if indices.shape[0] == 0:
|
||||||
# DP idle: payload is empty stub; lazy-init shape peek would IndexError.
|
# DP idle: payload is empty stub; lazy-init shape peek would IndexError.
|
||||||
return
|
return
|
||||||
if self.spec_algo.is_none():
|
# Dispatch by payload type, not spec_algo: spec_v1 (non-overlap spec)
|
||||||
|
# also passes a token Tensor here.
|
||||||
|
# FIXME(lsyin): unify this relay path with a dataclass instead of the
|
||||||
|
# Tensor / EagleDraftInput type switch.
|
||||||
|
if isinstance(payload, torch.Tensor):
|
||||||
self.output_tokens_buf[indices] = payload.to(torch.int64)
|
self.output_tokens_buf[indices] = payload.to(torch.int64)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from sglang.srt.dllm.config import DllmConfig
|
|||||||
from sglang.srt.model_executor.forward_batch_info import ForwardBatch
|
from sglang.srt.model_executor.forward_batch_info import ForwardBatch
|
||||||
from sglang.srt.utils.common import (
|
from sglang.srt.utils.common import (
|
||||||
ceil_align,
|
ceil_align,
|
||||||
flatten_arrays_to_int64_tensor,
|
flatten_arrays_to_pinned_cpu,
|
||||||
is_pin_memory_available,
|
is_pin_memory_available,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1525,6 +1525,9 @@ class ScheduleBatch(ScheduleBatchDisaggregationDecodeMixin):
|
|||||||
# === GPU tensors crossing to ForwardBatch (clone targets for stream isolation) ===
|
# === GPU tensors crossing to ForwardBatch (clone targets for stream isolation) ===
|
||||||
# Batched arguments to model runner
|
# Batched arguments to model runner
|
||||||
input_ids: torch.Tensor = None # shape: [b], int64
|
input_ids: torch.Tensor = None # shape: [b], int64
|
||||||
|
# Staging consumed by resolve_forward_inputs (prefill H2D / mixed gather).
|
||||||
|
prefill_input_ids_cpu: Optional[torch.Tensor] = None
|
||||||
|
mix_running_indices: Optional[torch.Tensor] = None
|
||||||
input_embeds: torch.Tensor = None # shape: [b, hidden_size], float32
|
input_embeds: torch.Tensor = None # shape: [b, hidden_size], float32
|
||||||
|
|
||||||
# Token replacement embeddings and absolute positions (optional).
|
# Token replacement embeddings and absolute positions (optional).
|
||||||
@@ -1729,8 +1732,10 @@ class ScheduleBatch(ScheduleBatchDisaggregationDecodeMixin):
|
|||||||
|
|
||||||
pt += req.extend_input_len
|
pt += req.extend_input_len
|
||||||
|
|
||||||
# Reassign
|
# Reassign: ED stripping rebuilds prefill_input_ids_cpu (CPU pinned);
|
||||||
self.input_ids = flatten_arrays_to_int64_tensor(input_ids, self.device, _pin)
|
# resolve_forward_inputs will H2D this on forward stream. self.input_ids
|
||||||
|
# stays None.
|
||||||
|
self.prefill_input_ids_cpu = flatten_arrays_to_pinned_cpu(input_ids, _pin)
|
||||||
self.seq_lens = torch.tensor(seq_lens, dtype=torch.int64, pin_memory=_pin).to(
|
self.seq_lens = torch.tensor(seq_lens, dtype=torch.int64, pin_memory=_pin).to(
|
||||||
self.device, non_blocking=True
|
self.device, non_blocking=True
|
||||||
)
|
)
|
||||||
@@ -1833,7 +1838,9 @@ class ScheduleBatch(ScheduleBatchDisaggregationDecodeMixin):
|
|||||||
]
|
]
|
||||||
|
|
||||||
_pin = is_pin_memory_available(self.device)
|
_pin = is_pin_memory_available(self.device)
|
||||||
input_ids_tensor = flatten_arrays_to_int64_tensor(input_ids, self.device, _pin)
|
# Stay on pinned CPU; H2D is deferred to forward stream via
|
||||||
|
# resolve_forward_inputs.
|
||||||
|
pinned_input_ids = flatten_arrays_to_pinned_cpu(input_ids, _pin)
|
||||||
seq_lens_tensor = torch.tensor(seq_lens, dtype=torch.int64, pin_memory=_pin).to(
|
seq_lens_tensor = torch.tensor(seq_lens, dtype=torch.int64, pin_memory=_pin).to(
|
||||||
self.device, non_blocking=True
|
self.device, non_blocking=True
|
||||||
)
|
)
|
||||||
@@ -2015,7 +2022,8 @@ class ScheduleBatch(ScheduleBatchDisaggregationDecodeMixin):
|
|||||||
replace_embeds_tensor = None
|
replace_embeds_tensor = None
|
||||||
replace_positions_tensor = None
|
replace_positions_tensor = None
|
||||||
|
|
||||||
self.input_ids = input_ids_tensor
|
self.input_ids = None
|
||||||
|
self.prefill_input_ids_cpu = pinned_input_ids
|
||||||
self.req_pool_indices = req_pool_indices_tensor
|
self.req_pool_indices = req_pool_indices_tensor
|
||||||
self.req_pool_indices_cpu = req_pool_indices_cpu
|
self.req_pool_indices_cpu = req_pool_indices_cpu
|
||||||
self.orig_seq_lens = orig_seq_lens_tensor
|
self.orig_seq_lens = orig_seq_lens_tensor
|
||||||
@@ -2214,11 +2222,12 @@ class ScheduleBatch(ScheduleBatchDisaggregationDecodeMixin):
|
|||||||
req.fill_ids = req.origin_input_ids + req.output_ids
|
req.fill_ids = req.origin_input_ids + req.output_ids
|
||||||
req.set_extend_input_len(1)
|
req.set_extend_input_len(1)
|
||||||
|
|
||||||
input_ids = torch.cat([self.input_ids, running_batch.input_ids])
|
# Decode tokens of the running portion live in future_map.output_tokens_buf.
|
||||||
|
self.input_ids = None
|
||||||
|
self.mix_running_indices = running_batch.req_pool_indices
|
||||||
out_cache_loc = torch.cat([self.out_cache_loc, running_batch.out_cache_loc])
|
out_cache_loc = torch.cat([self.out_cache_loc, running_batch.out_cache_loc])
|
||||||
|
|
||||||
self.merge_batch(running_batch)
|
self.merge_batch(running_batch)
|
||||||
self.input_ids = input_ids
|
|
||||||
self.out_cache_loc = out_cache_loc
|
self.out_cache_loc = out_cache_loc
|
||||||
|
|
||||||
# For overlap scheduler, the output_ids has one step delay
|
# For overlap scheduler, the output_ids has one step delay
|
||||||
@@ -2428,27 +2437,25 @@ class ScheduleBatch(ScheduleBatchDisaggregationDecodeMixin):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if self.sampling_info.penalizer_orchestrator.is_required:
|
if self.sampling_info.penalizer_orchestrator.is_required:
|
||||||
if self.enable_overlap:
|
# Under overlap batch.input_ids is just a placeholder here -- the
|
||||||
# TODO: this can be slow, optimize this.
|
# real token is relayed via future_map and resolved at forward
|
||||||
delayed_output_ids = torch.tensor(
|
# entry. So take the last output token from Req directly
|
||||||
[
|
# (origin_input_ids[-1] on the first decode, before any output).
|
||||||
(
|
latest_output_ids = torch.tensor(
|
||||||
req.output_ids[-1]
|
[
|
||||||
if len(req.output_ids)
|
(
|
||||||
else req.origin_input_ids[-1]
|
req.output_ids[-1]
|
||||||
)
|
if len(req.output_ids)
|
||||||
for req in self.reqs
|
else req.origin_input_ids[-1]
|
||||||
],
|
)
|
||||||
dtype=torch.int64,
|
for req in self.reqs
|
||||||
device=self.device,
|
],
|
||||||
)
|
dtype=torch.int64,
|
||||||
self.sampling_info.penalizer_orchestrator.cumulate_output_tokens(
|
device=self.device,
|
||||||
delayed_output_ids
|
)
|
||||||
)
|
self.sampling_info.penalizer_orchestrator.cumulate_output_tokens(
|
||||||
else:
|
latest_output_ids
|
||||||
self.sampling_info.penalizer_orchestrator.cumulate_output_tokens(
|
)
|
||||||
self.input_ids
|
|
||||||
)
|
|
||||||
|
|
||||||
# input_ids is set at end of previous run_batch (placeholder for
|
# input_ids is set at end of previous run_batch (placeholder for
|
||||||
# overlap; next_token_ids cast for non-overlap).
|
# overlap; next_token_ids cast for non-overlap).
|
||||||
@@ -2606,8 +2613,14 @@ class ScheduleBatch(ScheduleBatchDisaggregationDecodeMixin):
|
|||||||
self.out_cache_loc = None
|
self.out_cache_loc = None
|
||||||
# Sum is recomputed lazily by ForwardBatch.init_new.
|
# Sum is recomputed lazily by ForwardBatch.init_new.
|
||||||
self.seq_lens_sum = None
|
self.seq_lens_sum = None
|
||||||
if self.input_ids is not None:
|
# Cat only when both sides hold a real token tensor; otherwise drop to
|
||||||
|
# None and let resolve_forward_inputs rebuild from the merged
|
||||||
|
# req_pool_indices. Mismatch arises e.g. with spec_v1, which keeps its
|
||||||
|
# tensor while a relay-staged side is None -- there the worker rebuilds.
|
||||||
|
if self.input_ids is not None and other.input_ids is not None:
|
||||||
self.input_ids = torch.cat([self.input_ids, other.input_ids])
|
self.input_ids = torch.cat([self.input_ids, other.input_ids])
|
||||||
|
else:
|
||||||
|
self.input_ids = None
|
||||||
# Optional under no-verify-sync; drop the mirror if either side absent.
|
# Optional under no-verify-sync; drop the mirror if either side absent.
|
||||||
if self.seq_lens_cpu is None or other.seq_lens_cpu is None:
|
if self.seq_lens_cpu is None or other.seq_lens_cpu is None:
|
||||||
self.seq_lens_cpu = None
|
self.seq_lens_cpu = None
|
||||||
|
|||||||
@@ -147,7 +147,10 @@ from sglang.srt.managers.io_struct import (
|
|||||||
)
|
)
|
||||||
from sglang.srt.managers.load_snapshot import LoadSnapshot, create_load_snapshot_writer
|
from sglang.srt.managers.load_snapshot import LoadSnapshot, create_load_snapshot_writer
|
||||||
from sglang.srt.managers.multimodal_processor import get_mm_processor, import_processors
|
from sglang.srt.managers.multimodal_processor import get_mm_processor, import_processors
|
||||||
from sglang.srt.managers.overlap_utils import decide_needs_cpu_seq_lens
|
from sglang.srt.managers.overlap_utils import (
|
||||||
|
decide_needs_cpu_seq_lens,
|
||||||
|
resolve_forward_inputs,
|
||||||
|
)
|
||||||
from sglang.srt.managers.prefill_delayer import (
|
from sglang.srt.managers.prefill_delayer import (
|
||||||
PrefillDelayer,
|
PrefillDelayer,
|
||||||
PrefillDelayerSinglePassExecutor,
|
PrefillDelayerSinglePassExecutor,
|
||||||
@@ -1155,14 +1158,13 @@ class Scheduler(
|
|||||||
self.device_module = torch.get_device_module(self.device)
|
self.device_module = torch.get_device_module(self.device)
|
||||||
|
|
||||||
if use_mlx():
|
if use_mlx():
|
||||||
# MLX overlap scheduling uses mx.async_eval / mx.eval for
|
# MLX: no CUDA streams / FutureMap.
|
||||||
# synchronisation so no CUDA/MPS streams or FutureMap needed.
|
|
||||||
self.future_map = None
|
self.future_map = None
|
||||||
# Empty result_queue is needed because idle-check references it
|
|
||||||
# when enable_overlap is True.
|
|
||||||
self.result_queue: Deque = deque()
|
self.result_queue: Deque = deque()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# forward_stream_ctx / copy_stream are also used by PP (non-overlap)
|
||||||
|
# via scheduler_pp_mixin; init unconditionally to match main.
|
||||||
self.forward_stream_ctx: CudaStreamContext = self.device_module.stream(
|
self.forward_stream_ctx: CudaStreamContext = self.device_module.stream(
|
||||||
self.forward_stream
|
self.forward_stream
|
||||||
)
|
)
|
||||||
@@ -1171,10 +1173,7 @@ class Scheduler(
|
|||||||
self.copy_stream
|
self.copy_stream
|
||||||
)
|
)
|
||||||
|
|
||||||
if not self.enable_overlap:
|
# FutureMap is always-on: input_ids relay used in both modes.
|
||||||
self.future_map = None
|
|
||||||
return
|
|
||||||
|
|
||||||
# Workers not on BaseSpecWorker (e.g. FrozenKVMTPWorker) lack the
|
# Workers not on BaseSpecWorker (e.g. FrozenKVMTPWorker) lack the
|
||||||
# override; fall back to target-only so the helper still produces a
|
# override; fall back to target-only so the helper still produces a
|
||||||
# safe decision (no accidental opt-out for unaudited shapes).
|
# safe decision (no accidental opt-out for unaudited shapes).
|
||||||
@@ -1192,6 +1191,10 @@ class Scheduler(
|
|||||||
self.req_to_token_pool,
|
self.req_to_token_pool,
|
||||||
needs_cpu_seq_lens=needs_cpu_seq_lens,
|
needs_cpu_seq_lens=needs_cpu_seq_lens,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if not self.enable_overlap:
|
||||||
|
return
|
||||||
|
|
||||||
self.batch_record_buf = [None] * 2
|
self.batch_record_buf = [None] * 2
|
||||||
self.batch_record_ct = 0
|
self.batch_record_ct = 0
|
||||||
|
|
||||||
@@ -2371,9 +2374,12 @@ class Scheduler(
|
|||||||
batch.seq_lens_cpu = torch.tensor(seq_lens, dtype=torch.int64)
|
batch.seq_lens_cpu = torch.tensor(seq_lens, dtype=torch.int64)
|
||||||
batch.orig_seq_lens = torch.tensor(seq_lens, dtype=torch.int32, device=device)
|
batch.orig_seq_lens = torch.tensor(seq_lens, dtype=torch.int32, device=device)
|
||||||
batch.seq_lens_sum = sum(seq_lens)
|
batch.seq_lens_sum = sum(seq_lens)
|
||||||
batch.input_ids = torch.tensor(
|
# Stash last token into relay; resolve_forward_inputs will gather.
|
||||||
|
last_tokens = torch.tensor(
|
||||||
[r.output_ids[-1] for r in reqs], dtype=torch.int64, device=device
|
[r.output_ids[-1] for r in reqs], dtype=torch.int64, device=device
|
||||||
)
|
)
|
||||||
|
self.future_map.stash(batch.req_pool_indices, last_tokens)
|
||||||
|
batch.input_ids = None
|
||||||
|
|
||||||
if batch.return_logprob:
|
if batch.return_logprob:
|
||||||
batch.top_logprobs_nums = [r.logprob.top_logprobs_num for r in reqs]
|
batch.top_logprobs_nums = [r.logprob.top_logprobs_num for r in reqs]
|
||||||
@@ -2970,21 +2976,30 @@ class Scheduler(
|
|||||||
# no-ops (ForwardBatch.init_new lazily computes the sum).
|
# no-ops (ForwardBatch.init_new lazily computes the sum).
|
||||||
self.future_map.resolve_seq_lens_cpu(batch)
|
self.future_map.resolve_seq_lens_cpu(batch)
|
||||||
|
|
||||||
with self._overlap_forward_isolation(batch):
|
with self.forward_stream_ctx:
|
||||||
future_indices = batch.req_pool_indices
|
self.forward_stream.wait_stream(self.schedule_stream)
|
||||||
|
# resolve consumes SB staging (prefill_input_ids_cpu /
|
||||||
|
# mix_running_indices). Run OUTSIDE isolation so the
|
||||||
|
# snapshot captures the post-consume state — restoring
|
||||||
|
# post-forward must not un-consume staging.
|
||||||
|
resolve_forward_inputs(batch, self.future_map)
|
||||||
|
|
||||||
# Spec_v2 fires on_publish mid-worker (between verify and
|
with self._overlap_forward_isolation(batch):
|
||||||
# draft_extend) so schedule prep can overlap with draft_extend.
|
future_indices = batch.req_pool_indices
|
||||||
# Non-spec has no later work — scheduler publishes after return.
|
|
||||||
fwd_kwargs = (
|
# Spec_v2 fires on_publish mid-worker (between verify and
|
||||||
{"on_publish": partial(self.future_map.publish, future_indices)}
|
# draft_extend) so schedule prep can overlap with draft_extend.
|
||||||
if batch.is_spec_v2
|
# Non-spec has no later work — scheduler publishes after return.
|
||||||
else {}
|
fwd_kwargs = (
|
||||||
)
|
{
|
||||||
|
"on_publish": partial(
|
||||||
|
self.future_map.publish, future_indices
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if batch.is_spec_v2
|
||||||
|
else {}
|
||||||
|
)
|
||||||
|
|
||||||
with self.forward_stream_ctx:
|
|
||||||
self.forward_stream.wait_stream(self.schedule_stream)
|
|
||||||
self.future_map.resolve_future(batch)
|
|
||||||
# FIXME: pp is not compatible with overlap
|
# FIXME: pp is not compatible with overlap
|
||||||
batch_result = self.model_worker.forward_batch_generation(
|
batch_result = self.model_worker.forward_batch_generation(
|
||||||
batch, **fwd_kwargs
|
batch, **fwd_kwargs
|
||||||
@@ -3014,28 +3029,42 @@ class Scheduler(
|
|||||||
else:
|
else:
|
||||||
batch_result.future_indices = future_indices
|
batch_result.future_indices = future_indices
|
||||||
|
|
||||||
self.future_map.set_input_ids_sentinel(batch, future_indices)
|
# Next-iter input_ids relayed via future_map.
|
||||||
|
batch.input_ids = None
|
||||||
|
|
||||||
if batch.is_spec_v2:
|
if batch.is_spec_v2:
|
||||||
batch.spec_info = batch_result.next_draft_input
|
batch.spec_info = batch_result.next_draft_input
|
||||||
batch.spec_info.future_indices = future_indices
|
batch.spec_info.future_indices = future_indices
|
||||||
elif self.enable_pdmux and batch.forward_mode.is_split_prefill():
|
elif self.enable_pdmux and batch.forward_mode.is_split_prefill():
|
||||||
|
resolve_forward_inputs(batch, self.future_map)
|
||||||
batch_result = self.tp_worker.forward_batch_split_prefill(batch)
|
batch_result = self.tp_worker.forward_batch_split_prefill(batch)
|
||||||
if isinstance(batch_result.next_token_ids, torch.Tensor):
|
if isinstance(batch_result.next_token_ids, torch.Tensor):
|
||||||
batch.input_ids = batch_result.next_token_ids.to(torch.int64)
|
self.future_map.stash(
|
||||||
|
batch.req_pool_indices, batch_result.next_token_ids
|
||||||
|
)
|
||||||
|
batch.input_ids = None
|
||||||
else:
|
else:
|
||||||
kwargs = (
|
kwargs = (
|
||||||
{"pp_proxy_tensors": pp_proxy_tensors}
|
{"pp_proxy_tensors": pp_proxy_tensors}
|
||||||
if self.spec_algorithm.is_none()
|
if self.spec_algorithm.is_none()
|
||||||
else {}
|
else {}
|
||||||
)
|
)
|
||||||
|
resolve_forward_inputs(batch, self.future_map)
|
||||||
batch_result = self.model_worker.forward_batch_generation(
|
batch_result = self.model_worker.forward_batch_generation(
|
||||||
batch, **kwargs
|
batch, **kwargs
|
||||||
)
|
)
|
||||||
# PP intermediate ranks return None; DLLM returns a per-req list.
|
|
||||||
# Only the tensor case maps onto batch.input_ids as next-iter input.
|
|
||||||
if isinstance(batch_result.next_token_ids, torch.Tensor):
|
if isinstance(batch_result.next_token_ids, torch.Tensor):
|
||||||
batch.input_ids = batch_result.next_token_ids.to(torch.int64)
|
if self.spec_algorithm.is_none():
|
||||||
|
# Non-spec: relay via future_map, gathered next iter.
|
||||||
|
self.future_map.stash(
|
||||||
|
batch.req_pool_indices, batch_result.next_token_ids
|
||||||
|
)
|
||||||
|
batch.input_ids = None
|
||||||
|
else:
|
||||||
|
# Spec_v1 (non-overlap spec): worker shape doesn't match
|
||||||
|
# req_pool_indices; relay is unused (worker rebuilds input_ids
|
||||||
|
# inside verify). Keep pre-PR behavior.
|
||||||
|
batch.input_ids = batch_result.next_token_ids.to(torch.int64)
|
||||||
self.update_cache_from_scheduler(batch, batch_result)
|
self.update_cache_from_scheduler(batch, batch_result)
|
||||||
|
|
||||||
# These 2 values are needed for processing the output, but the values can be
|
# These 2 values are needed for processing the output, but the values can be
|
||||||
@@ -3058,6 +3087,7 @@ class Scheduler(
|
|||||||
self.record_batch_in_overlap(batch)
|
self.record_batch_in_overlap(batch)
|
||||||
with self.forward_stream_ctx:
|
with self.forward_stream_ctx:
|
||||||
self.forward_stream.wait_stream(self.schedule_stream)
|
self.forward_stream.wait_stream(self.schedule_stream)
|
||||||
|
resolve_forward_inputs(batch, self.future_map)
|
||||||
pooler_output = self.tp_worker.forward_batch_embedding(batch)
|
pooler_output = self.tp_worker.forward_batch_embedding(batch)
|
||||||
ret = EmbeddingBatchResult(
|
ret = EmbeddingBatchResult(
|
||||||
embeddings=pooler_output.embeddings,
|
embeddings=pooler_output.embeddings,
|
||||||
@@ -3065,6 +3095,7 @@ class Scheduler(
|
|||||||
)
|
)
|
||||||
ret.copy_to_cpu()
|
ret.copy_to_cpu()
|
||||||
else:
|
else:
|
||||||
|
resolve_forward_inputs(batch, self.future_map)
|
||||||
pooler_output = self.tp_worker.forward_batch_embedding(batch)
|
pooler_output = self.tp_worker.forward_batch_embedding(batch)
|
||||||
ret = EmbeddingBatchResult(
|
ret = EmbeddingBatchResult(
|
||||||
embeddings=pooler_output.embeddings,
|
embeddings=pooler_output.embeddings,
|
||||||
|
|||||||
@@ -1091,6 +1091,10 @@ class SchedulerPPMixin:
|
|||||||
extend_logprob_start_len_per_req,
|
extend_logprob_start_len_per_req,
|
||||||
) = get_logprob_from_pp_outputs(pp_outputs)
|
) = get_logprob_from_pp_outputs(pp_outputs)
|
||||||
batch.input_ids = pp_outputs["next_token_ids"].to(torch.int64)
|
batch.input_ids = pp_outputs["next_token_ids"].to(torch.int64)
|
||||||
|
# PP rank 0 also relays into output_tokens_buf so the next iter's
|
||||||
|
# resolve_forward_inputs finds these tokens for the decode portion
|
||||||
|
# of mixed-chunk batches (which gather via mix_running_indices).
|
||||||
|
self.future_map.stash(batch.req_pool_indices, batch.input_ids)
|
||||||
output_result = GenerationBatchResult(
|
output_result = GenerationBatchResult(
|
||||||
logits_output=logits_output,
|
logits_output=logits_output,
|
||||||
pp_hidden_states_proxy_tensors=None,
|
pp_hidden_states_proxy_tensors=None,
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ class EagleDraftInputV2Mixin:
|
|||||||
batch.out_cache_loc = torch.empty(
|
batch.out_cache_loc = torch.empty(
|
||||||
(bs * topk * num_steps,),
|
(bs * topk * num_steps,),
|
||||||
dtype=torch.int64,
|
dtype=torch.int64,
|
||||||
device=batch.input_ids.device,
|
device=batch.device,
|
||||||
)
|
)
|
||||||
# FIXME(lsyin): align with the default code path
|
# FIXME(lsyin): align with the default code path
|
||||||
assign_draft_cache_locs_page_size_1[(bs,)](
|
assign_draft_cache_locs_page_size_1[(bs,)](
|
||||||
@@ -294,7 +294,7 @@ class EagleVerifyInputV2Mixin:
|
|||||||
batch.model_config.vocab_size,
|
batch.model_config.vocab_size,
|
||||||
"v2 prepare_for_verify input_ids",
|
"v2 prepare_for_verify input_ids",
|
||||||
)
|
)
|
||||||
device = batch.input_ids.device
|
device = batch.device
|
||||||
batch.out_cache_loc = assign_extend_cache_locs_func(
|
batch.out_cache_loc = assign_extend_cache_locs_func(
|
||||||
req_pool_indices=batch.req_pool_indices,
|
req_pool_indices=batch.req_pool_indices,
|
||||||
req_to_token=req_to_token_pool.req_to_token,
|
req_to_token=req_to_token_pool.req_to_token,
|
||||||
@@ -355,20 +355,16 @@ class EagleVerifyInputV2Mixin:
|
|||||||
Verify and find accepted tokens based on logits output and batch
|
Verify and find accepted tokens based on logits output and batch
|
||||||
(which contains spec decoding information).
|
(which contains spec decoding information).
|
||||||
"""
|
"""
|
||||||
|
device = batch.device
|
||||||
if batch.forward_mode.is_idle():
|
if batch.forward_mode.is_idle():
|
||||||
predict = torch.empty(0, dtype=torch.int32, device=batch.input_ids.device)
|
predict = torch.empty(0, dtype=torch.int32, device=device)
|
||||||
num_correct_drafts = torch.empty(
|
num_correct_drafts = torch.empty(0, dtype=torch.int32, device=device)
|
||||||
0, dtype=torch.int32, device=batch.input_ids.device
|
accept_index = torch.empty(0, dtype=torch.int32, device=device)
|
||||||
)
|
|
||||||
accept_index = torch.empty(
|
|
||||||
0, dtype=torch.int32, device=batch.input_ids.device
|
|
||||||
)
|
|
||||||
return predict, num_correct_drafts, accept_index
|
return predict, num_correct_drafts, accept_index
|
||||||
|
|
||||||
bs = len(batch.seq_lens)
|
bs = len(batch.seq_lens)
|
||||||
sampling_info = batch.sampling_info
|
sampling_info = batch.sampling_info
|
||||||
next_token_logits = logits_output.next_token_logits
|
next_token_logits = logits_output.next_token_logits
|
||||||
device = batch.input_ids.device
|
|
||||||
|
|
||||||
# Apply penalty
|
# Apply penalty
|
||||||
# This is a relaxed version of penalties for speculative decoding.
|
# This is a relaxed version of penalties for speculative decoding.
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ def apply_eagle_prefill_input_rotation(
|
|||||||
return
|
return
|
||||||
assert len(next_token_ids) == len(batch.seq_lens)
|
assert len(next_token_ids) == len(batch.seq_lens)
|
||||||
extend_lens = torch.tensor(
|
extend_lens = torch.tensor(
|
||||||
batch.extend_lens, dtype=torch.int64, device=batch.input_ids.device
|
batch.extend_lens, dtype=torch.int64, device=batch.device
|
||||||
)
|
)
|
||||||
seg_ends = extend_lens.cumsum(0) - 1
|
seg_ends = extend_lens.cumsum(0) - 1
|
||||||
rotated = torch.empty_like(batch.input_ids)
|
rotated = torch.empty_like(batch.input_ids)
|
||||||
|
|||||||
@@ -103,19 +103,24 @@ logger = logging.getLogger(__name__)
|
|||||||
torch_release = pkg_version.parse(torch.__version__).release
|
torch_release = pkg_version.parse(torch.__version__).release
|
||||||
|
|
||||||
|
|
||||||
def flatten_arrays_to_int64_tensor(
|
def flatten_arrays_to_pinned_cpu(parts: List[array[int]], pin: bool) -> torch.Tensor:
|
||||||
parts: List[array[int]], device, pin: bool
|
"""Flatten array.array('q') buffers into one int64 CPU tensor.
|
||||||
) -> torch.Tensor:
|
|
||||||
"""Flatten a list of array.array('q') buffers into one int64 tensor.
|
|
||||||
|
|
||||||
Uses NumPy here to speed up the conversion by using memcpy
|
NumPy memcpy instead of a per-element PyLong-to-int64 walk. Stays on
|
||||||
instead of a per-element PyLong-to-int64 walk.
|
(optionally pinned) CPU; H2D is the caller's job.
|
||||||
"""
|
"""
|
||||||
combined = np.concatenate([np.frombuffer(p, dtype=np.int64) for p in parts])
|
combined = np.concatenate([np.frombuffer(p, dtype=np.int64) for p in parts])
|
||||||
cpu_t = torch.from_numpy(combined)
|
cpu_t = torch.from_numpy(combined)
|
||||||
if pin:
|
if pin:
|
||||||
cpu_t = cpu_t.pin_memory()
|
cpu_t = cpu_t.pin_memory()
|
||||||
return cpu_t.to(device, non_blocking=True)
|
return cpu_t
|
||||||
|
|
||||||
|
|
||||||
|
def flatten_arrays_to_int64_tensor(
|
||||||
|
parts: List[array[int]], device, pin: bool
|
||||||
|
) -> torch.Tensor:
|
||||||
|
"""Flatten a list of array.array('q') buffers into one int64 tensor on `device`."""
|
||||||
|
return flatten_arrays_to_pinned_cpu(parts, pin).to(device, non_blocking=True)
|
||||||
|
|
||||||
|
|
||||||
# https://pytorch.org/docs/stable/notes/hip.html#checking-for-hip
|
# https://pytorch.org/docs/stable/notes/hip.html#checking-for-hip
|
||||||
|
|||||||
Reference in New Issue
Block a user