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>
137 lines
5.2 KiB
Python
137 lines
5.2 KiB
Python
"""serve/prompt_tokenizer.py: every backend must give tokenizer.encode's ids (CPU only, needs the checkpoint's tokenizer)."""
|
||
|
||
from __future__ import annotations
|
||
|
||
import copy
|
||
import json
|
||
import os
|
||
import sys
|
||
import time
|
||
from pathlib import Path
|
||
from typing import Any
|
||
|
||
import pytest
|
||
|
||
REPO_ROOT = Path(__file__).resolve().parents[2]
|
||
sys.path.insert(0, str(REPO_ROOT / "serve"))
|
||
from prompt_tokenizer import PromptTokenizer # noqa: E402
|
||
|
||
MODEL = os.environ.get("DEEPINFER_HF_MODEL_DIR")
|
||
pytestmark = pytest.mark.skipif(not MODEL or not Path(MODEL, "tokenizer.json").exists(), reason="DEEPINFER_HF_MODEL_DIR not set")
|
||
|
||
|
||
@pytest.fixture(scope="module")
|
||
def tokenizer() -> Any:
|
||
from transformers import AutoTokenizer
|
||
|
||
return AutoTokenizer.from_pretrained(MODEL)
|
||
|
||
|
||
@pytest.fixture(scope="module")
|
||
def turns() -> list[str]:
|
||
"""A growing tool-use conversation rendered with the checkpoint's encode_messages, one prompt per turn."""
|
||
sys.path.insert(0, str(Path(MODEL, "encoding")))
|
||
from encoding import encode_messages
|
||
|
||
files = sorted((REPO_ROOT / "serve").glob("*.py")) + sorted((REPO_ROOT / "deepinfer" / "engine").glob("*.py"))[:8]
|
||
tools = [
|
||
{
|
||
"type": "function",
|
||
"function": {"name": "read_file", "parameters": {"type": "object", "properties": {"path": {"type": "string"}}}},
|
||
}
|
||
]
|
||
msgs = [
|
||
{"role": "system", "content": "You are a coding agent. 你是一个编程助手。", "tools": tools},
|
||
{"role": "user", "content": "Summarise serve/. 12345"},
|
||
]
|
||
out = []
|
||
for i, f in enumerate(files):
|
||
call = {"type": "function", "function": {"name": "read_file", "arguments": json.dumps({"path": str(f.name)})}}
|
||
msgs.append({"role": "assistant", "content": f"Step {i}: reading.", "tool_calls": [call]})
|
||
msgs.append({"role": "tool", "content": f.read_text()})
|
||
out.append(encode_messages(copy.deepcopy(msgs), thinking_mode="chat"))
|
||
return out
|
||
|
||
|
||
def available(tokenizer: Any) -> list[str]:
|
||
names = ["segment", "hf"]
|
||
try:
|
||
import fastokens # noqa: F401
|
||
|
||
names.insert(0, "fastokens")
|
||
except ImportError:
|
||
pass
|
||
return names
|
||
|
||
|
||
def test_backends_match_hf(tokenizer: Any, turns: list[str]) -> None:
|
||
for name in available(tokenizer):
|
||
pt = PromptTokenizer(tokenizer, backend=name, verify_rate=0)
|
||
assert pt.name == name
|
||
for prompt in turns:
|
||
assert pt.encode(prompt) == tokenizer.encode(prompt, add_special_tokens=False), name
|
||
|
||
|
||
def test_auto_picks_a_fast_backend(tokenizer: Any) -> None:
|
||
assert PromptTokenizer(tokenizer, verify_rate=0).name in ("fastokens", "segment")
|
||
|
||
|
||
def test_segment_cache_reuses_earlier_turns(tokenizer: Any, turns: list[str]) -> None:
|
||
pt = PromptTokenizer(tokenizer, backend="segment", verify_rate=0)
|
||
hf_s = seg_s = 0.0
|
||
for prompt in turns:
|
||
t0 = time.perf_counter()
|
||
ref = tokenizer.encode(prompt, add_special_tokens=False)
|
||
t1 = time.perf_counter()
|
||
assert pt.encode(prompt) == ref
|
||
hf_s, seg_s = hf_s + t1 - t0, seg_s + time.perf_counter() - t1
|
||
assert seg_s < hf_s
|
||
|
||
|
||
def test_segment_eviction_stays_exact(tokenizer: Any, turns: list[str]) -> None:
|
||
pt = PromptTokenizer(tokenizer, backend="segment", verify_rate=0, cache_bytes=4096)
|
||
for prompt in turns + turns[::-1]:
|
||
assert pt.encode(prompt) == tokenizer.encode(prompt, add_special_tokens=False)
|
||
|
||
|
||
def test_segment_with_strip_tokens(tokenizer: Any) -> None:
|
||
"""Added tokens that swallow neighbouring whitespace (lstrip/rstrip, as Phi-3 and others use) stay exact."""
|
||
from tokenizers import AddedToken
|
||
|
||
tok = copy.deepcopy(tokenizer)
|
||
tok.add_tokens([AddedToken("<|l|>", lstrip=True, normalized=False), AddedToken("<|r|>", rstrip=True, normalized=False)])
|
||
pt = PromptTokenizer(tok, backend="segment", verify_rate=0)
|
||
for text in ["a <|l|> b", "a<|r|> \n\t b", " <|l|><|r|> ", "x <|l|> y<|r|> z", "<|r|> ", " <|l|>"]:
|
||
assert pt.encode(text) == tok.encode(text, add_special_tokens=False), repr(text)
|
||
|
||
|
||
def test_runtime_mismatch_switches_to_hf(tokenizer: Any) -> None:
|
||
pt = PromptTokenizer(tokenizer, backend="segment", verify_rate=1.0)
|
||
pt._encode = lambda text: [0] # a backend gone wrong after startup
|
||
pt.encode("hello world")
|
||
for _ in range(200):
|
||
if pt.name == "hf":
|
||
break
|
||
time.sleep(0.01)
|
||
assert pt.name == "hf" and pt.stats["verify_mismatches"] == 1
|
||
assert pt.encode("hello world") == tokenizer.encode("hello world", add_special_tokens=False)
|
||
|
||
|
||
def test_backend_error_falls_back_to_hf(tokenizer: Any) -> None:
|
||
pt = PromptTokenizer(tokenizer, backend="segment", verify_rate=0)
|
||
|
||
def broken(text: str) -> list[int]:
|
||
raise RuntimeError("regex limit")
|
||
|
||
pt._encode = broken
|
||
assert pt.encode("hello") == tokenizer.encode("hello", add_special_tokens=False)
|
||
assert pt.stats["errors"] == 1
|
||
|
||
|
||
def test_forced_unavailable_backend_raises(tokenizer: Any, monkeypatch: pytest.MonkeyPatch) -> None:
|
||
monkeypatch.setitem(sys.modules, "fastokens", None)
|
||
monkeypatch.setitem(sys.modules, "fastokens._native", None)
|
||
with pytest.raises(RuntimeError, match="fastokens"):
|
||
PromptTokenizer(tokenizer, backend="fastokens")
|
||
assert PromptTokenizer(tokenizer, verify_rate=0).name == "segment"
|