whn09 ed49792a0c Add AWS Trainium (Neuron) platform, basic mode — part 1
Run DeepInfer on the native torch-neuronx device (PrivateUse1 `neuron`,
torch-neuronx 2.14, no torch-xla), following the Ascend port's structure.

- deep_cluster: Neuron discovery (one DeepInfer device = one logical
  NeuronCore, NEURON_RT_VISIBLE_CORES, NUMA from neuron-ls).
- PlatformNeuron: takes the Ascend (is_npu) kernel layouts, adds is_neuron;
  DEEPINFER_NEURON_DEVICE=cpu runs the same code on the host for
  correctness work.
- neuron_ops/transfer_to_neuron: map "cuda" devices, torch.device and
  torch.cuda.* onto neuron:0 (counterpart of torch_npu transfer_to_npu).
- C++ extension: -DWITH_NEURON host-only build; torch-allocated buffers and
  tensor-view slot copies instead of raw device-pointer copies.
- Pure-torch drop-in vendor packages (Ascend API semantics): deep_gemm,
  flash_mla, deep_select, tile_kernels, tilelang (constants only).
- tilelang_ops/neuron backend: KV cache / metadata / norm / compress kernels;
  CP, ZOS and VL entry points raise NotImplementedError.
- Basic mode defaults on Neuron: eager, no DSpark/MTP drafting, no
  zero-overhead scheduling; MoE avoids the GPU-only mega kernel; mHC
  prenorm GEMM uses the Ascend single-split reduction.

Not yet included: deep_ep, the sampling kernels, end-to-end harness.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 18:34:51 +08:00

360 lines
13 KiB
Python

"""Dense / grouped GEMMs; mirrors ``DeepGEMM-Ascend/csrc/apis/gemm.hpp``.
Everything is a straightforward dequantize-then-matmul reference with fp32
accumulation. Outputs are written in place into the caller's ``d`` tensor, and
``c`` follows the upstream contract:
* ``c is d`` (same storage) -> accumulate: ``D = A @ B^T + D``
* separate contiguous ``[1, N]`` tensor -> bias
* ``None`` -> plain ``D = A @ B^T``
Operand majorness does not change the logical shapes, so the ``nt`` / ``nn`` /
``tn`` / ``tt`` variants only differ by which operands are logically transposed.
"""
import torch
from ._quant import dequantize, is_packed_fp4
from .layout import transform_sf_into_required_layout
def _is_alias(a: torch.Tensor, b: torch.Tensor) -> bool:
return a.data_ptr() == b.data_ptr() and a.shape == b.shape
def _logical_k(data: torch.Tensor) -> int:
"""Logical K of a low-precision operand whose K axis is the last dim."""
return data.shape[-1] * (2 if is_packed_fp4(data.dtype) else 1)
def _write_output(out: torch.Tensor, d: torch.Tensor, c: torch.Tensor | None, alpha: float | None) -> None:
if alpha is not None:
out = out * alpha
if c is not None:
if _is_alias(c, d):
out = out + d.float()
else:
# Separate bias tensor, [1, N] (or broadcastable).
assert c.shape[-1] == d.shape[-1], f"{c.shape=}, {d.shape=}"
out = out + c.float()
d.copy_(out.to(d.dtype))
def _dequant_operand(
data: torch.Tensor,
sf: torch.Tensor,
mn: int,
k: int,
recipe: tuple[int, int, int],
is_sfa: bool,
num_groups: int | None = None,
disable_ue8m0_cast: bool = False,
) -> torch.Tensor:
sf = transform_sf_into_required_layout(
sf, mn, k, recipe, num_groups=num_groups, is_sfa=is_sfa, disable_ue8m0_cast=disable_ue8m0_cast
)
return dequantize(data, sf, gran_k=recipe[2])
# --------------------------------------------------------------------------- #
# BF16
# --------------------------------------------------------------------------- #
def _bf16_gemm(
a: torch.Tensor,
b: torch.Tensor,
d: torch.Tensor,
c: torch.Tensor | None,
trans_a: bool,
trans_b: bool,
alpha: float | None,
) -> None:
if trans_a:
a = a.transpose(0, 1)
if trans_b:
b = b.transpose(0, 1)
assert a.dtype == b.dtype == torch.bfloat16, f"{a.dtype=}, {b.dtype=}"
assert d.dtype in (torch.bfloat16, torch.float32), f"{d.dtype=}"
m, k = a.shape
n = b.shape[0]
assert b.shape[1] == k and d.shape == (m, n), f"{a.shape=}, {b.shape=}, {d.shape=}"
if m == 0 or n == 0:
return
if k == 0:
_write_output(torch.zeros((m, n), dtype=torch.float32, device=d.device), d, c, alpha)
return
_write_output(a.float() @ b.float().transpose(0, 1), d, c, alpha)
def bf16_gemm_nt(a, b, d, c=None, compiled_dims="nk", alpha=None, epilogue=None) -> None:
"""D = A[M, K] @ B[N, K]^T (+ C)."""
assert epilogue is None, "epilogue classes are not supported"
_bf16_gemm(a, b, d, c, False, False, alpha)
def bf16_gemm_nn(a, b, d, c=None, compiled_dims="nk", alpha=None, epilogue=None) -> None:
"""D = A[M, K] @ B[K, N] (+ C)."""
assert epilogue is None, "epilogue classes are not supported"
_bf16_gemm(a, b, d, c, False, True, alpha)
def bf16_gemm_tn(a, b, d, c=None, compiled_dims="mn", alpha=None, epilogue=None) -> None:
"""D = A[K, M]^T @ B[K, N] (+ C)."""
assert epilogue is None, "epilogue classes are not supported"
_bf16_gemm(a, b, d, c, True, True, alpha)
def bf16_gemm_tt(a, b, d, c=None, compiled_dims="mn", alpha=None, epilogue=None) -> None:
"""D = A[K, M]^T @ B[N, K]^T (+ C)."""
assert epilogue is None, "epilogue classes are not supported"
_bf16_gemm(a, b, d, c, True, False, alpha)
# The Ascend build aliases the cuBLASLt entry points onto its ACLNN bf16 GEMMs
# (``csrc/apis/aclnn.hpp``), so they are the same kernel here.
def cublaslt_gemm_nt(a, b, d, c=None) -> None:
_bf16_gemm(a, b, d, c, False, False, None)
def cublaslt_gemm_nn(a, b, d, c=None) -> None:
_bf16_gemm(a, b, d, c, False, True, None)
def cublaslt_gemm_tn(a, b, d, c=None) -> None:
_bf16_gemm(a, b, d, c, True, True, None)
def cublaslt_gemm_tt(a, b, d, c=None) -> None:
_bf16_gemm(a, b, d, c, True, False, None)
aclnn_bf16_gemm_nt = cublaslt_gemm_nt
aclnn_bf16_gemm_nn = cublaslt_gemm_nn
aclnn_bf16_gemm_tn = cublaslt_gemm_tn
aclnn_bf16_gemm_tt = cublaslt_gemm_tt
# --------------------------------------------------------------------------- #
# FP8 / FP4
# --------------------------------------------------------------------------- #
def _transposed_pair(
operand: tuple[torch.Tensor, torch.Tensor], trans: bool, dim0: int, dim1: int
) -> tuple[torch.Tensor, torch.Tensor]:
data, sf = operand
if not trans:
return data, sf
assert not is_packed_fp4(data.dtype), "packed FP4 operands cannot be logically transposed"
return data.transpose(dim0, dim1), sf.transpose(dim0, dim1)
def _fp8_fp4_gemm(
a_: tuple[torch.Tensor, torch.Tensor],
b_: tuple[torch.Tensor, torch.Tensor],
d: torch.Tensor,
c: torch.Tensor | None,
recipe,
recipe_a,
recipe_b,
disable_ue8m0_cast: bool,
alpha: float | None,
trans_a: bool,
trans_b: bool,
) -> None:
from .layout import get_recipe
a, sfa = _transposed_pair(a_, trans_a, 0, 1)
b, sfb = _transposed_pair(b_, trans_b, 0, 1)
assert d.dtype in (torch.bfloat16, torch.float32), f"{d.dtype=}"
m = a.shape[0]
n = b.shape[0]
k = _logical_k(a)
assert _logical_k(b) == k, f"{a.shape=}, {b.shape=}"
assert d.shape == (m, n), f"{d.shape=}, {m=}, {n=}"
if m == 0 or n == 0:
return
full_recipe = get_recipe(recipe, recipe_a, recipe_b)
a_f32 = _dequant_operand(a, sfa, m, k, full_recipe, True, disable_ue8m0_cast=disable_ue8m0_cast)
b_f32 = _dequant_operand(b, sfb, n, k, full_recipe, False, disable_ue8m0_cast=disable_ue8m0_cast)
_write_output(a_f32 @ b_f32.transpose(0, 1), d, c, alpha)
def fp8_fp4_gemm_nt(
a, b, d, c=None, recipe=None, recipe_a=None, recipe_b=None,
compiled_dims="nk", disable_ue8m0_cast=False, alpha=None, epilogue=None,
) -> None:
"""D = A[M, K] @ B[N, K]^T (+ C), with FP8/FP4 operands and UE8M0 scales."""
assert epilogue is None, "epilogue classes are not supported"
_fp8_fp4_gemm(a, b, d, c, recipe, recipe_a, recipe_b, disable_ue8m0_cast, alpha, False, False)
def fp8_fp4_gemm_nn(
a, b, d, c=None, recipe=None, recipe_a=None, recipe_b=None,
compiled_dims="nk", disable_ue8m0_cast=False, alpha=None, epilogue=None,
) -> None:
assert epilogue is None, "epilogue classes are not supported"
_fp8_fp4_gemm(a, b, d, c, recipe, recipe_a, recipe_b, disable_ue8m0_cast, alpha, False, True)
def fp8_fp4_gemm_tn(
a, b, d, c=None, recipe=None, recipe_a=None, recipe_b=None,
compiled_dims="mn", disable_ue8m0_cast=False, alpha=None, epilogue=None,
) -> None:
assert epilogue is None, "epilogue classes are not supported"
_fp8_fp4_gemm(a, b, d, c, recipe, recipe_a, recipe_b, disable_ue8m0_cast, alpha, True, True)
def fp8_fp4_gemm_tt(
a, b, d, c=None, recipe=None, recipe_a=None, recipe_b=None,
compiled_dims="mn", disable_ue8m0_cast=False, alpha=None, epilogue=None,
) -> None:
assert epilogue is None, "epilogue classes are not supported"
_fp8_fp4_gemm(a, b, d, c, recipe, recipe_a, recipe_b, disable_ue8m0_cast, alpha, True, False)
# Upstream keeps separate FP8-only entry points with the same semantics.
fp8_gemm_nt = fp8_fp4_gemm_nt
fp8_gemm_nn = fp8_fp4_gemm_nn
fp8_gemm_tn = fp8_fp4_gemm_tn
fp8_gemm_tt = fp8_fp4_gemm_tt
aclnn_fp8_fp4_gemm_nt = fp8_fp4_gemm_nt
aclnn_fp8_fp4_gemm_nn = fp8_fp4_gemm_nn
aclnn_fp8_fp4_gemm_tn = fp8_fp4_gemm_tn
aclnn_fp8_fp4_gemm_tt = fp8_fp4_gemm_tt
# --------------------------------------------------------------------------- #
# M-grouped contiguous ("psum") layout
# --------------------------------------------------------------------------- #
def _psum_group_spans(grouped_layout: torch.Tensor, alignment: int, total_m: int) -> list[tuple[int, int]]:
"""Decode the contiguous psum layout into per-group ``[start, end)`` row spans.
``grouped_layout`` is the cumulative end offset of each group's valid rows; the
next group starts at ``align(end, alignment)``
(``DeepGEMM-Ascend/tests/generators.py::generate_psum_layout``).
"""
assert grouped_layout.dim() == 1 and grouped_layout.dtype == torch.int32, f"{grouped_layout.shape=}, {grouped_layout.dtype=}"
# NEURON: host sync - the group boundaries are data dependent, and the number of
# groups is small (one per local expert), so the loop runs on the host.
ends = grouped_layout.tolist()
spans = []
start = 0
for end in ends:
end = max(start, min(int(end), total_m))
spans.append((start, end))
start = min((int(end) + alignment - 1) // alignment * alignment, total_m)
return spans
def m_grouped_bf16_gemm_nt_contiguous(
a: torch.Tensor,
b: torch.Tensor,
d: torch.Tensor,
grouped_layout: torch.Tensor,
compiled_dims="nk",
use_psum_layout: bool = True,
ensure_zero_padding: bool = True,
expected_m_for_psum_layout: int | None = None,
epilogue=None,
) -> None:
"""D[rows of group i] = A[rows of group i] @ B[i]^T, contiguous psum layout."""
assert epilogue is None, "epilogue classes are not supported"
assert use_psum_layout, "only the psum layout is supported"
assert a.dtype == b.dtype == torch.bfloat16, f"{a.dtype=}, {b.dtype=}"
num_groups, n, k = b.shape
total_m = a.shape[0]
assert a.shape[1] == k and d.shape == (total_m, n), f"{a.shape=}, {b.shape=}, {d.shape=}"
if total_m == 0 or n == 0:
return
from .runtime import get_mk_alignment_for_contiguous_layout
spans = _psum_group_spans(grouped_layout, get_mk_alignment_for_contiguous_layout(), total_m)
assert len(spans) == num_groups, f"{len(spans)=}, {num_groups=}"
d.zero_()
for group_idx, (start, end) in enumerate(spans):
if end <= start:
continue
out = a[start:end].float() @ b[group_idx].float().transpose(0, 1)
d[start:end].copy_(out.to(d.dtype))
def m_grouped_bf16_gemm_nn_contiguous(
a, b, d, grouped_layout, compiled_dims="nk", use_psum_layout=True,
ensure_zero_padding=True, expected_m_for_psum_layout=None, epilogue=None,
) -> None:
m_grouped_bf16_gemm_nt_contiguous(
a, b.transpose(1, 2), d, grouped_layout, compiled_dims, use_psum_layout,
ensure_zero_padding, expected_m_for_psum_layout, epilogue,
)
def m_grouped_fp8_fp4_gemm_nt_contiguous(
a_: tuple[torch.Tensor, torch.Tensor],
b_: tuple[torch.Tensor, torch.Tensor],
d: torch.Tensor,
grouped_layout: torch.Tensor,
recipe=None,
recipe_a=None,
recipe_b=None,
compiled_dims="nk",
disable_ue8m0_cast: bool = False,
use_psum_layout: bool = True,
ensure_zero_padding: bool = True,
expected_m_for_psum_layout: int | None = None,
epilogue=None,
) -> None:
"""D[rows of group i] = A[rows of group i] @ B[i]^T for FP8/FP4 operands."""
from .layout import get_recipe
from .runtime import get_mk_alignment_for_contiguous_layout
assert epilogue is None, "epilogue classes are not supported"
assert use_psum_layout, "only the psum layout is supported"
a, sfa = a_
b, sfb = b_
total_m = a.shape[0]
num_groups, n = b.shape[0], b.shape[1]
k = _logical_k(a)
assert _logical_k(b) == k, f"{a.shape=}, {b.shape=}"
assert d.shape == (total_m, n), f"{d.shape=}, {total_m=}, {n=}"
if total_m == 0 or n == 0:
return
full_recipe = get_recipe(recipe, recipe_a, recipe_b)
a_f32 = _dequant_operand(a, sfa, total_m, k, full_recipe, True, disable_ue8m0_cast=disable_ue8m0_cast)
b_f32 = _dequant_operand(
b, sfb, n, k, full_recipe, False, num_groups=num_groups, disable_ue8m0_cast=disable_ue8m0_cast
)
spans = _psum_group_spans(grouped_layout, get_mk_alignment_for_contiguous_layout(), total_m)
assert len(spans) == num_groups, f"{len(spans)=}, {num_groups=}"
d.zero_()
for group_idx, (start, end) in enumerate(spans):
if end <= start:
continue
out = a_f32[start:end] @ b_f32[group_idx].transpose(0, 1)
d[start:end].copy_(out.to(d.dtype))
def m_grouped_fp8_fp4_gemm_nn_contiguous(
a, b, d, grouped_layout, recipe=None, recipe_a=None, recipe_b=None,
compiled_dims="nk", disable_ue8m0_cast=False, use_psum_layout=True,
ensure_zero_padding=True, expected_m_for_psum_layout=None, epilogue=None,
) -> None:
data, sf = b
assert not is_packed_fp4(data.dtype), "packed FP4 B cannot be logically transposed"
m_grouped_fp8_fp4_gemm_nt_contiguous(
a, (data.transpose(1, 2), sf.transpose(1, 2)), d, grouped_layout, recipe, recipe_a, recipe_b,
compiled_dims, disable_ue8m0_cast, use_psum_layout, ensure_zero_padding,
expected_m_for_psum_layout, epilogue,
)
m_grouped_fp8_gemm_nt_contiguous = m_grouped_fp8_fp4_gemm_nt_contiguous
m_grouped_fp8_gemm_nn_contiguous = m_grouped_fp8_fp4_gemm_nn_contiguous
__all__ = [name for name in globals() if "gemm" in name]