mirror of
https://github.com/deepseek-ai/DeepInfer.git
synced 2026-10-28 14:57:09 +08:00
- serve/: the minimal OpenAI-compatible server of deepseek-ai/DeepInfer#147, with a Neuron worker setup (basic mode env, per-rank NeuronCore, fixed KV pool via --num-kv-blocks, no MTP lookahead) and a /v1/completions endpoint (prompt text or token ids, no chat template): what vllm bench serve --backend openai drives, as vllm-neuron#71's run_bench.py does. - examples/neuron/bench_neuron.py: the same TTFT/TPOT matrix offline. - static MoE: one packed fp32 all-gather instead of three (each 64-rank collective is ~2 ms of fixed latency a layer); a capacity-grouped expert path for prefill chunks (DEEPINFER_NEURON_MOE_CAPACITY, dense rerun on overflow; not yet validated on device). - Sub-region timings at split() boundaries under DEEPINFER_NEURON_REGION_TIMING. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
119 lines
4.7 KiB
Python
119 lines
4.7 KiB
Python
"""Region graphs for the Neuron decode step (issue #25), the vllm-neuron#71 layout.
|
|
|
|
A whole decode step in one graph crashes neuronx-cc (issue #9) and takes long to compile. As in vllm-neuron#71, the
|
|
step instead runs eagerly and compiles a few entry points per layer (the attention half and the MoE), each its own
|
|
fullgraph. Layers of the same kind trace to the same graph, so neuronx-cc's NEFF cache compiles each kind once.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import functools
|
|
import os
|
|
import time
|
|
from collections import defaultdict
|
|
from collections.abc import Callable
|
|
from typing import Any
|
|
|
|
import torch
|
|
|
|
from deepinfer import PLATFORM, envs
|
|
|
|
|
|
def enabled(input_metadata: Any) -> bool:
|
|
"""Region graphs: Neuron, decode, DEEPINFER_ENFORCE_EAGER=0 and DEEPINFER_NEURON_GRAPH_MODE=regions."""
|
|
return (
|
|
PLATFORM.is_neuron
|
|
and not envs.DEEPINFER_ENFORCE_EAGER
|
|
and envs.DEEPINFER_NEURON_GRAPH_MODE == "regions"
|
|
and not input_metadata.is_prompt
|
|
)
|
|
|
|
|
|
def raise_dynamo_limits() -> None:
|
|
"""Every layer recompiles each entry (its parameters and attributes differ); keep dynamo compiling them. Past
|
|
the accumulated limit dynamo silently runs frames eagerly instead (see vllm-neuron#71 model.py)."""
|
|
for name in ("recompile_limit", "cache_size_limit", "accumulated_recompile_limit", "accumulated_cache_size_limit"):
|
|
if hasattr(torch._dynamo.config, name):
|
|
setattr(torch._dynamo.config, name, 1 << 30)
|
|
|
|
|
|
_ENTRIES: dict[tuple[type, str], Callable[..., Any]] = {}
|
|
#: DEEPINFER_NEURON_REGION_TIMING: seconds per region kind in the current step (each call synchronized).
|
|
_TIMES: dict[str, list[float]] = defaultdict(list)
|
|
#: Experiment (issue #27): synchronize after every N region calls (0 = never).
|
|
_SYNC_EVERY = int(os.environ.get("DEEPINFER_NEURON_SYNC_EVERY", "0"))
|
|
_CALLS = [0]
|
|
|
|
|
|
def take_timings() -> dict[str, list[float]]:
|
|
"""The region timings recorded since the last call, then reset."""
|
|
out = dict(_TIMES)
|
|
_TIMES.clear()
|
|
return out
|
|
|
|
|
|
def entry(module: torch.nn.Module, name: str) -> Callable[..., Any]:
|
|
"""``type(module).<name>`` as a region, compiled once per (class, method) and called with the module.
|
|
|
|
One compiled callable for all layers of a class: Dynamo then reuses the same graph (and the same loaded NEFF)
|
|
for every layer whose guards match (same layer kind and shapes). A per-module compile gave every layer its own
|
|
compiled artifact and NEFF load -- the same code loaded 43 times filled ~6 GB of HBM with model code.
|
|
"""
|
|
key = (type(module), name)
|
|
fn = _ENTRIES.get(key)
|
|
if fn is None:
|
|
from deepinfer.neuron_ops.transfer_to_neuron import hide_cuda
|
|
|
|
raise_dynamo_limits()
|
|
# fullgraph=False: split() marks sub-region boundaries inside the entry (smaller graphs for neuronx-cc).
|
|
compiled = torch.compile(
|
|
getattr(type(module), name), backend=envs.DEEPINFER_NEURON_COMPILE_BACKEND, fullgraph=False, dynamic=False
|
|
)
|
|
|
|
label = f"{type(module).__name__}.{name}"
|
|
|
|
def fn(mod: torch.nn.Module, *args: Any, **kwargs: Any) -> Any:
|
|
with hide_cuda():
|
|
if not envs.DEEPINFER_NEURON_REGION_TIMING:
|
|
out = compiled(mod, *args, **kwargs)
|
|
if _SYNC_EVERY:
|
|
_CALLS[0] += 1
|
|
if _CALLS[0] % _SYNC_EVERY == 0:
|
|
torch.neuron.synchronize()
|
|
return out
|
|
torch.neuron.synchronize()
|
|
started = time.perf_counter()
|
|
_MARK[0], _MARK[1], _MARK[2] = started, 0, label
|
|
out = compiled(mod, *args, **kwargs)
|
|
torch.neuron.synchronize()
|
|
done = time.perf_counter()
|
|
if _MARK[1]:
|
|
_TIMES[f"{label}.split{_MARK[1]}"].append(done - _MARK[0])
|
|
_MARK[0] = 0.0
|
|
_TIMES[label].append(done - started)
|
|
return out
|
|
|
|
_ENTRIES[key] = fn
|
|
return functools.partial(fn, module)
|
|
|
|
|
|
_MARK: list = [0.0, 0, ""]
|
|
|
|
|
|
@torch._dynamo.disable
|
|
def _boundary() -> None:
|
|
"""A graph boundary and nothing else (vllm-neuron#71's ``_graph_break``). Under DEEPINFER_NEURON_REGION_TIMING
|
|
also records the time since the previous boundary (or the region's start) as ``split<i>``."""
|
|
if envs.DEEPINFER_NEURON_REGION_TIMING:
|
|
torch.neuron.synchronize()
|
|
now = time.perf_counter()
|
|
if _MARK[0]:
|
|
_TIMES[f"{_MARK[2]}.split{_MARK[1]}"].append(now - _MARK[0])
|
|
_MARK[0], _MARK[1] = now, _MARK[1] + 1
|
|
|
|
|
|
def split() -> None:
|
|
"""Cut the current region graph here (only while compiling; free in eager)."""
|
|
if torch.compiler.is_compiling() and envs.DEEPINFER_NEURON_REGION_SPLIT:
|
|
_boundary()
|