[Logprob] Serve input-logprob temporaries from CUDA-graph-pool dead space (#40038)
Co-authored-by: cctry <csycfl@gmail.com>
This commit is contained in:
@@ -243,6 +243,9 @@ class LogitsProcessorOutput:
|
||||
)
|
||||
input_token_ids_logprobs_idx: Optional[List] = None
|
||||
|
||||
# Completion of input-logprob copies from borrowed graph storage.
|
||||
input_logprobs_copy_done: Optional[torch.cuda.Event] = None
|
||||
|
||||
## Part 4: Diffusion LLM only.
|
||||
full_logits: Optional[torch.Tensor] = None
|
||||
|
||||
@@ -262,6 +265,22 @@ class LogitsProcessorOutput:
|
||||
# Scheduler-local output copied alongside the ordinary generation result.
|
||||
auxiliary_device_output: Optional[DeviceAuxiliaryOutput] = None
|
||||
|
||||
def finalize_input_logprobs(self) -> None:
|
||||
if self.input_logprobs_copy_done is None:
|
||||
return
|
||||
self.input_logprobs_copy_done.synchronize()
|
||||
self.input_logprobs_copy_done = None
|
||||
# Only borrowed results contain spans within each sequence. Other
|
||||
# producers (including multi-item scoring) keep their existing layout.
|
||||
for sequences in (
|
||||
self.input_top_logprobs_val,
|
||||
self.input_top_logprobs_idx,
|
||||
self.input_token_ids_logprobs_val,
|
||||
):
|
||||
if sequences is not None:
|
||||
for i, spans in enumerate(sequences):
|
||||
sequences[i] = [row for span in spans for row in span.tolist()]
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class LogitsMetadata:
|
||||
@@ -278,6 +297,8 @@ class LogitsMetadata:
|
||||
extend_logprob_pruned_lens_cpu: Optional[List[int]] = None
|
||||
top_logprobs_nums: Optional[List[int]] = None
|
||||
extend_input_logprob_token_ids_gpu: Optional[torch.Tensor] = None
|
||||
sample_indices_cpu: Optional[List[int]] = None
|
||||
input_logprob_indices_cpu: Optional[List[int]] = None
|
||||
token_ids_logprobs: Optional[List[List[int]]] = None
|
||||
|
||||
# logits and logprobs post processing
|
||||
@@ -737,6 +758,8 @@ class LogitsProcessor(nn.Module):
|
||||
else [torch.cat(lst) for lst in aux_pruned_states_lists]
|
||||
)
|
||||
|
||||
logits_metadata.sample_indices_cpu = sample_indices
|
||||
logits_metadata.input_logprob_indices_cpu = input_logprob_indices
|
||||
# Build the index tensors via pinned host memory + non-blocking H2D
|
||||
# so the small copy doesn't drain the stream.
|
||||
sample_indices = torch.tensor(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import bisect
|
||||
import dataclasses
|
||||
import logging
|
||||
from contextlib import nullcontext
|
||||
@@ -17,6 +18,7 @@ from sglang.srt.model_executor.runner_utils.pool import (
|
||||
graph_pool_borrow_largest_run,
|
||||
)
|
||||
from sglang.srt.runtime_context import get_exec
|
||||
from sglang.srt.utils.common import async_d2h
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sglang.srt.layers.logits_processor import LogitsMetadata, LogitsProcessorOutput
|
||||
@@ -45,6 +47,7 @@ class LogprobResult:
|
||||
top_logprobs_idx: Optional[List] = None
|
||||
token_ids_logprobs_val: Optional[List] = None
|
||||
token_ids_logprobs_idx: Optional[List] = None
|
||||
input_copy_done: Optional[torch.cuda.Event] = None
|
||||
|
||||
def write_input_to(self, logits_output: LogitsProcessorOutput) -> None:
|
||||
if self.token_logprobs is not None:
|
||||
@@ -55,6 +58,7 @@ class LogprobResult:
|
||||
if self.token_ids_logprobs_val is not None:
|
||||
logits_output.input_token_ids_logprobs_val = self.token_ids_logprobs_val
|
||||
logits_output.input_token_ids_logprobs_idx = self.token_ids_logprobs_idx
|
||||
logits_output.input_logprobs_copy_done = self.input_copy_done
|
||||
|
||||
def write_output_to(self, logits_output: LogitsProcessorOutput) -> None:
|
||||
if self.token_logprobs is not None:
|
||||
@@ -202,6 +206,7 @@ def get_top_logprobs_chunk(
|
||||
split_pruned_len: int,
|
||||
log_normalizer: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,
|
||||
precomputed_topk: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,
|
||||
copy_to_pinned_cpu: bool = False,
|
||||
) -> int:
|
||||
"""Get top-k logprobs for each sequence in the chunk.
|
||||
|
||||
@@ -217,6 +222,8 @@ def get_top_logprobs_chunk(
|
||||
log_normalizer: Per-row (max, logsumexp - max) of the logits
|
||||
precomputed_topk: (raw fp32 top values, indices) from the fused
|
||||
logsumexp+top-k kernel, sorted with lowest-index tie-breaking
|
||||
copy_to_pinned_cpu: Copy gathered results to pinned CPU spans;
|
||||
finalize_input_logprobs converts them to rows after the copy completes.
|
||||
|
||||
Returns:
|
||||
int: Number of remaining tokens to process in next chunk
|
||||
@@ -234,8 +241,14 @@ def get_top_logprobs_chunk(
|
||||
]
|
||||
else:
|
||||
values_tensor, indices_tensor = logprobs.topk(max_k, dim=1)
|
||||
values = values_tensor.tolist()
|
||||
indices = indices_tensor.tolist()
|
||||
if copy_to_pinned_cpu:
|
||||
values = async_d2h(values_tensor)
|
||||
indices = async_d2h(indices_tensor)
|
||||
rows_avail = values.shape[0]
|
||||
else:
|
||||
values = values_tensor.tolist()
|
||||
indices = indices_tensor.tolist()
|
||||
rows_avail = len(values)
|
||||
|
||||
pt = 0
|
||||
next_split_pruned_len = 0
|
||||
@@ -254,17 +267,19 @@ def get_top_logprobs_chunk(
|
||||
top_logprobs_idx.append([])
|
||||
continue
|
||||
|
||||
# Handle remaining tokens in next chunk if any
|
||||
available_len = min(pruned_len, max(rows_avail - pt, 0))
|
||||
if available_len < pruned_len:
|
||||
next_split_pruned_len = split_pruned_len + available_len
|
||||
|
||||
# Get the top-k logprobs
|
||||
val = []
|
||||
idx = []
|
||||
for j in range(pruned_len):
|
||||
# Handle remaining tokens in next chunk if any
|
||||
if pt + j >= len(values):
|
||||
next_split_pruned_len = split_pruned_len + j
|
||||
break
|
||||
# Append the top-k logprobs
|
||||
val.append(values[pt + j][:k])
|
||||
idx.append(indices[pt + j][:k])
|
||||
if copy_to_pinned_cpu:
|
||||
# Keep one span per sequence to avoid per-row tensor slicing.
|
||||
val = [values[pt : pt + available_len, :k]] if available_len else []
|
||||
idx = [indices[pt : pt + available_len, :k]] if available_len else []
|
||||
else:
|
||||
val = [values[pt + j][:k] for j in range(available_len)]
|
||||
idx = [indices[pt + j][:k] for j in range(available_len)]
|
||||
|
||||
# Append or extend based on whether the sequence was split across chunks
|
||||
# Split-sequence continuations extend; everyone else owns a fresh
|
||||
@@ -288,6 +303,7 @@ def get_token_ids_logprobs_chunk(
|
||||
token_ids_logprobs_idx: List,
|
||||
split_pruned_len: int = 0,
|
||||
log_normalizer: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,
|
||||
copy_to_pinned_cpu: bool = False,
|
||||
):
|
||||
"""Get token_ids logprobs for each sequence in the chunk.
|
||||
|
||||
@@ -301,6 +317,8 @@ def get_token_ids_logprobs_chunk(
|
||||
token_ids_logprobs_idx: List to store token indices
|
||||
split_pruned_len: Length of pruned tokens from previous chunk
|
||||
log_normalizer: Per-row (max, logsumexp - max) of the logits
|
||||
copy_to_pinned_cpu: Copy gathered results to pinned CPU spans;
|
||||
finalize_input_logprobs converts them to rows after the copy completes.
|
||||
|
||||
Returns:
|
||||
int: Number of remaining tokens to process in next chunk
|
||||
@@ -329,20 +347,25 @@ def get_token_ids_logprobs_chunk(
|
||||
token_ids_logprobs_idx.append([])
|
||||
continue
|
||||
|
||||
# Handle remaining tokens in next chunk if any
|
||||
available_len = min(pruned_len, max(logprobs.shape[0] - pt, 0))
|
||||
if available_len < pruned_len:
|
||||
next_split_pruned_len = split_pruned_len + available_len
|
||||
|
||||
# Get the token ids logprobs
|
||||
val = []
|
||||
idx = []
|
||||
for j in range(pruned_len):
|
||||
# Handle remaining tokens in next chunk if any
|
||||
if pt + j >= logprobs.shape[0]:
|
||||
next_split_pruned_len = split_pruned_len + j
|
||||
break
|
||||
if token_ids is not None:
|
||||
row = logprobs[pt + j, token_ids]
|
||||
if log_normalizer is not None:
|
||||
row = (row.float() - row_max[pt + j]) - row_log_sum[pt + j]
|
||||
val.append(row.tolist())
|
||||
idx.append(token_ids)
|
||||
if token_ids is not None and available_len > 0:
|
||||
rows = logprobs[pt : pt + available_len, token_ids]
|
||||
if log_normalizer is not None:
|
||||
rows = (
|
||||
rows.float() - row_max[pt : pt + available_len, None]
|
||||
) - row_log_sum[pt : pt + available_len, None]
|
||||
if copy_to_pinned_cpu:
|
||||
val.append(async_d2h(rows))
|
||||
else:
|
||||
val.extend(rows.tolist())
|
||||
idx.extend([token_ids] * available_len)
|
||||
|
||||
# Split-sequence continuations extend; everyone else owns a fresh
|
||||
# (possibly empty) entry.
|
||||
@@ -497,9 +520,12 @@ class InputLogprobProcessor:
|
||||
else:
|
||||
chunk_size = self.logprobs_chunk_size
|
||||
|
||||
borrow_logits_memory = False
|
||||
if pruned_states.is_cuda and not skip_chunking_for_dp_attn:
|
||||
borrow_logits_memory = self._can_borrow_logits_memory(chunk_size)
|
||||
borrow_logprob_memory = False
|
||||
if pruned_states.is_cuda:
|
||||
borrow_logprob_memory = self._can_borrow_logprob_memory(
|
||||
chunk_size=chunk_size,
|
||||
borrow_logits=not skip_chunking_for_dp_attn,
|
||||
)
|
||||
|
||||
return self._forward_by_chunk(
|
||||
pruned_states,
|
||||
@@ -510,23 +536,36 @@ class InputLogprobProcessor:
|
||||
get_logits_fn,
|
||||
logits_metadata,
|
||||
chunk_size,
|
||||
borrow_logits_memory=borrow_logits_memory,
|
||||
borrow_logprob_memory=borrow_logprob_memory,
|
||||
borrow_logits_memory=(
|
||||
borrow_logprob_memory and not skip_chunking_for_dp_attn
|
||||
),
|
||||
)
|
||||
|
||||
def _can_borrow_logits_memory(self, chunk_size: int) -> bool:
|
||||
def _can_borrow_logprob_memory(
|
||||
self,
|
||||
chunk_size: int,
|
||||
borrow_logits: bool,
|
||||
) -> bool:
|
||||
"""Borrow only when the planned chunk fits on every TP rank.
|
||||
|
||||
Resizing chunks to graph-pool capacity changes LM-head GEMM shapes
|
||||
and their rounding. Keep chunk boundaries independent of the graph
|
||||
memory layout, including when borrowing is disabled on another runner.
|
||||
"""
|
||||
# TP gathering can hold the local projection, gathered tensor, and
|
||||
# contiguous reshape together; FP32 bounds their possible dtypes.
|
||||
bytes_per_row = 3 * self.vocab_size * 4
|
||||
# NCCL's symmetric allocator owns its collective buffers, so those
|
||||
# allocations cannot be counted as borrowed storage.
|
||||
# A TP gather can hold the local projection, gathered tensor, and its
|
||||
# contiguous reshape together. Scoring alone needs at most two matrices.
|
||||
matrices_per_chunk = (
|
||||
3 if borrow_logits else (1 if self.enable_fast_input_logprobs else 2)
|
||||
)
|
||||
bytes_per_row = matrices_per_chunk * self.vocab_size * 4
|
||||
|
||||
# NCCL's symmetric allocator owns its collective buffers, so they
|
||||
# cannot be counted as borrowed storage.
|
||||
free_run = (
|
||||
0 if is_symmetric_memory_enabled() else graph_pool_borrow_largest_run()
|
||||
0
|
||||
if borrow_logits and is_symmetric_memory_enabled()
|
||||
else graph_pool_borrow_largest_run()
|
||||
)
|
||||
fit_rows = max(0, free_run - _GRAPH_POOL_BORROW_SLACK_BYTES) // bytes_per_row
|
||||
if self.chunking_group is not None:
|
||||
@@ -551,13 +590,16 @@ class InputLogprobProcessor:
|
||||
get_logits_fn: Callable,
|
||||
logits_metadata: LogitsMetadata,
|
||||
chunk_size: int,
|
||||
borrow_logprob_memory: bool = False,
|
||||
borrow_logits_memory: bool = False,
|
||||
) -> Tuple[LogprobResult, torch.Tensor]:
|
||||
"""Compute input logprobs chunk by chunk to cap peak memory."""
|
||||
total_size = pruned_states.shape[0]
|
||||
num_chunks = (total_size + chunk_size - 1) // chunk_size
|
||||
|
||||
token_logprobs = []
|
||||
sample_indices_cpu = logits_metadata.sample_indices_cpu
|
||||
input_logprob_indices_cpu = logits_metadata.input_logprob_indices_cpu
|
||||
token_logprobs = None if borrow_logprob_memory else []
|
||||
if logits_metadata.extend_return_top_logprob:
|
||||
top_logprobs_val = []
|
||||
top_logprobs_idx = []
|
||||
@@ -595,15 +637,10 @@ class InputLogprobProcessor:
|
||||
if num_chunks > 1 and hasattr(lm_head, "set_lm_head_pass"):
|
||||
lm_head.set_lm_head_pass(i)
|
||||
|
||||
# Get indices for this chunk
|
||||
chunk_mask = (input_logprob_indices >= start_idx) & (
|
||||
input_logprob_indices < end_idx
|
||||
)
|
||||
global_indices = input_logprob_indices[chunk_mask]
|
||||
chunk_indices = global_indices - start_idx
|
||||
# Get the positions in the original array where chunk_mask is True
|
||||
# This is needed to correctly index into extend_input_logprob_token_ids_gpu
|
||||
mask_indices = torch.nonzero(chunk_mask, as_tuple=True)[0]
|
||||
# The sorted host indices avoid per-chunk device synchronization.
|
||||
lp_lo = bisect.bisect_left(input_logprob_indices_cpu, start_idx)
|
||||
lp_hi = bisect.bisect_left(input_logprob_indices_cpu, end_idx)
|
||||
chunk_indices = input_logprob_indices[lp_lo:lp_hi] - start_idx
|
||||
|
||||
# Get the logits for this chunk. Each chunk must own its output:
|
||||
# writing through the shared graph logits buffer would alias
|
||||
@@ -621,107 +658,133 @@ class InputLogprobProcessor:
|
||||
use_logits_buffer=num_chunks == 1,
|
||||
)
|
||||
|
||||
# Sampled outputs must survive graph replay, so they are allocated
|
||||
# outside borrowing. The transient logits are released below.
|
||||
# These outputs must survive graph replay. Allocate them outside
|
||||
# borrowing, then consume and release chunk_logits in the next scope.
|
||||
if i == 0:
|
||||
sampled_logits = torch.empty(
|
||||
(sample_indices.shape[0], chunk_logits.shape[1]),
|
||||
dtype=chunk_logits.dtype,
|
||||
device=chunk_logits.device,
|
||||
)
|
||||
|
||||
# Handle sampled logits for the chunk if needed
|
||||
# This must be done before the continue statement to ensure all sampled_logits are filled
|
||||
chunk_sample_mask = (sample_indices >= start_idx) & (
|
||||
sample_indices < end_idx
|
||||
)
|
||||
if chunk_sample_mask.any():
|
||||
chunk_sample_indices = sample_indices[chunk_sample_mask] - start_idx
|
||||
sampled_logits[chunk_sample_mask] = chunk_logits[chunk_sample_indices]
|
||||
|
||||
# Zero-logprob-row chunks still need the per-sequence bookkeeping below.
|
||||
chunk_logprobs = chunk_logits[chunk_indices]
|
||||
del chunk_logits
|
||||
|
||||
# End at the last row inside the chunk; token_to_seq_idx[end_idx]
|
||||
# belongs to the next chunk and would emit its sequence twice.
|
||||
chunk_slice = slice(
|
||||
token_to_seq_idx[start_idx], token_to_seq_idx[end_idx - 1] + 1
|
||||
)
|
||||
|
||||
chunk_precomputed_topk = None
|
||||
if self.enable_fast_input_logprobs:
|
||||
# Every consumer below needs only small gathers / top-k plus a
|
||||
# per-row normalizer, so keep the raw logits and skip the
|
||||
# full-vocab log-softmax materialization entirely. When top-k
|
||||
# is requested, the fused kernel produces the normalizer and
|
||||
# the top-k in the same single read of the logits.
|
||||
max_k = (
|
||||
max(logits_metadata.top_logprobs_nums[chunk_slice])
|
||||
if logits_metadata.extend_return_top_logprob
|
||||
else 0
|
||||
)
|
||||
if 0 < max_k <= fused_max_k:
|
||||
row_max, row_log_sum, top_vals, top_idx = fused_kernel(
|
||||
chunk_logprobs, max_k
|
||||
if borrow_logprob_memory:
|
||||
token_logprobs = torch.empty(
|
||||
input_logprob_indices.shape,
|
||||
dtype=(
|
||||
torch.float32
|
||||
if self.enable_fast_input_logprobs
|
||||
else chunk_logits.dtype
|
||||
),
|
||||
device="cpu",
|
||||
pin_memory=True,
|
||||
)
|
||||
chunk_log_normalizer = (row_max, row_log_sum)
|
||||
chunk_precomputed_topk = (top_vals, top_idx)
|
||||
|
||||
# Fill the sampled logits whose rows fall in this chunk.
|
||||
s_lo = bisect.bisect_left(sample_indices_cpu, start_idx)
|
||||
s_hi = bisect.bisect_left(sample_indices_cpu, end_idx)
|
||||
if s_hi > s_lo:
|
||||
sampled_logits[s_lo:s_hi] = chunk_logits[
|
||||
sample_indices[s_lo:s_hi] - start_idx
|
||||
]
|
||||
|
||||
borrow_scope = (
|
||||
borrow_graph_pool(user="input logprob processing")
|
||||
if borrow_logprob_memory
|
||||
else nullcontext()
|
||||
)
|
||||
with borrow_scope:
|
||||
chunk_logprobs = chunk_logits[chunk_indices]
|
||||
del chunk_logits
|
||||
|
||||
# End at the last row inside the chunk; token_to_seq_idx[end_idx]
|
||||
# belongs to the next chunk and would emit its sequence twice.
|
||||
chunk_slice = slice(
|
||||
token_to_seq_idx[start_idx], token_to_seq_idx[end_idx - 1] + 1
|
||||
)
|
||||
|
||||
chunk_precomputed_topk = None
|
||||
if self.enable_fast_input_logprobs:
|
||||
# Every consumer below needs only small gathers / top-k plus a
|
||||
# per-row normalizer, so keep the raw logits and skip the
|
||||
# full-vocab log-softmax materialization entirely. When top-k
|
||||
# is requested, the fused kernel produces the normalizer and
|
||||
# the top-k in the same single read of the logits.
|
||||
max_k = (
|
||||
max(logits_metadata.top_logprobs_nums[chunk_slice])
|
||||
if logits_metadata.extend_return_top_logprob
|
||||
else 0
|
||||
)
|
||||
if 0 < max_k <= fused_max_k:
|
||||
row_max, row_log_sum, top_vals, top_idx = fused_kernel(
|
||||
chunk_logprobs, max_k
|
||||
)
|
||||
chunk_log_normalizer = (row_max, row_log_sum)
|
||||
chunk_precomputed_topk = (top_vals, top_idx)
|
||||
else:
|
||||
chunk_log_normalizer = compute_row_log_normalizer(
|
||||
chunk_logprobs
|
||||
)
|
||||
else:
|
||||
chunk_log_normalizer = compute_row_log_normalizer(chunk_logprobs)
|
||||
else:
|
||||
# Free the raw logits before the out-of-place log_softmax:
|
||||
# keeping all three alive is a 3x peak, which OOMs when the
|
||||
# single chunk covers a large batch.
|
||||
chunk_log_normalizer = None
|
||||
chunk_logprobs = torch.nn.functional.log_softmax(chunk_logprobs, dim=-1)
|
||||
# Free the raw logits before the out-of-place log_softmax:
|
||||
# keeping all three alive is a 3x peak, which OOMs when the
|
||||
# single chunk covers a large batch.
|
||||
chunk_log_normalizer = None
|
||||
chunk_logprobs = torch.nn.functional.log_softmax(
|
||||
chunk_logprobs, dim=-1
|
||||
)
|
||||
|
||||
# Get the logprob of top-k tokens
|
||||
if logits_metadata.extend_return_top_logprob:
|
||||
top_k_nums = logits_metadata.top_logprobs_nums[chunk_slice]
|
||||
pruned_lens = logits_metadata.extend_logprob_pruned_lens_cpu[
|
||||
chunk_slice
|
||||
# Get the logprob of top-k tokens
|
||||
if logits_metadata.extend_return_top_logprob:
|
||||
top_k_nums = logits_metadata.top_logprobs_nums[chunk_slice]
|
||||
pruned_lens = logits_metadata.extend_logprob_pruned_lens_cpu[
|
||||
chunk_slice
|
||||
]
|
||||
split_len_topk = get_top_logprobs_chunk(
|
||||
chunk_logprobs,
|
||||
top_k_nums,
|
||||
pruned_lens,
|
||||
top_logprobs_val,
|
||||
top_logprobs_idx,
|
||||
split_len_topk,
|
||||
log_normalizer=chunk_log_normalizer,
|
||||
precomputed_topk=chunk_precomputed_topk,
|
||||
copy_to_pinned_cpu=borrow_logprob_memory,
|
||||
)
|
||||
|
||||
# Get the logprob of given token id
|
||||
if logits_metadata.extend_token_ids_logprob:
|
||||
token_ids_logprobs = logits_metadata.token_ids_logprobs[chunk_slice]
|
||||
pruned_lens = logits_metadata.extend_logprob_pruned_lens_cpu[
|
||||
chunk_slice
|
||||
]
|
||||
split_len_token_ids = get_token_ids_logprobs_chunk(
|
||||
chunk_logprobs,
|
||||
token_ids_logprobs,
|
||||
pruned_lens,
|
||||
token_ids_logprobs_val,
|
||||
token_ids_logprobs_idx,
|
||||
split_len_token_ids,
|
||||
log_normalizer=chunk_log_normalizer,
|
||||
copy_to_pinned_cpu=borrow_logprob_memory,
|
||||
)
|
||||
|
||||
# Get the logprob of the requested token ids
|
||||
chunk_token_logprobs = chunk_logprobs[
|
||||
torch.arange(chunk_logprobs.shape[0], device=chunk_logprobs.device),
|
||||
logits_metadata.extend_input_logprob_token_ids_gpu[lp_lo:lp_hi],
|
||||
]
|
||||
split_len_topk = get_top_logprobs_chunk(
|
||||
chunk_logprobs,
|
||||
top_k_nums,
|
||||
pruned_lens,
|
||||
top_logprobs_val,
|
||||
top_logprobs_idx,
|
||||
split_len_topk,
|
||||
log_normalizer=chunk_log_normalizer,
|
||||
precomputed_topk=chunk_precomputed_topk,
|
||||
)
|
||||
|
||||
# Get the logprob of given token id
|
||||
if logits_metadata.extend_token_ids_logprob:
|
||||
token_ids_logprobs = logits_metadata.token_ids_logprobs[chunk_slice]
|
||||
pruned_lens = logits_metadata.extend_logprob_pruned_lens_cpu[
|
||||
chunk_slice
|
||||
]
|
||||
split_len_token_ids = get_token_ids_logprobs_chunk(
|
||||
chunk_logprobs,
|
||||
token_ids_logprobs,
|
||||
pruned_lens,
|
||||
token_ids_logprobs_val,
|
||||
token_ids_logprobs_idx,
|
||||
split_len_token_ids,
|
||||
log_normalizer=chunk_log_normalizer,
|
||||
)
|
||||
|
||||
# Get the logprob of the requested token ids
|
||||
chunk_token_logprobs = chunk_logprobs[
|
||||
torch.arange(chunk_logprobs.shape[0], device=chunk_logprobs.device),
|
||||
logits_metadata.extend_input_logprob_token_ids_gpu[mask_indices],
|
||||
]
|
||||
if chunk_log_normalizer is not None:
|
||||
row_max, row_log_sum = chunk_log_normalizer
|
||||
chunk_token_logprobs = (
|
||||
chunk_token_logprobs.float() - row_max
|
||||
) - row_log_sum
|
||||
token_logprobs.append(chunk_token_logprobs)
|
||||
# Free before the next chunk's logits (bf16 + fp32) materialize.
|
||||
del chunk_logprobs
|
||||
if chunk_log_normalizer is not None:
|
||||
row_max, row_log_sum = chunk_log_normalizer
|
||||
chunk_token_logprobs = (
|
||||
chunk_token_logprobs.float() - row_max
|
||||
) - row_log_sum
|
||||
if borrow_logprob_memory:
|
||||
token_logprobs[lp_lo:lp_hi].copy_(
|
||||
chunk_token_logprobs, non_blocking=True
|
||||
)
|
||||
else:
|
||||
token_logprobs.append(chunk_token_logprobs)
|
||||
# Free before the next chunk's logits (bf16 + fp32) materialize.
|
||||
del chunk_logprobs
|
||||
|
||||
# Restore the full-pruned lm_head batch_info after chunk iteration.
|
||||
if num_chunks > 1 and hasattr(lm_head, "reset_lm_head_pass"):
|
||||
@@ -730,8 +793,14 @@ class InputLogprobProcessor:
|
||||
)
|
||||
lm_head.reset_lm_head_pass()
|
||||
|
||||
# Concatenate the results
|
||||
token_logprobs = torch.cat(token_logprobs, dim=0)
|
||||
input_copy_done = None
|
||||
if borrow_logprob_memory:
|
||||
# The copies and the next replay share the forward stream.
|
||||
input_copy_done = torch.cuda.Event()
|
||||
input_copy_done.record(torch.cuda.current_stream(pruned_states.device))
|
||||
else:
|
||||
# Concatenate the results
|
||||
token_logprobs = torch.cat(token_logprobs, dim=0)
|
||||
|
||||
return (
|
||||
LogprobResult(
|
||||
@@ -740,6 +809,7 @@ class InputLogprobProcessor:
|
||||
top_logprobs_idx=top_logprobs_idx,
|
||||
token_ids_logprobs_val=token_ids_logprobs_val,
|
||||
token_ids_logprobs_idx=token_ids_logprobs_idx,
|
||||
input_copy_done=input_copy_done,
|
||||
),
|
||||
sampled_logits,
|
||||
)
|
||||
|
||||
@@ -519,6 +519,7 @@ class SchedulerBatchResultProcessor:
|
||||
logits_output: LogitsProcessorOutput,
|
||||
) -> None:
|
||||
if batch.return_logprob:
|
||||
logits_output.finalize_input_logprobs()
|
||||
if logits_output.next_token_logprobs is not None:
|
||||
logits_output.next_token_logprobs = (
|
||||
logits_output.next_token_logprobs.tolist()
|
||||
|
||||
@@ -21,6 +21,7 @@ from sglang.srt.model_executor.forward_batch_info import PPProxyTensors
|
||||
from sglang.srt.runtime_context import get_spec, max_speculative_num_draft_tokens
|
||||
from sglang.srt.speculative.spec_info import SpeculativeAlgorithm
|
||||
from sglang.srt.state_capturer.base import TopkCaptureOutput
|
||||
from sglang.srt.utils.common import async_d2h as _async_d2h
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sglang.srt.managers.auxiliary_output import HostAuxiliaryOutput
|
||||
@@ -45,19 +46,6 @@ def allocate_distinct_stream(device_module, avoid_streams):
|
||||
raise RuntimeError("Unable to allocate a distinct stream")
|
||||
|
||||
|
||||
def _async_d2h(t: torch.Tensor) -> torch.Tensor:
|
||||
"""Async D2H copy for overlap scheduling. On CUDA the dest is pinned (a D2H
|
||||
to pageable host memory blocks the caller until done) and record_stream keeps
|
||||
the source alive until the copy stream drains, so the caching allocator can't
|
||||
recycle it early. Non-CUDA falls back to a plain copy."""
|
||||
if not t.is_cuda:
|
||||
return t.to("cpu", non_blocking=True)
|
||||
cpu_t = torch.empty(t.shape, dtype=t.dtype, pin_memory=True)
|
||||
cpu_t.copy_(t, non_blocking=True)
|
||||
t.record_stream(torch.cuda.current_stream(t.device))
|
||||
return cpu_t
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class GenerationBatchResult:
|
||||
logits_output: Optional[LogitsProcessorOutput] = None
|
||||
@@ -292,6 +280,9 @@ def get_logprob_dict_from_result(result: GenerationBatchResult) -> dict:
|
||||
|
||||
logits_output = result.logits_output
|
||||
assert logits_output is not None
|
||||
# Nested pinned CPU tensors are serialized as Python metadata by PP, so
|
||||
# their forward-stream copies must be complete before pickling starts.
|
||||
logits_output.finalize_input_logprobs()
|
||||
sampling_mask_output = logits_output.sampling_mask_output
|
||||
|
||||
return {
|
||||
|
||||
@@ -2052,6 +2052,7 @@ class PrefillCudaGraphRunner(BaseCudaGraphRunner):
|
||||
input_top_logprobs_idx=output.input_top_logprobs_idx,
|
||||
input_token_ids_logprobs_val=output.input_token_ids_logprobs_val,
|
||||
input_token_ids_logprobs_idx=output.input_token_ids_logprobs_idx,
|
||||
input_logprobs_copy_done=output.input_logprobs_copy_done,
|
||||
mm_input_embeds=mm_input_embeds,
|
||||
)
|
||||
|
||||
|
||||
@@ -558,6 +558,16 @@ def is_pin_memory_available(device=None) -> bool:
|
||||
return current_platform.is_pin_memory_available(device)
|
||||
|
||||
|
||||
def async_d2h(tensor: torch.Tensor) -> torch.Tensor:
|
||||
"""Enqueue a CUDA-to-pinned-host copy on the current stream."""
|
||||
if not tensor.is_cuda:
|
||||
return tensor.to("cpu", non_blocking=True)
|
||||
host = torch.empty(tensor.shape, dtype=tensor.dtype, pin_memory=True)
|
||||
host.copy_(tensor, non_blocking=True)
|
||||
tensor.record_stream(torch.cuda.current_stream(tensor.device))
|
||||
return host
|
||||
|
||||
|
||||
def get_dispatch_device_backend():
|
||||
if is_cuda_alike():
|
||||
dispatch_key = "CUDA"
|
||||
|
||||
Reference in New Issue
Block a user