mirror of
https://github.com/deepseek-ai/DeepInfer.git
synced 2026-10-28 14:57:09 +08:00
bench18 (512 in, 128 out, TP=64, nextn=5): acceptance 3.85 tokens a step, TPOT 465.9 ms. bench21 with the table path for 6-row steps: acceptance 1.27, TPOT 848 ms, so verification keeps the per-row path. Whole-layer 6-row graphs crash neuronx-cc on the full model (DEEPINFER_NEURON_SPLIT_MIN_ROWS, default 1 = split as before). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
160 lines
7.7 KiB
Python
160 lines
7.7 KiB
Python
"""TTFT / TPOT benchmark of the full DeepSeek-V4.1-Flash on one trn2.48xlarge, the vllm-neuron#71 method offline.
|
|
|
|
vllm-neuron#71 (``contrib/models/deepseek_v41/test/performance/run_bench.py``) serves the model and drives it with
|
|
``vllm bench serve --dataset-name random``: random token ids fix the input length exactly and share no prefix,
|
|
output 128 tokens, and per cell it reports the median TTFT, the median TPOT (decode only:
|
|
``(latency - ttft) / (output_tokens - 1)``) and the decode tokens per second. DeepInfer has no server, so this drives
|
|
the engine directly with the same requests: random prompt token ids of the cell's input length, ``ignore_eos`` and
|
|
``max_tokens`` 128, timed per request from submission.
|
|
|
|
Topology: one engine process per DP rank (EP=64, DP-attention), and every rank runs its own request of the cell at
|
|
the same time: batch 1 per rank, 64 requests in flight (the engine steps all DP ranks together). #71's bs1 cells are
|
|
one request for the whole machine; per request the two loads cost the same here, since the static MoE computes
|
|
every rank's row either way.
|
|
|
|
export DEEPINFER_ACCELERATOR=neuron WORLD_SIZE=1 RANK=0 DEEPINFER_NEURON_PREFILL_BUCKET=128 \\
|
|
DEEPINFER_MAX_BACKTRACKING_TOKENS=128
|
|
python examples/neuron/bench_neuron.py --model_name /mnt/nvme/models/DeepSeek-V4.1-Flash-1017 --ep 64
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
import statistics
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
|
|
EXAMPLES = Path(__file__).resolve().parent.parent
|
|
sys.path.insert(0, str(EXAMPLES))
|
|
|
|
from _config import DEFAULT_MODEL_NAME, create_llm, setup_common_env, setup_cuda_visible_devices # noqa: E402
|
|
from _launch import launch_dp # noqa: E402
|
|
|
|
parser = argparse.ArgumentParser(description="V4.1 TTFT/TPOT benchmark on Neuron (vllm-neuron#71 method)")
|
|
parser.add_argument("--model_name", default=DEFAULT_MODEL_NAME)
|
|
parser.add_argument("--ep", default=64, type=int)
|
|
parser.add_argument("--cp", default=1, type=int, help="TP group size: attention heads sharded over cp ranks running one batch (issue #28)")
|
|
parser.add_argument("--input_lens", default="512,1024,2048,8192", help="comma-separated prompt lengths (tokens)")
|
|
parser.add_argument("--output_len", default=128, type=int)
|
|
parser.add_argument("--repeats", default=3, type=int, help="requests per cell (medians are reported)")
|
|
parser.add_argument("--warmup", default=1, type=int, help="untimed requests before the first cell")
|
|
parser.add_argument("--num_kv_blocks", default=400, type=int)
|
|
parser.add_argument("--max_model_len", default=16384, type=int)
|
|
parser.add_argument("--max_num_batched_tokens", default=128, type=int)
|
|
parser.add_argument("--max_num_seqs", default=1, type=int)
|
|
parser.add_argument("--nextn", default=0, type=int, help="DSpark drafts per step (0 = plain decode)")
|
|
parser.add_argument("--out", default="", help="write the results as JSON here (rank 0)")
|
|
args = parser.parse_args()
|
|
dp_size = args.ep // args.cp
|
|
|
|
|
|
def _fixed_kv_pool(num_blocks: int) -> None:
|
|
os.environ["DEEPINFER_NEURON_NUM_KV_BLOCKS"] = str(num_blocks) # CP followers read it too
|
|
from deepinfer.worker.base import WorkerBase
|
|
|
|
def profile_num_available_blocks(self): # noqa: ANN001, ANN202
|
|
self.model_runner.set_block_size(self.cache_config.block_size)
|
|
self.cache_config.num_gpu_blocks = num_blocks
|
|
return num_blocks
|
|
|
|
WorkerBase.profile_num_available_blocks = profile_num_available_blocks
|
|
|
|
|
|
def _random_prompt(length: int, seed: int, vocab: int) -> list[int]:
|
|
import numpy as np
|
|
|
|
rng = np.random.default_rng(seed)
|
|
return rng.integers(1000, vocab - 1000, size=length, dtype=np.int64).astype(np.int32).tolist()
|
|
|
|
|
|
def _run_one(llm, prompt: list[int] | None, params) -> tuple[float, float, int] | None: # noqa: ANN001
|
|
"""Run the engine until it drains. With a prompt, returns (ttft_s, latency_s, output_tokens, steps_with_output) for it."""
|
|
engine = llm.llm_engine
|
|
start = time.perf_counter()
|
|
if prompt is not None:
|
|
llm._add_request(None, params, prompt)
|
|
first = last = None
|
|
tokens = steps = 0
|
|
while True:
|
|
unfinished = engine.has_unfinished_requests()
|
|
outputs = engine.step() if unfinished else engine.flush_inflight_reqs()
|
|
now = time.perf_counter()
|
|
steps += len(outputs) > 0
|
|
for out in outputs:
|
|
tokens += 1
|
|
first = first if first is not None else now
|
|
last = now
|
|
if not unfinished:
|
|
break
|
|
if prompt is None:
|
|
return None
|
|
return first - start, last - start, tokens, steps
|
|
|
|
|
|
def main(dp_size: int, dp_rank: int) -> None:
|
|
os.environ["DEEPINFER_USE_NEXTN_DECODE"] = str(args.nextn)
|
|
os.environ.setdefault("DEEPINFER_ACCELERATOR", "neuron")
|
|
setup_common_env(model_name=args.model_name, sampler_force_topk=0, set_override_experts=False, enable_eplb=False)
|
|
from deep_cluster.hardware import get_actual_num_gpus
|
|
|
|
setup_cuda_visible_devices(dp_rank, args.cp, get_actual_num_gpus())
|
|
_fixed_kv_pool(args.num_kv_blocks)
|
|
llm = create_llm(
|
|
args.model_name,
|
|
dp_rank,
|
|
ep_size=args.ep,
|
|
cp_size=args.cp,
|
|
env={"DEEPINFER_LOGGING_INTERVAL_STEP": "100"},
|
|
data_parallel_size=dp_size,
|
|
max_num_batched_tokens=args.max_num_batched_tokens,
|
|
max_model_len=args.max_model_len,
|
|
max_num_seqs=args.max_num_seqs,
|
|
gpu_memory_utilization=0.85,
|
|
lookahead_tokens=1 if args.nextn else 0,
|
|
)
|
|
from deepinfer.sampling_params import SamplingParams
|
|
|
|
params = SamplingParams(temperature=0.0, max_tokens=args.output_len, ignore_eos=True)
|
|
vocab = llm.llm_engine.tokenizer.vocab_size if hasattr(llm.llm_engine.tokenizer, "vocab_size") else 128000
|
|
busy = True
|
|
lens = [int(x) for x in args.input_lens.split(",")]
|
|
|
|
for w in range(args.warmup):
|
|
_run_one(llm, _random_prompt(lens[0], 10_000 + w + 7919 * dp_rank, vocab) if busy else None, params)
|
|
results = []
|
|
for length in lens:
|
|
# One untimed request a cell: a cell's first request pays for its own context bucket's lazy compile.
|
|
_run_one(llm, _random_prompt(length, 20_000 + length + 7919 * dp_rank, vocab) if busy else None, params)
|
|
cell = []
|
|
for r in range(args.repeats):
|
|
got = _run_one(llm, _random_prompt(length, 1000 * length + r + 7919 * dp_rank, vocab) if busy else None, params)
|
|
if got is not None:
|
|
ttft, latency, n, steps = got
|
|
tpot = (latency - ttft) / max(n - 1, 1)
|
|
accept = (n - 1) / max(steps - 1, 1) # tokens per decode step (the acceptance length with DSpark)
|
|
cell.append((ttft, tpot, n, accept))
|
|
if dp_rank == 0:
|
|
print(f"BENCH input={length} rep={r} ttft_ms={ttft * 1e3:.1f} tpot_ms={tpot * 1e3:.1f} tokens={n} "
|
|
f"tokens_per_step={accept:.2f}", flush=True)
|
|
if dp_rank == 0 and cell:
|
|
ttft = statistics.median(c[0] for c in cell)
|
|
tpot = statistics.median(c[1] for c in cell)
|
|
results.append({"input": length, "output": args.output_len, "ttft_p50_ms": ttft * 1e3, "tpot_p50_ms": tpot * 1e3,
|
|
"decode_tok_s_per_request": 1.0 / tpot,
|
|
"tokens_per_step": statistics.median(c[3] for c in cell)})
|
|
if dp_rank == 0:
|
|
print("BENCH_RESULTS " + json.dumps(results), flush=True)
|
|
print(f"| input | output | TTFT p50 (ms) | TPOT p50 (ms) | decode tok/s per request | (batch 1 per DP rank)")
|
|
for r in results:
|
|
print(f"| {r['input']} | {r['output']} | {r['ttft_p50_ms']:.0f} | {r['tpot_p50_ms']:.1f} | {r['decode_tok_s_per_request']:.2f} |")
|
|
if args.out:
|
|
Path(args.out).write_text(json.dumps(results, indent=2))
|
|
llm.close()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
launch_dp(main, dp_size)
|