DeepInfer/tests/serve/test_prompt_tokenizer.py
whn09 d8f7a2e898 Neuron: PR #147 OpenAI server (+ /v1/completions, Neuron worker setup), offline bench, MoE fixes (#26, #27, #29)
- 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>
2026-10-23 16:23:28 +08:00

137 lines
5.2 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""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"