mirror of
https://github.com/deepseek-ai/DeepInfer.git
synced 2026-10-28 23:07:09 +08:00
- bench_neuron.py --dataset vllm-sonnet: vLLM's SonnetDataset as #71's bench builds it for the completions backend ("Pick as many lines..." + random poem lines, chat-formatted; --chat_tokenizer for a template the checkpoint lacks). --dataset sonnet: a contiguous text window. --dump: prompt/output token ids. - reference_dspark.py: the official inference/model.py forward_spec on the host CPU (tiny checkpoint, teacher-forced on reference_golden.py tokens): each decode step's 5 drafts and their logits. - run_deepinfer_tiny.py RUN_TINY_DRAFTS=1 prints every draft block. DeepInfer's drafts match the reference on host eager, on device and with attention TP: every divergence is a near tie (top-1/top-2 gap 0.001-0.055). With #71's prompt format the acceptance is #71's: 2.8 tokens a step at 512 (first position 65-79%); a raw text window gave 1.4 (continuations not in the context), random token ids 3.85 (repetitive outputs). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
255 lines
14 KiB
Python
255 lines
14 KiB
Python
"""DSpark draft ground truth: the OFFICIAL DeepSeek-V4.1 reference (inference/model.py forward_spec) on the host CPU.
|
|
|
|
Teacher-forced on reference_golden.py tokens; records each decode step\'s 5 drafts. Shares the loading code of
|
|
reference_golden.py (copied below the docstring).
|
|
|
|
Original reference_golden.py docstring follows.
|
|
|
|
Ground truth for the Neuron port: run the OFFICIAL DeepSeek-V4.1 reference model on the host CPU.
|
|
|
|
The downloaded reference (`inference/model.py`, `engram.py`, `convert.py`, ...) is used unmodified; only its
|
|
TileLang `kernel` module is swapped for the pure-torch `ref_kernels_torch` (same numerics, see that file).
|
|
DSpark/MTP (`dspark_block_size=0`) and vision (`vision_n_layers=0`) are disabled: DeepInfer runs text-only with
|
|
DEEPINFER_USE_NEXTN_DECODE=0, and neither affects the backbone logits.
|
|
|
|
Steps:
|
|
1. convert the HF checkpoint with the official convert.py (MP=1, --expert-dtype fp4) unless already done;
|
|
2. build reference ModelArgs from the HF config.json (same mapping as the official inference/config.json);
|
|
3. per prompt (batch 1, fresh model): prefill the whole prompt, then N greedy decode steps (temperature 0,
|
|
no EOS stop), saving logits (fp32), greedy tokens, per-layer block outputs, indexer top-k and engram hashes.
|
|
|
|
Usage (on the box, host CPU only; TORCH_DEVICE_BACKEND_AUTOLOAD=0 keeps torch_neuronx/NRT out of the process):
|
|
TORCH_DEVICE_BACKEND_AUTOLOAD=0 ~/di/venv/bin/python -I examples/neuron/reference_golden.py \
|
|
--hf-ckpt ~/di/tiny_ckpt --ref-dir ~/di/ref_dsv41/inference --converted ~/di/tiny_ckpt_ref_mp1 \
|
|
--out ~/di/golden --summary-dir examples/neuron/golden
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import dataclasses
|
|
import functools
|
|
import hashlib
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
PROMPTS = {
|
|
# short: no window wrap, no compressed top-k selection
|
|
"short": "The quick brown fox jumps over the lazy dog.",
|
|
# medium (~200 tokens): wraps the 128-token sliding window and makes both indexer top-k levels select
|
|
"medium": (
|
|
"In the early morning the harbor was quiet. Fishing boats rocked gently against the wooden piers, "
|
|
"their ropes creaking as the tide came in. An old man sat on an overturned crate, mending a net with "
|
|
"slow, careful hands. He had worked these waters for more than fifty years, and he could read the "
|
|
"weather in the color of the sky and the shape of the clouds. Today the sky was pale gray, and a thin "
|
|
"mist hung over the water. Somewhere a gull cried, and the sound echoed off the stone walls of the "
|
|
"warehouses. A young girl ran down the pier, her boots clattering on the boards. She stopped beside "
|
|
"the old man and asked him whether the boats would go out today. He looked at the horizon for a long "
|
|
"moment before answering. The sea, he said, decides that for us. We only listen."
|
|
),
|
|
}
|
|
# long (~700 tokens): more than candidate_topk_blocks * candidate_block_size = 512 compressed positions in the
|
|
# ratio-1 layers, so the candidate block pre-filter is active as well
|
|
PROMPTS["long"] = " ".join(
|
|
[
|
|
PROMPTS["medium"],
|
|
"The history of computing is a story of abstraction. Early machines were programmed by rewiring "
|
|
"panels and flipping switches, and every program was tied to the physical layout of the hardware. "
|
|
"Assembly languages gave names to instructions, and compilers later translated whole expressions "
|
|
"into sequences of those instructions. Operating systems hid the details of disks and terminals "
|
|
"behind files and streams, while virtual memory let each process pretend it owned the entire machine.",
|
|
"Networks added another layer. Packets travel through routers that know nothing about the "
|
|
"applications sending them, and protocols stacked on top of one another turn unreliable links into "
|
|
"dependable conversations. A web page that appears in a fraction of a second may involve dozens of "
|
|
"servers, caches and databases spread across several continents, each one following rules that were "
|
|
"agreed upon decades ago by people who could not have imagined how the system would grow.",
|
|
"Mathematics offers a similar lesson. A proof about prime numbers does not care whether the numbers "
|
|
"are written in decimal or binary, on paper or in silicon. The same theorem holds in every notation, "
|
|
"and that independence is exactly what makes it useful. Engineers borrow this idea when they define "
|
|
"interfaces: as long as both sides respect the contract, either side can be rewritten, optimized or "
|
|
"replaced without the other noticing.",
|
|
"Cooking, perhaps surprisingly, works the same way. A recipe names its ingredients and steps but "
|
|
"leaves the choice of pan, stove and kitchen to the cook. Bread rises because yeast consumes sugar "
|
|
"and releases gas, whether the dough is kneaded by hand in a village bakery or by a machine in a "
|
|
"factory. Understanding the underlying chemistry lets a baker adapt when the flour is different, the "
|
|
"air is humid, or the oven runs hot.",
|
|
"Music, too, depends on structure. A melody can be transposed into another key, played on a different "
|
|
"instrument, or slowed down to half its tempo, and listeners will still recognize it. Composers rely on "
|
|
"patterns of tension and release, repetition and variation, to guide the ear through a piece. The notes "
|
|
"on the page are only instructions; the performance brings them to life in a particular room on a "
|
|
"particular evening, never exactly the same twice.",
|
|
"Gardens change with every season. In spring the first green shoots push through the cold soil, and by "
|
|
"summer the beds are crowded with tomatoes, beans and sunflowers that lean toward the light. Autumn "
|
|
"brings the harvest and the slow work of clearing dead stems, while winter rests under frost and snow. "
|
|
"A patient gardener learns that each year is different and that the soil remembers what was done to it.",
|
|
"At the end of the day the harbor grew quiet again. The boats returned one by one, low in the water "
|
|
"with their catch, and the old man folded his net and walked slowly home along the shore.",
|
|
]
|
|
)
|
|
|
|
|
|
def hf_to_model_args(hf: dict) -> dict:
|
|
"""HF config.json -> reference ModelArgs kwargs (field mapping as in the official inference/config.json)."""
|
|
t = hf["text_config"]
|
|
v = hf["vision_config"]
|
|
rs = t["rope_scaling"]
|
|
return dict(
|
|
vocab_size=t["vocab_size"],
|
|
dim=t["hidden_size"],
|
|
moe_inter_dim=t["moe_intermediate_size"],
|
|
n_layers=t["num_hidden_layers"],
|
|
n_mtp_layers=t["num_nextn_predict_layers"],
|
|
dspark_block_size=t["dspark_block_size"],
|
|
dspark_noise_token_id=t["dspark_noise_token_id"],
|
|
dspark_target_layer_ids=tuple(t["dspark_target_layer_ids"]),
|
|
dspark_markov_rank=t["dspark_markov_rank"],
|
|
dspark_n_routed_experts=t["dspark_n_routed_experts"],
|
|
dspark_n_activated_experts=t["dspark_num_experts_per_tok"],
|
|
n_heads=t["num_attention_heads"],
|
|
n_routed_experts=t["n_routed_experts"],
|
|
n_shared_experts=t["n_shared_experts"],
|
|
n_activated_experts=t["num_experts_per_tok"],
|
|
score_func=t["scoring_func"],
|
|
norm_topk_prob=t["norm_topk_prob"],
|
|
route_scale=t["routed_scaling_factor"],
|
|
swiglu_limit=t["swiglu_limit"],
|
|
q_lora_rank=t["q_lora_rank"],
|
|
head_dim=t["head_dim"],
|
|
rope_head_dim=t["qk_rope_head_dim"],
|
|
norm_eps=t["rms_norm_eps"],
|
|
o_groups=t["o_groups"],
|
|
o_lora_rank=t["o_lora_rank"],
|
|
window_size=t["sliding_window"],
|
|
kv_source_layers=tuple(t["kv_source_layer_ids"]),
|
|
index_source_layers=tuple(t["index_source_layer_ids"]),
|
|
original_seq_len=rs["original_max_position_embeddings"],
|
|
rope_theta=t["rope_theta"],
|
|
rope_factor=rs["factor"],
|
|
beta_fast=rs["beta_fast"],
|
|
beta_slow=rs["beta_slow"],
|
|
index_n_heads=t["index_n_heads"],
|
|
index_head_dim=t["index_head_dim"],
|
|
index_topk=t["index_topk"],
|
|
candidate_source_layer=t["candidate_source_layer_id"],
|
|
candidate_topk_blocks=t["candidate_topk_blocks"],
|
|
candidate_block_size=t["candidate_block_size"],
|
|
hc_mult=t["hc_mult"],
|
|
hc_sinkhorn_iters=t["hc_sinkhorn_iters"],
|
|
hc_eps=t["hc_eps"],
|
|
engram_layer_ids=tuple(t["engram_layer_ids"]),
|
|
engram_vocab_size=t["engram_vocab_size"],
|
|
engram_num_embeddings=tuple(t["engram_num_embeddings"]),
|
|
engram_max_ngram_size=t["engram_max_ngram_size"],
|
|
engram_pad_id=t["engram_pad_token_id"],
|
|
engram_compressed_vocab_size=t["engram_compressed_vocab_size"],
|
|
engram_n_heads=t["engram_n_heads"],
|
|
engram_head_dim=t["engram_head_dim"],
|
|
dtype="fp8" if hf["quantization_config"]["quant_method"] == "fp8" else "bf16",
|
|
expert_dtype=hf["quantization_config"]["expert_dtype"],
|
|
compress_rope_theta=t["compress_rope_theta"],
|
|
compress_ratios=tuple(t["compress_ratios"]),
|
|
vision_n_layers=v["num_hidden_layers"],
|
|
vision_dim=v["hidden_size"],
|
|
vision_n_heads=v["num_attention_heads"],
|
|
vision_inter_dim=v["intermediate_size"],
|
|
vision_patch_size=v["patch_size"],
|
|
vision_rope_theta=v["rope_theta"],
|
|
vision_downsample_ratio=v["downsample_ratio"],
|
|
vision_max_n_token=v["max_image_tokens"],
|
|
vision_min_pixels=v["min_pixels"],
|
|
vision_max_wh_ratio=v["max_wh_ratio"],
|
|
image_token_id=hf["image_token_id"],
|
|
)
|
|
|
|
|
|
def convert(ref_dir: str, hf_ckpt: str, out: str) -> str:
|
|
path = os.path.join(out, "model0-mp1.safetensors")
|
|
if os.path.exists(path):
|
|
print(f"[golden] using existing converted checkpoint {path}")
|
|
return path
|
|
cmd = [sys.executable, "-I", os.path.join(ref_dir, "convert.py"), "--hf-ckpt-path", hf_ckpt, "--save-path", out,
|
|
"--model-parallel", "1", "--expert-dtype", "fp4"]
|
|
print("[golden] $", " ".join(cmd))
|
|
subprocess.run(cmd, check=True, cwd="/tmp")
|
|
return path
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("--hf-ckpt", default="~/di/tiny_ckpt")
|
|
ap.add_argument("--ref-dir", default="~/di/ref_dsv41/inference", help="copy of the official inference/ dir")
|
|
ap.add_argument("--converted", default="~/di/tiny_ckpt_ref_mp1")
|
|
ap.add_argument("--golden-dir", default="~/di/golden", help="reference_golden.py outputs (tokens to feed)")
|
|
ap.add_argument("--out", default="~/di/golden_dspark")
|
|
ap.add_argument("--prompts", default="short,long")
|
|
ap.add_argument("--threads", type=int, default=16)
|
|
ap.add_argument("--max-seq-len", type=int, default=2048)
|
|
args = ap.parse_args()
|
|
for k in ("hf_ckpt", "ref_dir", "converted", "golden_dir", "out"):
|
|
setattr(args, k, os.path.abspath(os.path.expanduser(getattr(args, k))))
|
|
os.makedirs(args.out, exist_ok=True)
|
|
|
|
import torch
|
|
|
|
torch.set_num_threads(args.threads)
|
|
assert "torch_neuronx" not in sys.modules, "run with TORCH_DEVICE_BACKEND_AUTOLOAD=0 (host CPU only)"
|
|
sys.path.insert(0, HERE)
|
|
import ref_kernels_torch
|
|
|
|
sys.modules["kernel"] = ref_kernels_torch
|
|
sys.path.insert(1, args.ref_dir)
|
|
import engram as ref_engram
|
|
import model as ref_model
|
|
from safetensors.torch import load_file
|
|
from transformers import AutoTokenizer
|
|
|
|
ref_engram.build_compressed_token_map = functools.lru_cache(maxsize=None)(ref_engram.build_compressed_token_map)
|
|
ckpt_file = convert(args.ref_dir, args.hf_ckpt, args.converted)
|
|
hf_cfg = json.load(open(os.path.join(args.hf_ckpt, "config.json")))
|
|
margs = hf_to_model_args(hf_cfg)
|
|
margs.update(max_batch_size=1, max_seq_len=args.max_seq_len, temperature=0.0, vision_n_layers=0)
|
|
model_args = ref_model.ModelArgs(**margs)
|
|
tokenizer = AutoTokenizer.from_pretrained(args.converted)
|
|
state = load_file(ckpt_file)
|
|
dropped = sorted(k for k in state if k.startswith(("vision.", "aligner.", "image_")) or k.endswith(".bias_vl"))
|
|
state = {k: v for k, v in state.items() if k not in set(dropped)}
|
|
torch.set_default_dtype(torch.bfloat16)
|
|
for name in args.prompts.split(","):
|
|
gold = torch.load(os.path.join(args.golden_dir, f"{name}.pt"), weights_only=False)
|
|
ids, gtok = list(gold["prompt_ids"]), list(gold["tokens"])
|
|
torch.manual_seed(0)
|
|
with torch.device("cpu"):
|
|
model = ref_model.Transformer(model_args, tokenizer)
|
|
missing, unexpected = model.load_state_dict(state, strict=False)
|
|
# the MTP stages share the backbone's embed / head (assigned in Transformer.__init__)
|
|
missing = [k for k in missing if not (k.startswith("mtp.") and k.endswith((".embed.weight", ".head.weight")))]
|
|
assert not missing and not unexpected, (missing, unexpected)
|
|
model.eval()
|
|
x = torch.tensor([ids], dtype=torch.long)
|
|
out_ids, logits, main_hidden = model(x, 0)
|
|
model.forward_spec(out_ids, main_hidden, 0) # seeds the DSpark window KV
|
|
pos = len(ids)
|
|
drafts, draft_logits = [], []
|
|
# decode step k is fed golden token k (teacher forcing); its drafts follow the token it predicts
|
|
for k in range(len(gtok) - 1):
|
|
x = torch.tensor([[gtok[k]]], dtype=torch.long)
|
|
out_ids, logits, main_hidden = model(x, pos)
|
|
res = model.forward_spec(out_ids, main_hidden, pos)
|
|
pos += 1
|
|
spec_ids, spec_logits, conf = res
|
|
drafts.append(spec_ids[0, 1:].tolist())
|
|
draft_logits.append(spec_logits[0].float().clone())
|
|
print(f"[dspark] {name} step {k}: fed {gtok[k]}, main argmax {int(out_ids[0])} (golden {gtok[k + 1]}), "
|
|
f"drafts {drafts[-1]}", flush=True)
|
|
torch.save({"name": name, "prompt_ids": ids, "fed": gtok[:-1], "drafts": drafts, "draft_logits": draft_logits},
|
|
os.path.join(args.out, f"{name}.pt"))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|