Skip to content

Commit a5c1d5f

Browse files
authored
[NV] [PR2] feat(synthetic-al): add vLLM synthetic-acceptance backend / [NV] [PR2] feat(synthetic-al):添加 vLLM 合成接受率后端
Add the vLLM recipe rewrite backend for controlled synthetic acceptance. / 添加用于受控合成接受率的 vLLM 配方重写后端。
1 parent 205f217 commit a5c1d5f

2 files changed

Lines changed: 76 additions & 3 deletions

File tree

runners/synthetic_injectors/__init__.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,8 @@
1919
"""
2020

2121
# framework value passed by the runner (e.g. "dynamo-vllm") -> backend module.
22-
# Populated by backend modules registering themselves (see e.g. vllm.py, added
23-
# in the framework-support PR). Empty here: the generic layer alone injects
24-
# nothing, so merging it changes no behavior.
22+
# Populated by backend modules registering themselves at import time (see the
23+
# `from . import` block below).
2524
_INJECTORS = {}
2625

2726

@@ -32,3 +31,8 @@ def register(framework, backend):
3231
def get_injector(framework):
3332
"""Return the backend for ``framework`` (as passed by the runner), or None."""
3433
return _INJECTORS.get(framework)
34+
35+
36+
# Import backends after register/get_injector are defined so each module can
37+
# call register() at import time. Add new frameworks (sglang, trtllm, ...) here.
38+
from . import vllm # noqa: E402,F401
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
"""vLLM synthetic-acceptance backend (FRAMEWORK=dynamo-vllm).
2+
3+
Rewrites every ``speculative-config: '<json>'`` entry in an srt-slurm recipe to
4+
use synthetic rejection sampling: it adds ``rejection_sample_method=synthetic``
5+
and ``synthetic_acceptance_length=<al>`` to the JSON so the engine emits a
6+
controlled mean acceptance length instead of running the real draft model.
7+
8+
Registered under the "dynamo-vllm" framework key at import time, so importing
9+
the ``synthetic_injectors`` package is enough for the generic driver to resolve
10+
this backend.
11+
"""
12+
13+
import json
14+
import re
15+
import sys
16+
17+
from . import register
18+
19+
# Matches `speculative-config: '<json>'` (single-quoted JSON, as written in the
20+
# recipe YAML). Capturing the JSON lets us edit it with the json module instead
21+
# of string-munging, so we never produce malformed quoting.
22+
_SPEC_CONFIG_RE = re.compile(r"speculative-config:\s*'([^']+)'")
23+
24+
25+
def spec_tokens_from_recipe(text):
26+
"""Best-effort: read num_speculative_tokens from the recipe itself."""
27+
for m in _SPEC_CONFIG_RE.finditer(text):
28+
try:
29+
spec = json.loads(m.group(1))
30+
except json.JSONDecodeError:
31+
continue
32+
n = spec.get("num_speculative_tokens")
33+
if n:
34+
return int(n)
35+
return 2
36+
37+
38+
def rewrite(content, al, log):
39+
"""Rewrite every speculative-config entry to synthetic acceptance.
40+
41+
Returns ``(new_content, count)`` where count is the number of entries
42+
modified (0 => nothing matched, recipe left unchanged by the driver).
43+
"""
44+
before = [ln.strip() for ln in content.splitlines() if _SPEC_CONFIG_RE.search(ln)]
45+
if before:
46+
log("Before:")
47+
for ln in before:
48+
print(f" {ln}")
49+
50+
def _replace(match):
51+
spec = json.loads(match.group(1))
52+
spec["rejection_sample_method"] = "synthetic"
53+
spec["synthetic_acceptance_length"] = al
54+
# Compact separators keep the same style as the hand-written recipes.
55+
return "speculative-config: '" + json.dumps(spec, separators=(",", ":")) + "'"
56+
57+
new_content, count = _SPEC_CONFIG_RE.subn(_replace, content)
58+
59+
if count:
60+
after = [ln.strip() for ln in new_content.splitlines() if _SPEC_CONFIG_RE.search(ln)]
61+
if after:
62+
log("After:")
63+
for ln in after:
64+
print(f" {ln}")
65+
66+
return new_content, count
67+
68+
69+
register("dynamo-vllm", sys.modules[__name__])

0 commit comments

Comments
 (0)