mirror of
https://github.com/deepseek-ai/DeepInfer.git
synced 2026-11-12 09:23:54 +08:00
neuron: shared expert sliced over ranks, summed in the MoE all-reduce (DEEPINFER_NEURON_SHARED_SLICE=1)
18 of 64 ranks compute a 128-column slice of the 2304-wide shared expert (the rest add zeros, same graph); the partial joins the routed experts' sum before the existing all-reduce. Decode step 591 -> 581 ms. Summation order changes, so greedy outputs differ from before (coherent; tokens/step 2.70/3.10/3.34, TPOT p50 184 ms). Tiny golden 9/9. Also DEEPINFER_NEURON_HOST_TIMING (host time per region call). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
6c653df08c
commit
c4c50f1ea5
@ -85,6 +85,9 @@ def get_model(
|
|||||||
from deepinfer.neuron_ops import nki_gemm
|
from deepinfer.neuron_ops import nki_gemm
|
||||||
|
|
||||||
logger.info(f"Neuron: {nki_gemm.prepare_model(model)} weights take the NKI block-quant GEMMs")
|
logger.info(f"Neuron: {nki_gemm.prepare_model(model)} weights take the NKI block-quant GEMMs")
|
||||||
|
from deepinfer.neuron_ops import static_moe
|
||||||
|
|
||||||
|
logger.info(f"Neuron: {static_moe.prepare_shared_slices(model)} MoE layers compute a slice of the shared expert")
|
||||||
from deepinfer.neuron_ops import nki_hc
|
from deepinfer.neuron_ops import nki_hc
|
||||||
|
|
||||||
logger.info(f"Neuron: {nki_hc.prepare_model(model)} mHC fn matrices split into bf16 hi + lo")
|
logger.info(f"Neuron: {nki_hc.prepare_model(model)} mHC fn matrices split into bf16 hi + lo")
|
||||||
|
|||||||
@ -120,6 +120,12 @@ def linear_qdq(x: torch.Tensor, w: torch.Tensor, group: int | None = None) -> to
|
|||||||
return _linear_kernel(name, weight.shape[0])(x, weight, scale)[: x.shape[0]]
|
return _linear_kernel(name, weight.shape[0])(x, weight, scale)[: x.shape[0]]
|
||||||
|
|
||||||
|
|
||||||
|
def linear_qdq_raw(x: torch.Tensor, weight: torch.Tensor, scale: torch.Tensor) -> torch.Tensor:
|
||||||
|
"""``linear_qdq`` for a bare fp8 weight (uint8 bytes ``[n, k]`` + kernel scale), e.g. a rank's shared-expert
|
||||||
|
slice (static_moe.prepare_shared_slices)."""
|
||||||
|
return _linear_kernel("fp8_linear", weight.shape[0])(x, weight, scale)[: x.shape[0]]
|
||||||
|
|
||||||
|
|
||||||
def linear_qdq_gated(x: torch.Tensor, w: torch.Tensor, group: int, flag: torch.Tensor) -> torch.Tensor | None:
|
def linear_qdq_gated(x: torch.Tensor, w: torch.Tensor, group: int, flag: torch.Tensor) -> torch.Tensor | None:
|
||||||
"""``linear_qdq`` for fp4 expert ``group`` that the kernel skips (zeros) unless ``flag`` (int32 ``[1, 1]``) is 1.
|
"""``linear_qdq`` for fp4 expert ``group`` that the kernel skips (zeros) unless ``flag`` (int32 ``[1, 1]``) is 1.
|
||||||
None when the gated kernel does not apply (not fp4, or columns that do not split across the two cores)."""
|
None when the gated kernel does not apply (not fp4, or columns that do not split across the two cores)."""
|
||||||
|
|||||||
@ -79,6 +79,12 @@ def entry(module: torch.nn.Module, name: str) -> Callable[..., Any]:
|
|||||||
|
|
||||||
def fn(mod: torch.nn.Module, *args: Any, **kwargs: Any) -> Any:
|
def fn(mod: torch.nn.Module, *args: Any, **kwargs: Any) -> Any:
|
||||||
with hide_cuda():
|
with hide_cuda():
|
||||||
|
if _HOST_TIMING:
|
||||||
|
started = time.perf_counter()
|
||||||
|
out = compiled(mod, *args, **kwargs)
|
||||||
|
_HOST[label][0] += 1
|
||||||
|
_HOST[label][1] += time.perf_counter() - started
|
||||||
|
return out
|
||||||
if not envs.DEEPINFER_NEURON_REGION_TIMING:
|
if not envs.DEEPINFER_NEURON_REGION_TIMING:
|
||||||
out = compiled(mod, *args, **kwargs)
|
out = compiled(mod, *args, **kwargs)
|
||||||
if _SYNC_EVERY:
|
if _SYNC_EVERY:
|
||||||
@ -103,6 +109,16 @@ def entry(module: torch.nn.Module, name: str) -> Callable[..., Any]:
|
|||||||
|
|
||||||
|
|
||||||
_MARK: list = [0.0, 0, ""]
|
_MARK: list = [0.0, 0, ""]
|
||||||
|
#: DEEPINFER_NEURON_HOST_TIMING: host seconds spent inside each region kind's compiled call (no synchronize: launch,
|
||||||
|
#: guard evaluation, resume frames, and any wait for the execution queue), per step (take_host_timings).
|
||||||
|
_HOST_TIMING = os.environ.get("DEEPINFER_NEURON_HOST_TIMING") == "1"
|
||||||
|
_HOST: dict[str, list] = defaultdict(lambda: [0, 0.0])
|
||||||
|
|
||||||
|
|
||||||
|
def take_host_timings() -> dict[str, list]:
|
||||||
|
out = {k: list(v) for k, v in _HOST.items()}
|
||||||
|
_HOST.clear()
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
@torch._dynamo.disable
|
@torch._dynamo.disable
|
||||||
|
|||||||
@ -39,6 +39,11 @@ _ALIGN = 16
|
|||||||
_GATED_EXPERTS = os.environ.get("DEEPINFER_NEURON_GATED_EXPERTS", "1") == "1"
|
_GATED_EXPERTS = os.environ.get("DEEPINFER_NEURON_GATED_EXPERTS", "1") == "1"
|
||||||
_GATED_MAX_ROWS = 16
|
_GATED_MAX_ROWS = 16
|
||||||
_FLAG_FORCE = os.environ.get("DEEPINFER_NEURON_MOE_FLAG_FORCE", "")
|
_FLAG_FORCE = os.environ.get("DEEPINFER_NEURON_MOE_FLAG_FORCE", "")
|
||||||
|
#: DEEPINFER_NEURON_SHARED_SLICE: each rank computes a 128-column slice of the shared expert's intermediate dimension
|
||||||
|
#: and adds it to the routed experts' partial sum before the MoE all-reduce, instead of every rank computing the whole
|
||||||
|
#: shared expert after it (35 M fp8 weights a layer, dequantized on every rank).
|
||||||
|
_SHARED_SLICE = os.environ.get("DEEPINFER_NEURON_SHARED_SLICE", "1") == "1"
|
||||||
|
_SHARED_COLS = 128
|
||||||
|
|
||||||
def _as_rows(t: torch.Tensor, rows: int) -> torch.Tensor:
|
def _as_rows(t: torch.Tensor, rows: int) -> torch.Tensor:
|
||||||
return t.contiguous().view(rows, -1).view(torch.uint8)
|
return t.contiguous().view(rows, -1).view(torch.uint8)
|
||||||
@ -124,6 +129,52 @@ def _reduce_scatter(t: torch.Tensor, group: torch.distributed.ProcessGroup, size
|
|||||||
return funcol.wait_tensor(funcol.reduce_scatter_tensor(t, "sum", 0, group))
|
return funcol.wait_tensor(funcol.reduce_scatter_tensor(t, "sum", 0, group))
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_shared_slices(model: torch.nn.Module) -> int:
|
||||||
|
"""Register this rank's shared-expert slice on every MoE (after nki_gemm.prepare_model). ``I // 128`` ranks own a
|
||||||
|
slice; the others get zero weights of the same shape, so every rank traces the same graph and their slice adds
|
||||||
|
exact zeros. Returns the number of MoE layers sliced."""
|
||||||
|
if not _SHARED_SLICE:
|
||||||
|
return 0
|
||||||
|
count = 0
|
||||||
|
for moe in model.modules():
|
||||||
|
gu = getattr(moe, "shared_gate_up_proj", None)
|
||||||
|
dn = getattr(moe, "shared_down_proj", None)
|
||||||
|
if not getattr(moe, "has_shared", False) or gu is None or dn is None:
|
||||||
|
continue
|
||||||
|
gw, dw = gu.weight, dn.weight
|
||||||
|
if getattr(gw, "_nki_scale", None) is None or getattr(dw, "_nki_scale", None) is None or gw.dtype != torch.float8_e4m3fn:
|
||||||
|
continue
|
||||||
|
size, rank = moe.ep_size, moe.ep_rank
|
||||||
|
inter = gw.shape[0] // 2
|
||||||
|
slices = inter // _SHARED_COLS
|
||||||
|
if size == 1 or inter % _SHARED_COLS or slices > size:
|
||||||
|
continue
|
||||||
|
c, b = _SHARED_COLS, _SHARED_COLS // 32
|
||||||
|
# On the host: strided slices of device tensors are not safe eagerly on a NeuronCore (one-time, at load).
|
||||||
|
device = gw.device
|
||||||
|
g_w, g_s, d_w, d_s = (t.cpu() for t in (gw._nki_weight, gw._nki_scale, dw._nki_weight, dw._nki_scale))
|
||||||
|
if rank < slices:
|
||||||
|
lo = rank * c
|
||||||
|
gu_w = torch.cat([g_w[lo : lo + c], g_w[inter + lo : inter + lo + c]])
|
||||||
|
gu_s = torch.cat([g_s[lo // 32 : lo // 32 + b], g_s[(inter + lo) // 32 : (inter + lo) // 32 + b]])
|
||||||
|
dn_w, dn_s = d_w[:, lo : lo + c], d_s[:, lo // 32 : lo // 32 + b]
|
||||||
|
else:
|
||||||
|
gu_w = torch.zeros((2 * c, g_w.shape[1]), dtype=g_w.dtype, device=g_w.device)
|
||||||
|
gu_s = torch.full((2 * b, g_s.shape[1]), 127, dtype=g_s.dtype, device=g_s.device) # e8m0 127 = 1.0
|
||||||
|
dn_w = torch.zeros((d_w.shape[0], c), dtype=d_w.dtype, device=d_w.device)
|
||||||
|
dn_s = torch.full((d_s.shape[0], b), 127, dtype=d_s.dtype, device=d_s.device)
|
||||||
|
for name, t in (("gu_w", gu_w), ("gu_s", gu_s), ("dn_w", dn_w), ("dn_s", dn_s)):
|
||||||
|
moe.register_buffer(f"_neuron_shared_{name}", t.contiguous().to(device), persistent=False)
|
||||||
|
count += 1
|
||||||
|
return count
|
||||||
|
|
||||||
|
|
||||||
|
def _shared_slice(moe: DeepseekMoE, x: torch.Tensor) -> torch.Tensor:
|
||||||
|
gate_up = nki_gemm.linear_qdq_raw(x, moe._neuron_shared_gu_w, moe._neuron_shared_gu_s)
|
||||||
|
act = swiglu_forward_and_per_token_cast(gate_up, fmt="bf16", num_per_channels=None, clamp_value=moe.swiglu_clamp_value)
|
||||||
|
return nki_gemm.linear_qdq_raw(act, moe._neuron_shared_dn_w, moe._neuron_shared_dn_s)
|
||||||
|
|
||||||
|
|
||||||
def static_moe_forward(
|
def static_moe_forward(
|
||||||
moe: DeepseekMoE,
|
moe: DeepseekMoE,
|
||||||
hidden_states: torch.Tensor | tuple[torch.Tensor, torch.Tensor],
|
hidden_states: torch.Tensor | tuple[torch.Tensor, torch.Tensor],
|
||||||
@ -283,11 +334,14 @@ def _nki_moe_forward(
|
|||||||
if down is None:
|
if down is None:
|
||||||
down = nki_gemm.linear_qdq(act, moe.down_proj_weight, group=e)
|
down = nki_gemm.linear_qdq(act, moe.down_proj_weight, group=e)
|
||||||
acc = acc + down.float()
|
acc = acc + down.float()
|
||||||
|
sliced = moe.has_shared and getattr(moe, "_neuron_shared_gu_w", None) is not None
|
||||||
|
if sliced:
|
||||||
|
acc = acc + _shared_slice(moe, x).float()
|
||||||
regions.split()
|
regions.split()
|
||||||
hier = getattr(moe, "_neuron_hier_ar", None)
|
hier = getattr(moe, "_neuron_hier_ar", None)
|
||||||
out = _all_reduce(acc, group, hier) if replicated else _reduce_scatter(acc, group, size, rank)[:num_tokens]
|
out = _all_reduce(acc, group, hier) if replicated else _reduce_scatter(acc, group, size, rank)[:num_tokens]
|
||||||
regions.split()
|
regions.split()
|
||||||
if moe.has_shared:
|
if moe.has_shared and not sliced:
|
||||||
shared = nki_gemm.linear_qdq(x_local, moe.shared_gate_up_proj.weight)
|
shared = nki_gemm.linear_qdq(x_local, moe.shared_gate_up_proj.weight)
|
||||||
shared = swiglu_forward_and_per_token_cast(
|
shared = swiglu_forward_and_per_token_cast(
|
||||||
shared, fmt="bf16", num_per_channels=None, clamp_value=moe.swiglu_clamp_value
|
shared, fmt="bf16", num_per_channels=None, clamp_value=moe.swiglu_clamp_value
|
||||||
|
|||||||
@ -199,6 +199,21 @@ class NeuronGraphRunner(CudaGraphRunner):
|
|||||||
reqs = -(-batch_size // (1 + envs.DEEPINFER_USE_NEXTN_DECODE))
|
reqs = -(-batch_size // (1 + envs.DEEPINFER_USE_NEXTN_DECODE))
|
||||||
nki_attention.set_table_bounds(reqs, getattr(input_metadata_device, "neuron_ctx_limit", None))
|
nki_attention.set_table_bounds(reqs, getattr(input_metadata_device, "neuron_ctx_limit", None))
|
||||||
if not envs.DEEPINFER_NEURON_REGION_TIMING:
|
if not envs.DEEPINFER_NEURON_REGION_TIMING:
|
||||||
|
if regions._HOST_TIMING:
|
||||||
|
started = time.perf_counter()
|
||||||
|
with hide_cuda():
|
||||||
|
out = self.compiled[batch_size](input_metadata_device)
|
||||||
|
self._host_steps = getattr(self, "_host_steps", 0) + 1
|
||||||
|
if self._host_steps % 20 == 0 and (not torch.distributed.is_initialized() or torch.distributed.get_rank() == 0):
|
||||||
|
host = regions.take_host_timings()
|
||||||
|
inside = sum(v[1] for v in host.values())
|
||||||
|
logger.info(
|
||||||
|
f"Neuron host bs={batch_size}: forward {1e3 * (time.perf_counter() - started):.1f} ms, in regions "
|
||||||
|
f"{1e3 * inside:.1f} ms; " + ", ".join(f"{k} {v[0]}x {1e3 * v[1]:.1f} ms" for k, v in sorted(host.items(), key=lambda kv: -kv[1][1]))
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
regions.take_host_timings()
|
||||||
|
return out
|
||||||
with hide_cuda():
|
with hide_cuda():
|
||||||
return self.compiled[batch_size](input_metadata_device)
|
return self.compiled[batch_size](input_metadata_device)
|
||||||
torch.neuron.synchronize()
|
torch.neuron.synchronize()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user