94057c3d3e
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
1142 lines
39 KiB
Python
1142 lines
39 KiB
Python
from __future__ import annotations
|
|
|
|
import copy
|
|
import dataclasses
|
|
import logging
|
|
from dataclasses import replace
|
|
from typing import TYPE_CHECKING, Dict, List, Optional, Sequence
|
|
|
|
import torch
|
|
|
|
from sglang.srt.batch_overlap.operations import (
|
|
execute_operations,
|
|
execute_overlapped_operations,
|
|
)
|
|
from sglang.srt.batch_overlap.operations_strategy import OperationsStrategy
|
|
from sglang.srt.layers import deep_gemm_wrapper
|
|
from sglang.srt.layers.communicator import (
|
|
CommunicateContext,
|
|
CommunicateSummableTensorPairFn,
|
|
ScatterMode,
|
|
)
|
|
from sglang.srt.layers.moe import (
|
|
get_deepep_mode,
|
|
get_moe_a2a_backend,
|
|
get_tbo_token_distribution_threshold,
|
|
is_tbo_enabled,
|
|
)
|
|
from sglang.srt.layers.moe.token_dispatcher import (
|
|
DeepEPDispatcher,
|
|
MooncakeEPDispatcher,
|
|
MoriEPDispatcher,
|
|
NixlEPDispatcher,
|
|
)
|
|
from sglang.srt.layers.moe.token_dispatcher.base import BaseDispatcher
|
|
from sglang.srt.managers.schedule_batch import ScheduleBatch
|
|
from sglang.srt.model_executor.forward_batch_info import (
|
|
ForwardBatch,
|
|
ForwardMode,
|
|
compute_position,
|
|
)
|
|
from sglang.srt.model_executor.forward_context import get_attn_backend
|
|
from sglang.srt.runtime_context import get_parallel, get_server_args
|
|
from sglang.srt.speculative.spec_info import SpecInput
|
|
from sglang.srt.utils import BumpAllocator, empty_context, get_bool_env_var, is_hip
|
|
|
|
if TYPE_CHECKING:
|
|
from sglang.srt.batch_overlap.single_batch_overlap import CombineOverlapArgs
|
|
from sglang.srt.layers.moe.token_dispatcher import DispatchOutput
|
|
from sglang.srt.speculative.eagle_info import EagleVerifyInput
|
|
|
|
_is_hip = is_hip()
|
|
|
|
_tbo_debug = get_bool_env_var("SGLANG_TBO_DEBUG")
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
# -------------------------------- Compute Basic Info ---------------------------------------
|
|
|
|
|
|
def get_token_num_per_seq(
|
|
forward_mode: ForwardMode,
|
|
spec_info: Optional[SpecInput] = None,
|
|
):
|
|
if forward_mode.is_target_verify():
|
|
return spec_info.draft_token_num
|
|
elif forward_mode.is_decode():
|
|
return 1
|
|
elif forward_mode.is_idle():
|
|
return 0
|
|
else:
|
|
# For extend, we should not use `token_num_per_seq`.
|
|
return None
|
|
|
|
|
|
# TODO: may smartly disable TBO when batch size is too small b/c it will slow down
|
|
def compute_split_seq_index(
|
|
forward_mode: ForwardMode,
|
|
num_tokens: int,
|
|
extend_lens: Optional[Sequence[int]],
|
|
token_num_per_seq: Optional[int],
|
|
) -> Optional[int]:
|
|
if forward_mode == ForwardMode.EXTEND or forward_mode == ForwardMode.MIXED:
|
|
assert extend_lens is not None
|
|
return _split_extend_seqs(extend_lens)
|
|
elif forward_mode.is_target_verify() or forward_mode.is_decode():
|
|
assert token_num_per_seq is not None
|
|
return (num_tokens // token_num_per_seq) // 2
|
|
elif forward_mode.is_idle() or forward_mode.is_prebuilt():
|
|
assert num_tokens == 0
|
|
return 0
|
|
else:
|
|
raise NotImplementedError()
|
|
|
|
|
|
def _is_two_chunk_split_enabled(extend_lens: Sequence[int]) -> bool:
|
|
if extend_lens is None:
|
|
return False
|
|
|
|
vanilla_split_seq_index = _split_array_by_balanced_sum(extend_lens)
|
|
left_sum = sum(extend_lens[:vanilla_split_seq_index])
|
|
overall_sum = sum(extend_lens)
|
|
threshold = get_tbo_token_distribution_threshold()
|
|
assert threshold <= 0.5, f"{threshold=}"
|
|
want_two_chunk = left_sum < overall_sum * threshold or left_sum > overall_sum * (
|
|
1 - threshold
|
|
)
|
|
if not want_two_chunk:
|
|
return False
|
|
|
|
# Two-chunk splits a single seq across both micro-batches by cutting at
|
|
# overall_sum // 2. child_a then spans seqs [0 : split_seq_index + 1]
|
|
# (batch_size = split_seq_index + 1) but only receives overall_sum // 2
|
|
# query tokens. For a degenerate batch (a single seq, or a near-empty
|
|
# DP-sync batch) this cut is 0 or tiny, leaving child_a with more seqs
|
|
# than query tokens (e.g. (bs=1, tok=0)). That violates the DSV4 compress
|
|
# planner invariant `batch_size <= num_q_tokens` and crashes the kernel.
|
|
# Fall back to a seq-boundary split, whose child_a is seq-aligned (each
|
|
# seq contributes >= 1 token) and cannot become empty-with-count.
|
|
split_seq_index = _split_array_by_cum_less_than_half(extend_lens)
|
|
child_a_batch_size = split_seq_index + 1
|
|
child_a_num_q_tokens = overall_sum // 2
|
|
if child_a_batch_size > child_a_num_q_tokens:
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def _split_extend_seqs(arr: Sequence[int]) -> int:
|
|
if _is_two_chunk_split_enabled(arr):
|
|
return _split_array_by_cum_less_than_half(arr)
|
|
|
|
return _split_array_by_balanced_sum(arr)
|
|
|
|
|
|
def _split_array_by_cum_less_than_half(arr: Sequence[int]) -> int:
|
|
left_sum = 0
|
|
overall_sum = sum(arr)
|
|
half_sum = overall_sum // 2
|
|
chosen_index = 0
|
|
|
|
for i in range(len(arr)):
|
|
left_sum += arr[i]
|
|
if left_sum > half_sum:
|
|
chosen_index = i
|
|
break
|
|
|
|
return chosen_index
|
|
|
|
|
|
def _split_array_by_balanced_sum(arr: Sequence[int]) -> int:
|
|
overall_sum = sum(arr)
|
|
left_sum = 0
|
|
min_diff = float("inf")
|
|
best_index = 0
|
|
|
|
for i in range(1, len(arr)):
|
|
left_sum += arr[i - 1]
|
|
right_sum = overall_sum - left_sum
|
|
diff = abs(left_sum - right_sum)
|
|
if diff <= min_diff:
|
|
min_diff = diff
|
|
best_index = i
|
|
else:
|
|
break
|
|
|
|
return best_index
|
|
|
|
|
|
def _update_device_and_sum_field_from_cpu_field(
|
|
batch: ForwardBatch, cpu_field: str, device_field: str, sum_field: str = None
|
|
):
|
|
cpu_value = getattr(batch, cpu_field, None)
|
|
old_device_value = getattr(batch, device_field, None)
|
|
if (
|
|
cpu_value is None
|
|
or old_device_value is None
|
|
or not (isinstance(cpu_value, torch.Tensor) or isinstance(cpu_value, list))
|
|
):
|
|
return
|
|
|
|
new_device_value = (
|
|
cpu_value
|
|
if isinstance(cpu_value, torch.Tensor)
|
|
else torch.tensor(cpu_value, dtype=old_device_value.dtype)
|
|
).to(device=get_server_args().device, non_blocking=True)
|
|
setattr(batch, device_field, new_device_value)
|
|
|
|
if sum_field is not None:
|
|
sum_value = (
|
|
cpu_value.sum().item()
|
|
if isinstance(cpu_value, torch.Tensor)
|
|
else sum(cpu_value)
|
|
)
|
|
setattr(batch, sum_field, sum_value)
|
|
|
|
|
|
def _compute_mask_offset(seq_index: int, spec_info: Optional[EagleVerifyInput]) -> int:
|
|
if seq_index == 0:
|
|
return 0
|
|
|
|
offset = 0
|
|
max_seq_len = min(seq_index, spec_info.seq_lens_cpu.shape[0])
|
|
for i in range(max_seq_len):
|
|
offset += (
|
|
spec_info.seq_lens_cpu[i] + spec_info.draft_token_num
|
|
) * spec_info.draft_token_num
|
|
return offset
|
|
|
|
|
|
def split_spec_info(
|
|
spec_info: Optional[EagleVerifyInput],
|
|
start_seq_index: int,
|
|
end_seq_index: int,
|
|
start_token_index: int,
|
|
end_token_index: int,
|
|
):
|
|
if spec_info is None:
|
|
return None
|
|
if spec_info.draft_token is not None:
|
|
draft_token = spec_info.draft_token[start_token_index:end_token_index]
|
|
else:
|
|
draft_token = None
|
|
if spec_info.custom_mask is not None and spec_info.draft_token is not None:
|
|
custom_mask_start = _compute_mask_offset(start_seq_index, spec_info)
|
|
if end_seq_index == spec_info.seq_lens_cpu.shape[0]:
|
|
custom_mask_end = spec_info.custom_mask.shape[0]
|
|
else:
|
|
custom_mask_end = _compute_mask_offset(end_seq_index, spec_info)
|
|
|
|
if custom_mask_end > custom_mask_start:
|
|
custom_mask = spec_info.custom_mask[custom_mask_start:custom_mask_end]
|
|
else:
|
|
custom_mask = spec_info.custom_mask
|
|
else:
|
|
custom_mask = spec_info.custom_mask
|
|
if spec_info.positions is not None:
|
|
positions = spec_info.positions[start_token_index:end_token_index]
|
|
else:
|
|
positions = None
|
|
if spec_info.retrieve_index is not None:
|
|
retrieve_index = spec_info.retrieve_index[start_seq_index:end_seq_index]
|
|
else:
|
|
retrieve_index = None
|
|
if spec_info.retrieve_next_token is not None:
|
|
retrieve_next_token = spec_info.retrieve_next_token[
|
|
start_seq_index:end_seq_index
|
|
]
|
|
else:
|
|
retrieve_next_token = None
|
|
if spec_info.retrieve_next_sibling is not None:
|
|
retrieve_next_sibling = spec_info.retrieve_next_sibling[
|
|
start_seq_index:end_seq_index
|
|
]
|
|
else:
|
|
retrieve_next_sibling = None
|
|
if spec_info.retrieve_cum_len is not None:
|
|
retrieve_cum_len = spec_info.retrieve_cum_len[start_seq_index:end_seq_index]
|
|
else:
|
|
retrieve_cum_len = None
|
|
|
|
if spec_info.seq_lens_cpu is not None:
|
|
seq_lens_cpu = spec_info.seq_lens_cpu[start_seq_index:end_seq_index]
|
|
else:
|
|
seq_lens_cpu = None
|
|
if seq_lens_cpu is not None:
|
|
seq_lens_sum = seq_lens_cpu.sum()
|
|
else:
|
|
seq_lens_sum = None
|
|
output_spec_info = replace(
|
|
spec_info,
|
|
custom_mask=custom_mask,
|
|
draft_token=draft_token,
|
|
positions=positions,
|
|
retrieve_index=retrieve_index,
|
|
retrieve_next_token=retrieve_next_token,
|
|
retrieve_next_sibling=retrieve_next_sibling,
|
|
retrieve_cum_len=retrieve_cum_len,
|
|
seq_lens_cpu=seq_lens_cpu,
|
|
seq_lens_sum=seq_lens_sum,
|
|
)
|
|
return output_spec_info
|
|
|
|
|
|
def compute_split_token_index(
|
|
split_seq_index: int,
|
|
forward_mode: ForwardMode,
|
|
extend_seq_lens: Optional[Sequence[int]],
|
|
token_num_per_seq: Optional[int],
|
|
) -> int:
|
|
if forward_mode == ForwardMode.EXTEND or forward_mode == ForwardMode.MIXED:
|
|
assert extend_seq_lens is not None
|
|
if _is_two_chunk_split_enabled(extend_seq_lens):
|
|
return sum(extend_seq_lens) // 2
|
|
return sum(extend_seq_lens[:split_seq_index])
|
|
elif forward_mode.is_target_verify() or forward_mode.is_decode():
|
|
assert token_num_per_seq is not None
|
|
return split_seq_index * token_num_per_seq
|
|
elif forward_mode.is_idle():
|
|
assert split_seq_index == 0
|
|
return 0
|
|
else:
|
|
raise NotImplementedError
|
|
|
|
|
|
def compute_split_indices_for_cuda_graph_replay(
|
|
forward_mode: ForwardMode,
|
|
cuda_graph_num_tokens: int,
|
|
spec_info: Optional[SpecInput],
|
|
):
|
|
forward_mode_for_tbo_split = (
|
|
forward_mode if forward_mode != ForwardMode.IDLE else ForwardMode.DECODE
|
|
)
|
|
token_num_per_seq = get_token_num_per_seq(
|
|
forward_mode=forward_mode, spec_info=spec_info
|
|
)
|
|
tbo_split_seq_index = compute_split_seq_index(
|
|
forward_mode=forward_mode_for_tbo_split,
|
|
num_tokens=cuda_graph_num_tokens,
|
|
extend_lens=None,
|
|
token_num_per_seq=token_num_per_seq,
|
|
)
|
|
tbo_split_token_index = compute_split_token_index(
|
|
split_seq_index=tbo_split_seq_index,
|
|
forward_mode=forward_mode_for_tbo_split,
|
|
extend_seq_lens=None,
|
|
token_num_per_seq=token_num_per_seq,
|
|
)
|
|
return tbo_split_seq_index, tbo_split_token_index
|
|
|
|
|
|
# -------------------------------- Preparation ---------------------------------------
|
|
|
|
|
|
class TboCudaGraphRunnerPlugin:
|
|
def __init__(self):
|
|
self._tbo_children_num_token_non_padded = torch.zeros(
|
|
(2,), dtype=torch.int32, device=get_server_args().device
|
|
)
|
|
|
|
def capture_one_batch_size(self, batch: ForwardBatch, num_tokens: int):
|
|
if not is_tbo_enabled():
|
|
return
|
|
token_num_per_seq = get_token_num_per_seq(
|
|
forward_mode=batch.forward_mode, spec_info=batch.spec_info
|
|
)
|
|
|
|
batch.tbo_split_seq_index = compute_split_seq_index(
|
|
forward_mode=batch.forward_mode,
|
|
num_tokens=num_tokens,
|
|
extend_lens=None,
|
|
token_num_per_seq=token_num_per_seq,
|
|
)
|
|
# For simplicity, when two_batch_overlap is enabled, we only capture CUDA Graph for tbo=true
|
|
assert batch.tbo_split_seq_index is not None, f"{num_tokens=}"
|
|
|
|
self._tbo_children_num_token_non_padded[...] = (
|
|
TboForwardBatchPreparer.compute_tbo_children_num_token_non_padded(batch)
|
|
)
|
|
|
|
TboForwardBatchPreparer.prepare_raw(
|
|
batch,
|
|
tbo_children_num_token_non_padded=self._tbo_children_num_token_non_padded,
|
|
)
|
|
|
|
def replay_prepare(
|
|
self,
|
|
forward_mode: ForwardMode,
|
|
bs: int,
|
|
num_token_non_padded: int,
|
|
spec_info: Optional[SpecInput],
|
|
):
|
|
token_num_per_seq = get_token_num_per_seq(
|
|
forward_mode=forward_mode, spec_info=spec_info
|
|
)
|
|
tbo_split_seq_index, tbo_split_token_index = (
|
|
compute_split_indices_for_cuda_graph_replay(
|
|
forward_mode=forward_mode,
|
|
cuda_graph_num_tokens=bs * token_num_per_seq,
|
|
spec_info=spec_info,
|
|
)
|
|
)
|
|
|
|
self._tbo_children_num_token_non_padded[...] = (
|
|
TboForwardBatchPreparer.compute_tbo_children_num_token_non_padded_raw(
|
|
tbo_split_token_index=tbo_split_token_index,
|
|
num_token_non_padded=num_token_non_padded,
|
|
)
|
|
)
|
|
|
|
|
|
class TboDPAttentionPreparer:
|
|
def prepare_all_gather(
|
|
self,
|
|
local_batch: ScheduleBatch,
|
|
):
|
|
|
|
deepep_mode = get_deepep_mode()
|
|
enable_a2a_moe = not get_moe_a2a_backend().is_none()
|
|
enable_two_batch_overlap = is_tbo_enabled()
|
|
|
|
self.enable_two_batch_overlap = enable_two_batch_overlap
|
|
|
|
# Short-circuit when TBO is off: prepare_mlp_sync_batch_raw invokes
|
|
# this preparer unconditionally for the forward_mode all-gather, but
|
|
# compute_split_seq_index is TBO-only and undefined for some modes
|
|
# (e.g. MIXED from enable_mixed_chunk).
|
|
if not enable_two_batch_overlap:
|
|
self.local_tbo_split_seq_index = None
|
|
return False, self._compute_local_forward_mode(local_batch)
|
|
|
|
if local_batch is not None:
|
|
token_num_per_seq = get_token_num_per_seq(
|
|
forward_mode=local_batch.forward_mode, spec_info=local_batch.spec_info
|
|
)
|
|
|
|
if (
|
|
local_batch.forward_mode.is_target_verify()
|
|
or local_batch.forward_mode.is_decode()
|
|
):
|
|
num_tokens = local_batch.batch_size() * token_num_per_seq
|
|
elif local_batch.forward_mode.is_prebuilt():
|
|
num_tokens = 0
|
|
else:
|
|
num_tokens = local_batch.extend_num_tokens
|
|
self.local_tbo_split_seq_index = compute_split_seq_index(
|
|
forward_mode=local_batch.forward_mode,
|
|
num_tokens=num_tokens,
|
|
extend_lens=local_batch.extend_lens,
|
|
token_num_per_seq=token_num_per_seq,
|
|
)
|
|
resolved_deepep_mode = deepep_mode.resolve(local_batch.is_extend_in_batch)
|
|
local_can_run_tbo = (self.local_tbo_split_seq_index is not None) and not (
|
|
(
|
|
local_batch.forward_mode.is_extend()
|
|
and not local_batch.forward_mode.is_target_verify()
|
|
)
|
|
and enable_a2a_moe
|
|
and (resolved_deepep_mode.is_low_latency())
|
|
)
|
|
else:
|
|
self.local_tbo_split_seq_index = 0
|
|
local_can_run_tbo = True
|
|
|
|
local_forward_mode = self._compute_local_forward_mode(local_batch)
|
|
|
|
return local_can_run_tbo, local_forward_mode
|
|
|
|
def compute_output(self, partial_global_info):
|
|
# Perform only one Device-to-Host (D2H) memory copy
|
|
cpu_data = partial_global_info[:, :2].cpu()
|
|
local_can_run_tbo_aggregated = min(cpu_data[:, 0].tolist())
|
|
forward_modes = cpu_data[:, 1].tolist()
|
|
|
|
global_forward_mode, forward_mode_agree = self._compute_global_forward_mode(
|
|
forward_modes
|
|
)
|
|
|
|
can_run_tbo = (
|
|
self.enable_two_batch_overlap
|
|
and local_can_run_tbo_aggregated
|
|
and forward_mode_agree
|
|
)
|
|
|
|
tbo_split_seq_index = self.local_tbo_split_seq_index if can_run_tbo else None
|
|
global_forward_mode = global_forward_mode if can_run_tbo else None
|
|
return tbo_split_seq_index, global_forward_mode
|
|
|
|
@staticmethod
|
|
def _compute_local_forward_mode(local_batch):
|
|
return (
|
|
local_batch.forward_mode if local_batch is not None else ForwardMode.IDLE
|
|
).value
|
|
|
|
@staticmethod
|
|
def _compute_global_forward_mode(forward_modes):
|
|
forward_modes_excluding_idle_and_prebuilt = [
|
|
x
|
|
for x in forward_modes
|
|
if x != ForwardMode.IDLE.value and x != ForwardMode.PREBUILT.value
|
|
]
|
|
|
|
if not forward_modes_excluding_idle_and_prebuilt:
|
|
return ForwardMode.IDLE, False
|
|
|
|
forward_mode_agree = TboDPAttentionPreparer._is_all_same(
|
|
forward_modes_excluding_idle_and_prebuilt
|
|
)
|
|
|
|
global_forward_mode = (
|
|
ForwardMode(forward_modes_excluding_idle_and_prebuilt[0])
|
|
if forward_mode_agree
|
|
else None
|
|
)
|
|
return global_forward_mode, forward_mode_agree
|
|
|
|
@staticmethod
|
|
def _is_all_same(x):
|
|
return all(value == x[0] for value in x)
|
|
|
|
|
|
class TboForwardBatchPreparer:
|
|
@classmethod
|
|
def prepare(cls, batch: ForwardBatch, is_draft_worker: bool = False):
|
|
if batch.tbo_split_seq_index is None or is_draft_worker:
|
|
return
|
|
|
|
tbo_children_num_token_non_padded = (
|
|
cls.compute_tbo_children_num_token_non_padded(batch)
|
|
)
|
|
cls.prepare_raw(
|
|
batch, tbo_children_num_token_non_padded=tbo_children_num_token_non_padded
|
|
)
|
|
|
|
@classmethod
|
|
def prepare_raw(
|
|
cls, batch: ForwardBatch, tbo_children_num_token_non_padded: torch.Tensor
|
|
):
|
|
from sglang.srt.layers.attention.tbo_backend import TboAttnBackend
|
|
|
|
tbo_split_token_index = cls._compute_split_token_index(batch)
|
|
|
|
is_enable_two_chunk = (
|
|
batch.forward_mode == ForwardMode.EXTEND
|
|
and _is_two_chunk_split_enabled(batch.extend_seq_lens_cpu)
|
|
)
|
|
|
|
if _tbo_debug:
|
|
logger.info(
|
|
f"TboForwardBatchPreparer.prepare "
|
|
f"is_enable_two_chunk={is_enable_two_chunk} "
|
|
f"tbo_split_seq_index={batch.tbo_split_seq_index} "
|
|
f"tbo_split_token_index={tbo_split_token_index} "
|
|
f"extend_seq_lens={batch.extend_seq_lens_cpu} "
|
|
f"bs={batch.batch_size} "
|
|
f"forward_mode={batch.forward_mode}"
|
|
)
|
|
|
|
# Sanity check: the global attn_backend should be a TboAttnBackend
|
|
# whose children handle the two halves.
|
|
attn_backend = get_attn_backend()
|
|
assert isinstance(attn_backend, TboAttnBackend)
|
|
|
|
[out_num_token_non_padded_a, out_num_token_non_padded_b] = (
|
|
tbo_children_num_token_non_padded
|
|
)
|
|
|
|
child_a = cls.filter_batch(
|
|
batch,
|
|
start_token_index=0,
|
|
end_token_index=tbo_split_token_index,
|
|
start_seq_index=0,
|
|
end_seq_index=(
|
|
batch.tbo_split_seq_index + 1
|
|
if is_enable_two_chunk
|
|
else batch.tbo_split_seq_index
|
|
),
|
|
out_num_token_non_padded=out_num_token_non_padded_a,
|
|
)
|
|
child_b = cls.filter_batch(
|
|
batch,
|
|
start_token_index=tbo_split_token_index,
|
|
end_token_index=batch.input_ids.shape[0],
|
|
start_seq_index=batch.tbo_split_seq_index,
|
|
end_seq_index=batch.batch_size,
|
|
out_num_token_non_padded=out_num_token_non_padded_b,
|
|
)
|
|
|
|
if is_enable_two_chunk:
|
|
cls.derive_fields_related_to_seq_len_for_two_chunk(
|
|
batch,
|
|
child_a=child_a,
|
|
child_b=child_b,
|
|
tbo_split_seq_index=batch.tbo_split_seq_index,
|
|
)
|
|
|
|
assert batch.tbo_children is None
|
|
batch.tbo_children = [child_a, child_b]
|
|
|
|
@classmethod
|
|
def derive_fields_related_to_seq_len_for_two_chunk(
|
|
cls,
|
|
batch: ForwardBatch,
|
|
*,
|
|
child_a: ForwardBatch,
|
|
child_b: ForwardBatch,
|
|
tbo_split_seq_index: int,
|
|
):
|
|
extend_seq_lens_cpu = batch.extend_seq_lens_cpu
|
|
overall_seq_lens_sum = sum(extend_seq_lens_cpu)
|
|
half_seq_lens_sum = overall_seq_lens_sum // 2
|
|
left_last_seq_token_num = half_seq_lens_sum - sum(
|
|
extend_seq_lens_cpu[:tbo_split_seq_index]
|
|
)
|
|
right_first_seq_token_num = (
|
|
extend_seq_lens_cpu[tbo_split_seq_index] - left_last_seq_token_num
|
|
)
|
|
|
|
# making deepcopy to be extra safe
|
|
child_a.extend_seq_lens_cpu = copy.deepcopy(child_a.extend_seq_lens_cpu)
|
|
child_a.extend_seq_lens_cpu[-1] = left_last_seq_token_num
|
|
child_b.extend_seq_lens_cpu = copy.deepcopy(child_b.extend_seq_lens_cpu)
|
|
child_b.extend_seq_lens_cpu[0] = right_first_seq_token_num
|
|
for child in [child_a, child_b]:
|
|
_update_device_and_sum_field_from_cpu_field(
|
|
batch=child,
|
|
cpu_field="extend_seq_lens_cpu",
|
|
device_field="extend_seq_lens",
|
|
sum_field="extend_num_tokens",
|
|
)
|
|
|
|
assert (
|
|
child_a.extend_num_tokens == half_seq_lens_sum
|
|
), f"{child_a.extend_num_tokens=}, {half_seq_lens_sum=}"
|
|
|
|
child_a.seq_lens_cpu = copy.deepcopy(child_a.seq_lens_cpu)
|
|
child_a.seq_lens_cpu[-1] = (
|
|
child_a.extend_seq_lens_cpu[-1] + child_a.extend_prefix_lens_cpu[-1]
|
|
)
|
|
_update_device_and_sum_field_from_cpu_field(
|
|
batch=child_a,
|
|
cpu_field="seq_lens_cpu",
|
|
device_field="seq_lens",
|
|
sum_field="seq_lens_sum",
|
|
)
|
|
|
|
child_b.extend_prefix_lens_cpu = copy.deepcopy(child_b.extend_prefix_lens_cpu)
|
|
child_b.extend_prefix_lens_cpu[0] += left_last_seq_token_num
|
|
_update_device_and_sum_field_from_cpu_field(
|
|
batch=child_b,
|
|
cpu_field="extend_prefix_lens_cpu",
|
|
device_field="extend_prefix_lens",
|
|
sum_field=None,
|
|
)
|
|
_, child_b.extend_start_loc = compute_position(
|
|
get_server_args().attention_backend,
|
|
child_b.extend_prefix_lens,
|
|
child_b.extend_seq_lens,
|
|
child_b.extend_num_tokens,
|
|
)
|
|
|
|
@classmethod
|
|
def filter_batch(
|
|
cls,
|
|
batch: ForwardBatch,
|
|
*,
|
|
start_token_index: int,
|
|
end_token_index: int,
|
|
start_seq_index: int,
|
|
end_seq_index: int,
|
|
out_num_token_non_padded: torch.Tensor,
|
|
):
|
|
assert (
|
|
end_token_index >= start_token_index
|
|
), f"{end_token_index=}, {start_token_index=}, batch={batch}"
|
|
num_tokens = batch.input_ids.shape[0]
|
|
num_seqs = batch.batch_size
|
|
|
|
output_dict = dict()
|
|
|
|
for key in [
|
|
"input_ids",
|
|
"positions",
|
|
"out_cache_loc",
|
|
]:
|
|
old_value = getattr(batch, key)
|
|
assert (
|
|
old_value.shape[0] == num_tokens
|
|
), f"{key=} {old_value=} {num_tokens=} {batch=}"
|
|
output_dict[key] = old_value[start_token_index:end_token_index]
|
|
|
|
attention_tp_size = get_parallel().attn_tp_size
|
|
_tbo_padded_len = (
|
|
(end_token_index - start_token_index - 1) // attention_tp_size + 1
|
|
) * attention_tp_size
|
|
output_dict["tbo_padded_len"] = _tbo_padded_len
|
|
|
|
for key in [
|
|
"req_pool_indices",
|
|
"seq_lens",
|
|
"seq_lens_cpu",
|
|
"extend_seq_lens",
|
|
"extend_prefix_lens",
|
|
"extend_start_loc",
|
|
"extend_prefix_lens_cpu",
|
|
"extend_seq_lens_cpu",
|
|
"extend_logprob_start_lens_cpu",
|
|
"lora_ids",
|
|
"rids",
|
|
]:
|
|
old_value = getattr(batch, key)
|
|
if old_value is None:
|
|
continue
|
|
elif batch.forward_mode.is_target_verify() and (
|
|
key == "extend_seq_lens"
|
|
or key == "extend_prefix_lens"
|
|
or key == "extend_start_loc"
|
|
or key == "extend_prefix_lens_cpu"
|
|
or key == "extend_seq_lens_cpu"
|
|
or key == "extend_logprob_start_lens_cpu"
|
|
):
|
|
output_dict[key] = None
|
|
continue
|
|
elif key == "rids" and len(old_value) != num_seqs:
|
|
output_dict[key] = old_value[
|
|
start_seq_index : min(end_seq_index, len(old_value))
|
|
]
|
|
continue
|
|
assert (
|
|
len(old_value) == num_seqs
|
|
), f"{key=} {old_value=} {num_seqs=} {batch=}"
|
|
output_dict[key] = old_value[start_seq_index:end_seq_index]
|
|
|
|
spec_info = getattr(batch, "spec_info")
|
|
output_spec_info = split_spec_info(
|
|
spec_info=spec_info,
|
|
start_token_index=start_token_index,
|
|
end_token_index=end_token_index,
|
|
start_seq_index=start_seq_index,
|
|
end_seq_index=end_seq_index,
|
|
)
|
|
output_dict["spec_info"] = output_spec_info
|
|
for key in [
|
|
"forward_mode",
|
|
"is_extend_in_batch",
|
|
"all_extend_in_batch",
|
|
"return_logprob",
|
|
"can_run_dp_cuda_graph",
|
|
"can_run_dp_breakable_cuda_graph",
|
|
"dp_padding_mode",
|
|
"global_forward_mode",
|
|
"is_prefill_only",
|
|
"spec_algorithm",
|
|
"capture_hidden_mode",
|
|
"padded_static_len",
|
|
"split_index", # for split prefill
|
|
"orig_seq_lens", # only used by qwen-1m, thus not care
|
|
"return_pooled_hidden_states",
|
|
"reuse_dsa_topk_indices", # forward-level flag, inherited by both child batches
|
|
]:
|
|
output_dict[key] = getattr(batch, key)
|
|
|
|
mrope_positions = getattr(batch, "mrope_positions")
|
|
if mrope_positions is not None:
|
|
output_dict["mrope_positions"] = mrope_positions[
|
|
:, start_token_index:end_token_index
|
|
]
|
|
else:
|
|
output_dict["mrope_positions"] = None
|
|
|
|
if not batch.forward_mode.is_target_verify():
|
|
assert (
|
|
_compute_extend_num_tokens(batch.input_ids, batch.forward_mode)
|
|
== batch.extend_num_tokens
|
|
), f"{batch=}"
|
|
extend_num_tokens = _compute_extend_num_tokens(
|
|
output_dict["input_ids"], output_dict["forward_mode"]
|
|
)
|
|
|
|
# TODO improve, e.g. unify w/ `init_raw`
|
|
if (
|
|
get_server_args().moe_dense_tp_size == 1
|
|
and batch.global_dp_buffer_len is not None
|
|
):
|
|
sum_len = end_token_index - start_token_index
|
|
global_dp_buffer_len = sum_len
|
|
else:
|
|
global_dp_buffer_len = None
|
|
|
|
output_dict.update(
|
|
dict(
|
|
batch_size=end_seq_index - start_seq_index,
|
|
seq_lens_sum=(
|
|
output_dict["seq_lens_cpu"].sum()
|
|
if "seq_lens_cpu" in output_dict
|
|
else None
|
|
),
|
|
extend_num_tokens=extend_num_tokens,
|
|
num_token_non_padded=out_num_token_non_padded,
|
|
# TODO: handle it when we need TBO + DeepSeek V3.2
|
|
num_token_non_padded_cpu=None,
|
|
tbo_split_seq_index=None,
|
|
tbo_parent_token_range=(start_token_index, end_token_index),
|
|
tbo_children=None,
|
|
original_global_num_tokens_cpu=None,
|
|
_original_batch_size=None,
|
|
_original_forward_mode=None,
|
|
global_num_tokens_gpu=None,
|
|
global_num_tokens_cpu=None,
|
|
global_dp_buffer_len=global_dp_buffer_len,
|
|
global_num_tokens_for_logprob_gpu=None,
|
|
global_num_tokens_for_logprob_cpu=None,
|
|
sampling_info=None,
|
|
# For logits and logprobs post processing, thus we do not care
|
|
temperature=None,
|
|
top_p=None,
|
|
mm_inputs=None,
|
|
top_logprobs_nums=None,
|
|
token_ids_logprobs=None,
|
|
next_token_logits_buffer=None,
|
|
return_hidden_states_before_norm=False,
|
|
# TBO children start unplanned — planned by the TBO-aware init
|
|
# flow; a stale parent "ready" would wrongly skip that.
|
|
forward_metadata_ready=False,
|
|
forward_metadata_planned_bs=None,
|
|
forward_metadata_planned_num_tokens=None,
|
|
forward_metadata_replan_equivalent=False,
|
|
)
|
|
)
|
|
|
|
errors = []
|
|
for field in dataclasses.fields(ForwardBatch):
|
|
if getattr(batch, field.name) is not None and field.name not in output_dict:
|
|
errors.append(
|
|
f"Field {field.name} has value, but is not yet supported (value={getattr(batch, field.name)} batch={batch})"
|
|
)
|
|
if len(errors) > 0:
|
|
raise Exception(f"{len(errors)} errors happen:\n" + "\n\n".join(errors))
|
|
|
|
return ForwardBatch(**output_dict)
|
|
|
|
@classmethod
|
|
def compute_tbo_children_num_token_non_padded(cls, batch: ForwardBatch):
|
|
return cls.compute_tbo_children_num_token_non_padded_raw(
|
|
tbo_split_token_index=cls._compute_split_token_index(batch),
|
|
num_token_non_padded=len(batch.input_ids),
|
|
)
|
|
|
|
@classmethod
|
|
def compute_tbo_children_num_token_non_padded_raw(
|
|
cls, tbo_split_token_index: int, num_token_non_padded: int
|
|
):
|
|
# TODO we may make padding on both sub-batches to make it slightly more balanced
|
|
value_a = min(tbo_split_token_index, num_token_non_padded)
|
|
value_b = max(0, num_token_non_padded - tbo_split_token_index)
|
|
return torch.tensor([value_a, value_b], dtype=torch.int32).to(
|
|
device=get_server_args().device, non_blocking=True
|
|
)
|
|
|
|
@classmethod
|
|
def _compute_split_token_index(cls, batch: ForwardBatch):
|
|
token_num_per_seq = get_token_num_per_seq(
|
|
forward_mode=batch.forward_mode, spec_info=batch.spec_info
|
|
)
|
|
return compute_split_token_index(
|
|
split_seq_index=batch.tbo_split_seq_index,
|
|
forward_mode=batch.forward_mode,
|
|
extend_seq_lens=batch.extend_seq_lens_cpu,
|
|
token_num_per_seq=token_num_per_seq,
|
|
)
|
|
|
|
|
|
def _compute_extend_num_tokens(input_ids, forward_mode: ForwardMode):
|
|
if (
|
|
forward_mode.is_decode()
|
|
or forward_mode.is_idle()
|
|
or forward_mode.is_target_verify()
|
|
):
|
|
return None
|
|
elif forward_mode.is_extend():
|
|
return input_ids.shape[0]
|
|
raise NotImplementedError
|
|
|
|
|
|
# -------------------------------- Execution ---------------------------------------
|
|
|
|
|
|
def model_forward_maybe_tbo(
|
|
layers,
|
|
enable_tbo: bool,
|
|
positions: torch.Tensor,
|
|
forward_batch: ForwardBatch,
|
|
hidden_states: torch.Tensor,
|
|
input_data_scatter_mode: ScatterMode,
|
|
residual: Optional[torch.Tensor],
|
|
zero_allocator: Optional[BumpAllocator] = None,
|
|
):
|
|
inputs = dict(
|
|
positions=positions,
|
|
hidden_states=hidden_states,
|
|
forward_batch=forward_batch,
|
|
residual=residual,
|
|
zero_allocator=zero_allocator,
|
|
)
|
|
layer_input_scatter_mode = layers[0].layer_scatter_modes.layer_input_mode
|
|
operations_strategy = OperationsStrategy.init_new_tbo(
|
|
layers, forward_batch.global_forward_mode
|
|
)
|
|
if enable_tbo:
|
|
return _model_forward_tbo(
|
|
inputs=inputs,
|
|
operations_strategy=operations_strategy,
|
|
input_data_scatter_mode=input_data_scatter_mode,
|
|
layer_input_scatter_mode=layer_input_scatter_mode,
|
|
)
|
|
else:
|
|
return _model_forward_non_tbo(inputs, operations_strategy)
|
|
|
|
|
|
def _model_forward_tbo(
|
|
inputs,
|
|
operations_strategy: OperationsStrategy,
|
|
input_data_scatter_mode: ScatterMode,
|
|
layer_input_scatter_mode: ScatterMode,
|
|
):
|
|
inputs_arr = _model_forward_tbo_split_inputs(
|
|
**inputs,
|
|
input_data_scatter_mode=input_data_scatter_mode,
|
|
layer_input_scatter_mode=layer_input_scatter_mode,
|
|
)
|
|
original_hidden_states_len = inputs["hidden_states"].shape[0]
|
|
del inputs
|
|
|
|
context = (
|
|
empty_context()
|
|
if _is_hip
|
|
else deep_gemm_wrapper.configure_deep_gemm_num_sms(
|
|
operations_strategy.deep_gemm_num_sms
|
|
)
|
|
)
|
|
|
|
with context:
|
|
outputs_arr = execute_overlapped_operations(
|
|
inputs_arr=inputs_arr,
|
|
operations_arr=[operations_strategy.operations] * 2,
|
|
delta_stages=[0, operations_strategy.tbo_delta_stages],
|
|
)
|
|
|
|
return _model_forward_tbo_merge_outputs(*outputs_arr, original_hidden_states_len)
|
|
|
|
|
|
def _model_forward_non_tbo(inputs, operations_strategy: OperationsStrategy):
|
|
outputs = execute_operations(inputs, operations_strategy.operations)
|
|
return outputs["hidden_states"], outputs["residual"]
|
|
|
|
|
|
def _model_forward_tbo_split_inputs(
|
|
hidden_states: torch.Tensor,
|
|
residual: torch.Tensor,
|
|
positions: torch.Tensor,
|
|
forward_batch: ForwardBatch,
|
|
zero_allocator: Optional[BumpAllocator],
|
|
input_data_scatter_mode: ScatterMode,
|
|
layer_input_scatter_mode: ScatterMode,
|
|
) -> List[Dict]:
|
|
tbo_splitter_scatter_mode = ScatterMode.TP_ATTN_FULL
|
|
context = CommunicateContext.init_new()
|
|
|
|
hidden_states, residual = CommunicateSummableTensorPairFn.execute(
|
|
hidden_states_input_mode=input_data_scatter_mode,
|
|
residual_input_mode=input_data_scatter_mode,
|
|
output_mode=tbo_splitter_scatter_mode,
|
|
hidden_states=hidden_states,
|
|
residual=residual,
|
|
forward_batch=forward_batch,
|
|
context=context,
|
|
)
|
|
|
|
inputs_arr = _model_forward_tbo_split_inputs_raw(
|
|
hidden_states=hidden_states,
|
|
residual=residual,
|
|
positions=positions,
|
|
forward_batch=forward_batch,
|
|
zero_allocator=zero_allocator,
|
|
)
|
|
|
|
def _post_transform(hidden_states, residual, forward_batch, **kwargs):
|
|
hidden_states, residual = CommunicateSummableTensorPairFn.execute(
|
|
hidden_states_input_mode=tbo_splitter_scatter_mode,
|
|
residual_input_mode=tbo_splitter_scatter_mode,
|
|
output_mode=layer_input_scatter_mode,
|
|
hidden_states=hidden_states,
|
|
residual=residual,
|
|
forward_batch=forward_batch,
|
|
context=context,
|
|
)
|
|
return dict(
|
|
hidden_states=hidden_states,
|
|
residual=residual,
|
|
forward_batch=forward_batch,
|
|
**kwargs,
|
|
)
|
|
|
|
return [_post_transform(**inputs) for inputs in inputs_arr]
|
|
|
|
|
|
def _model_forward_tbo_split_inputs_raw(
|
|
hidden_states: torch.Tensor,
|
|
residual: torch.Tensor,
|
|
positions: torch.Tensor,
|
|
forward_batch: ForwardBatch,
|
|
zero_allocator: Optional[BumpAllocator],
|
|
) -> List[Dict]:
|
|
return [
|
|
dict(
|
|
**_model_forward_filter_inputs(
|
|
hidden_states=hidden_states,
|
|
residual=residual,
|
|
positions=positions,
|
|
output_forward_batch=output_forward_batch,
|
|
tbo_subbatch_index=tbo_subbatch_index,
|
|
),
|
|
**(
|
|
dict(zero_allocator=zero_allocator)
|
|
if zero_allocator is not None
|
|
else {}
|
|
),
|
|
)
|
|
for tbo_subbatch_index, output_forward_batch in enumerate(
|
|
forward_batch.tbo_children
|
|
)
|
|
]
|
|
|
|
|
|
def _model_forward_filter_inputs(
|
|
hidden_states: torch.Tensor,
|
|
residual: torch.Tensor,
|
|
positions: torch.Tensor,
|
|
output_forward_batch: ForwardBatch,
|
|
tbo_subbatch_index: int,
|
|
) -> Dict:
|
|
token_slice = slice(*output_forward_batch.tbo_parent_token_range)
|
|
hidden_states = hidden_states[token_slice]
|
|
residual = None if residual is None else residual[token_slice]
|
|
positions = positions[token_slice]
|
|
|
|
assert output_forward_batch.tbo_padded_len is not None
|
|
padded_len = output_forward_batch.tbo_padded_len
|
|
|
|
def _pad(x):
|
|
nonlocal padded_len
|
|
if x is None:
|
|
return None
|
|
if x.shape[0] == padded_len:
|
|
return x
|
|
res = torch.zeros((padded_len, *x.shape[1:]), dtype=x.dtype, device=x.device)
|
|
res[: x.shape[0]] = x
|
|
return res
|
|
|
|
return dict(
|
|
hidden_states=_pad(hidden_states),
|
|
residual=_pad(residual),
|
|
positions=_pad(positions),
|
|
forward_batch=output_forward_batch,
|
|
tbo_subbatch_index=tbo_subbatch_index,
|
|
)
|
|
|
|
|
|
def _model_forward_tbo_merge_outputs(output_a, output_b, original_len):
|
|
def _handle_key(name):
|
|
value_a = output_a[name]
|
|
value_b = output_b[name]
|
|
assert (value_a is None) == (value_b is None)
|
|
if value_a is None:
|
|
return None
|
|
s0, t0 = output_a["forward_batch"].tbo_parent_token_range
|
|
s1, t1 = output_b["forward_batch"].tbo_parent_token_range
|
|
res = torch.zeros(
|
|
(original_len, *value_a.shape[1:]),
|
|
dtype=value_a.dtype,
|
|
device=value_a.device,
|
|
)
|
|
res[slice(s0, t0)] = value_a[: t0 - s0]
|
|
res[slice(s1, t1)] = value_b[: t1 - s1]
|
|
return res
|
|
|
|
return _handle_key("hidden_states"), _handle_key("residual")
|
|
|
|
|
|
# -------------------------------- Utilities and wrappers ---------------------------------------
|
|
|
|
|
|
class MaybeTboDeepEPDispatcher(BaseDispatcher):
|
|
def __init__(self, **kwargs):
|
|
super().__init__()
|
|
num_inner_dispatchers = 2 if is_tbo_enabled() else 1
|
|
if get_moe_a2a_backend().is_deepep():
|
|
self._inners = [
|
|
DeepEPDispatcher(**kwargs) for _ in range(num_inner_dispatchers)
|
|
]
|
|
elif get_moe_a2a_backend().is_mooncake():
|
|
self._inners = [
|
|
MooncakeEPDispatcher(**kwargs) for _ in range(num_inner_dispatchers)
|
|
]
|
|
elif get_moe_a2a_backend().is_mori():
|
|
self._inners = [
|
|
MoriEPDispatcher(instance_id=i, **kwargs)
|
|
for i in range(num_inner_dispatchers)
|
|
]
|
|
elif get_moe_a2a_backend().is_nixl():
|
|
self._inners = [
|
|
NixlEPDispatcher(**kwargs) for _ in range(num_inner_dispatchers)
|
|
]
|
|
|
|
@property
|
|
def expert_mask_gpu(self):
|
|
return self._inners[0].expert_mask_gpu
|
|
|
|
def _execute(self, name, tbo_subbatch_index: Optional[int] = None, **kwargs):
|
|
return getattr(self._inners[tbo_subbatch_index or 0], name)(**kwargs)
|
|
|
|
def dispatch(self, **kwargs) -> DispatchOutput:
|
|
return self._execute("dispatch", **kwargs)
|
|
|
|
def dispatch_a(self, **kwargs):
|
|
return self._execute("dispatch_a", **kwargs)
|
|
|
|
def dispatch_b(self, **kwargs):
|
|
return self._execute("dispatch_b", **kwargs)
|
|
|
|
def combine(self, **kwargs) -> torch.Tensor:
|
|
return self._execute("combine", **kwargs)
|
|
|
|
def combine_a(self, **kwargs):
|
|
return self._execute("combine_a", **kwargs)
|
|
|
|
def combine_b(self, **kwargs):
|
|
return self._execute("combine_b", **kwargs)
|
|
|
|
def register_deepep_dispatch_hook(self, hook):
|
|
handle_list = []
|
|
for inner in self._inners:
|
|
handle_list.append(inner.register_deepep_dispatch_hook(hook))
|
|
return handle_list
|
|
|
|
def set_quant_config(self, quant_config: dict):
|
|
super().set_quant_config(quant_config)
|
|
for inner in self._inners:
|
|
inner.set_quant_config(quant_config)
|
|
|
|
def set_overlap_args(
|
|
self, combine_overlap_args: CombineOverlapArgs, meta_overlap_args: dict
|
|
):
|
|
super().set_overlap_args(combine_overlap_args, meta_overlap_args)
|
|
for inner in self._inners:
|
|
inner.set_overlap_args(combine_overlap_args, meta_overlap_args)
|
|
|
|
def clear_overlap_args(self):
|
|
super().clear_overlap_args()
|
|
for inner in self._inners:
|
|
inner.clear_overlap_args()
|