[Perf] Unified memory: close the DCP decode gap on Blackwell (#37926)
This commit is contained in:
@@ -14,6 +14,8 @@ MTP inter-phase seam:
|
||||
This module fuses that chain into a single launch.
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
import torch
|
||||
import triton
|
||||
import triton.language as tl
|
||||
@@ -24,15 +26,20 @@ def _fused_replay_state_indices_kernel(
|
||||
req_pool_indices_ptr, # (total_bs,) int64 — static replay buffer
|
||||
mamba_map_ptr, # (req_pool_size,) int32 — req_index_to_mamba_index_mapping
|
||||
out_ptr, # (total_bs,) int32 — state_indices_list[bs - 1]
|
||||
v2p_ptr, # (num_slots + 1,) int64 — mamba virtual->physical, or unused
|
||||
valid_bs,
|
||||
total_bs,
|
||||
BS_UPPER: tl.constexpr,
|
||||
HAS_V2P: tl.constexpr,
|
||||
):
|
||||
offs = tl.arange(0, BS_UPPER)
|
||||
in_range = offs < total_bs
|
||||
valid = offs < valid_bs
|
||||
req = tl.load(req_pool_indices_ptr + offs, mask=valid, other=0)
|
||||
idx = tl.load(mamba_map_ptr + req, mask=valid, other=0)
|
||||
if HAS_V2P:
|
||||
# Must gather before the padding sentinel, as the reference chain does.
|
||||
idx = tl.load(v2p_ptr + idx, mask=valid, other=0)
|
||||
out_val = tl.where(valid, idx.to(tl.int32), -1)
|
||||
tl.store(out_ptr + offs, out_val, mask=in_range)
|
||||
# Preserve the reference chain's side effect: padded rows of the static
|
||||
@@ -49,6 +56,7 @@ def fused_replay_state_indices(
|
||||
out_state_indices: torch.Tensor,
|
||||
valid_bs: int,
|
||||
total_bs: int,
|
||||
v2p: Optional[torch.Tensor] = None,
|
||||
) -> torch.Tensor:
|
||||
"""Fill the captured replay state-indices buffer in one launch.
|
||||
|
||||
@@ -58,9 +66,11 @@ def fused_replay_state_indices(
|
||||
the ``-1`` sentinel (mamba kernels skip ``state_idx < 0``) and their
|
||||
``req_pool_indices`` entries are zeroed.
|
||||
|
||||
Callers must supply an identity virtual->physical mapping (the static
|
||||
hybrid pool); the unified pool's allocator translate is not a flat table
|
||||
gather and has to take the reference chain.
|
||||
``v2p`` is the mamba virtual->physical slot table, for a pool whose slot
|
||||
ids are virtual (the unified memory pool). Pass None when the mapping
|
||||
already yields physical slots (the static hybrid pool). The unified
|
||||
allocator runs the mamba sub-pool at page_size 1, so its translate is a
|
||||
plain table gather and folds into this launch.
|
||||
|
||||
Returns the filled ``out_state_indices[:total_bs]`` view.
|
||||
"""
|
||||
@@ -68,8 +78,10 @@ def fused_replay_state_indices(
|
||||
req_pool_indices,
|
||||
mamba_index_mapping,
|
||||
out_state_indices,
|
||||
v2p,
|
||||
valid_bs,
|
||||
total_bs,
|
||||
BS_UPPER=triton.next_power_of_2(total_bs),
|
||||
HAS_V2P=v2p is not None,
|
||||
)
|
||||
return out_state_indices[:total_bs]
|
||||
|
||||
@@ -18,6 +18,7 @@ _TRITON_KERNELS = [
|
||||
("virtual_slot", "alloc_bind_inplace"),
|
||||
("virtual_slot", "free_unbind_inplace"),
|
||||
("virtual_slot", "bind_inplace"),
|
||||
("virtual_slot", "write_loc_to_kernel_ids"),
|
||||
]
|
||||
for _mod, _fn in _TRITON_KERNELS:
|
||||
register_kernel(
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
import torch
|
||||
import triton
|
||||
import triton.language as tl
|
||||
@@ -189,3 +191,134 @@ def bind_inplace(
|
||||
return
|
||||
grid = (triton.cdiv(N, ALLOC_BIND_BLOCK),)
|
||||
bind_inplace_kernel[grid](v, p, v2p, p2v, N, BLOCK=ALLOC_BIND_BLOCK)
|
||||
|
||||
|
||||
WRITE_LOC_BLOCK = 512
|
||||
|
||||
|
||||
@triton.jit
|
||||
def write_loc_to_kernel_id_kernel(
|
||||
loc_ptr, # in: [N] int64 — WIDENED virtual token ids
|
||||
v2p_ptr, # in: [num_pages + 1] int64 — virtual->physical page table
|
||||
out_ptr, # out: [N] int64 — kernel-facing ids
|
||||
N, # runtime: live element count
|
||||
W, # runtime: lanes to write; [N, W) get 0
|
||||
stride, # runtime: pool_page_size * kernel_page_multiplier
|
||||
PAGE_SIZE: tl.constexpr,
|
||||
DCP_SIZE: tl.constexpr,
|
||||
DCP_RANK: tl.constexpr,
|
||||
BLOCK: tl.constexpr,
|
||||
):
|
||||
"""``kernel_id(t) = v2p[t // ps] * ps * mult + t % ps``, clamped at 0.
|
||||
|
||||
Under DCP the incoming id is WIDENED: ``loc % dcp_size`` names its owner
|
||||
and ``loc // dcp_size`` is the row. Ids this rank does not own resolve to
|
||||
kernel id 0, the padding sink every write kernel skips.
|
||||
|
||||
Triton truncates ``//`` toward zero where torch floors it, so a negative
|
||||
loc is tested explicitly rather than left to the division; it resolves to
|
||||
0, as the torch path does.
|
||||
|
||||
Writing ``W > N`` lanes fills ``[N, W)`` with 0, the padding sink, so a
|
||||
caller may hand in a capture-stable buffer wider than this batch and have
|
||||
the stale tail cleared in the same launch.
|
||||
"""
|
||||
pid = tl.program_id(0)
|
||||
offs = pid * BLOCK + tl.arange(0, BLOCK)
|
||||
in_range = offs < W
|
||||
mask = offs < N
|
||||
loc = tl.load(loc_ptr + offs, mask=mask, other=0).to(tl.int64)
|
||||
|
||||
keep = mask & (loc >= 0)
|
||||
if DCP_SIZE > 1:
|
||||
keep = keep & ((loc % DCP_SIZE) == DCP_RANK)
|
||||
loc = loc // DCP_SIZE
|
||||
|
||||
page = loc // PAGE_SIZE if PAGE_SIZE > 1 else loc
|
||||
offset = loc % PAGE_SIZE if PAGE_SIZE > 1 else 0
|
||||
# `keep` already excludes negatives, so the gather index is in range.
|
||||
phys = tl.load(v2p_ptr + tl.where(keep, page, 0), mask=mask, other=0).to(tl.int64)
|
||||
ids = tl.maximum(phys * stride + offset, 0)
|
||||
tl.store(out_ptr + offs, tl.where(keep, ids, 0), mask=in_range)
|
||||
|
||||
|
||||
def write_loc_to_kernel_ids(
|
||||
*,
|
||||
loc: torch.Tensor,
|
||||
v2p: torch.Tensor,
|
||||
page_size: int,
|
||||
stride: int,
|
||||
dcp_size: int = 1,
|
||||
dcp_rank: int = 0,
|
||||
out: Optional[torch.Tensor] = None,
|
||||
out_width: Optional[int] = None,
|
||||
) -> torch.Tensor:
|
||||
"""One launch for the whole write-loc conversion; see the kernel.
|
||||
|
||||
``out`` is written in place when given (a captured graph records the
|
||||
gather against a fixed ``data_ptr``), else a fresh int64 tensor is
|
||||
returned. Cuda-graph safe: no ``.item()``, no host sync, no allocation on
|
||||
the ``out=`` path.
|
||||
|
||||
``out_width`` writes that many lanes rather than ``loc.numel()``, zeroing
|
||||
the ones past the batch; pass the captured tier's width to clear a stale
|
||||
tail here.
|
||||
"""
|
||||
N = int(loc.numel())
|
||||
# Flat-indexed as `ptr + offs`, so a strided view is mis-addressed.
|
||||
assert loc.is_contiguous(), (
|
||||
f"write_loc_to_kernel_ids: loc must be contiguous, got shape "
|
||||
f"{tuple(loc.shape)} stride {tuple(loc.stride())}"
|
||||
)
|
||||
if out is None:
|
||||
out = torch.empty_like(loc, dtype=torch.int64)
|
||||
width = N if out_width is None else int(out_width)
|
||||
assert out.dtype == torch.int64, (
|
||||
f"write_loc_to_kernel_ids: out dtype must be int64 (matches v2p), "
|
||||
f"got {out.dtype}"
|
||||
)
|
||||
if out_width is None:
|
||||
# `out` mirrors `loc` whatever its shape; a 2-D page table is legal.
|
||||
assert out.shape == loc.shape and out.is_contiguous(), (
|
||||
f"write_loc_to_kernel_ids: out shape {tuple(out.shape)} must match "
|
||||
f"loc shape {tuple(loc.shape)}"
|
||||
)
|
||||
else:
|
||||
assert out.dim() == 1 and out.is_contiguous() and out.numel() >= width, (
|
||||
f"write_loc_to_kernel_ids: out_width needs a packed 1-D out of at "
|
||||
f"least {width}, got {tuple(out.shape)}"
|
||||
)
|
||||
assert width >= N, (
|
||||
f"write_loc_to_kernel_ids: out_width {width} is under the batch's "
|
||||
f"{N} locs, which would drop live rows"
|
||||
)
|
||||
if width == 0:
|
||||
return out
|
||||
if not loc.is_cuda:
|
||||
# Pure-torch reference; the allocator's unit tests run on CPU.
|
||||
big = loc.to(torch.int64)
|
||||
keep = big >= 0
|
||||
if dcp_size > 1:
|
||||
keep = keep & (big % dcp_size == dcp_rank)
|
||||
big = torch.div(big, dcp_size, rounding_mode="floor")
|
||||
page = torch.where(keep, torch.div(big, page_size, rounding_mode="floor"), 0)
|
||||
offset = big % page_size if page_size > 1 else 0
|
||||
ids = (v2p[page] * stride + offset).clamp_(min=0)
|
||||
out[:N].copy_(torch.where(keep, ids, torch.zeros_like(ids)))
|
||||
if width > N:
|
||||
out[N:width].zero_()
|
||||
return out
|
||||
grid = (triton.cdiv(width, WRITE_LOC_BLOCK),)
|
||||
write_loc_to_kernel_id_kernel[grid](
|
||||
loc,
|
||||
v2p,
|
||||
out,
|
||||
N,
|
||||
width,
|
||||
stride,
|
||||
PAGE_SIZE=page_size,
|
||||
DCP_SIZE=dcp_size,
|
||||
DCP_RANK=dcp_rank,
|
||||
BLOCK=WRITE_LOC_BLOCK,
|
||||
)
|
||||
return out
|
||||
|
||||
Reference in New Issue
Block a user