Skip to content

Commit e3bb5f5

Browse files
chengduxiaowuchengduxiaowu
andauthored
[Feature] add synthetic mode for speculative decode (vllm-project#12256)
What this PR does / why we need it Ports upstream vLLM's rejection_sample_method="synthetic" (controlled-acceptance-rate spec-decode benchmarking) to vLLM-Ascend. Bug fixed: AscendRejectionSampler never passed spec_config/device to the base class, so synthetic_mode was never set — the synthetic config was silently ignored and standard rejection sampling ran instead. Changes (additive only — non-synthetic paths byte-for-byte unchanged) - worker/model_runner_v1.py: pass spec_config, device into AscendRejectionSampler so base init populates synthetic_mode/synthetic_conditional_rates (fp32, on-device, via upstream's unconditional_to_conditional_rates). - sample/rejection_sampler.py: forward synthetic_* through init/forward/rejection_sample; generate uniform_probs_for_greedy (fp64→fp32 for Ascend's fp32-only triton); add synthetic branches to the 3 pyTorch fallbacks; disable block_verify under synthetic with warning_once (its joint-cumprod verification conflicts with synthetic's per-token first-rejection Bernoulli, corrupting the acceptance distribution). - ops/triton/reject_sample.py: add SYNTHETIC_MODE: tl.constexpr branches to the greedy (spec_len=1 + general) and random kernels; thread the new params through the triton wrapper. block_verify kernel untouched. Correctness gotchas: fp64→fp32 at triton entry; int32/int64 mismatch handled (draft int32 vs target_argmax int64) via separate stores / tl.where with cast; recovered_token_ids reused as global ids under enable_reduce_sample. User-facing change Opt-in only. Set rejection_sample_method="synthetic" (+ synthetic_acceptance_length/rates) in --speculative-config. No default change — synthetic_mode=False makes all new branches dead (tl.constexpr-eliminated). Only side effect: modified triton kernels recompile once (output identical). block_verify auto-disabled with warning; reduce_sample/entropy_verify silently bypassed per-token. Testing - Static: py_compile on all 3 files; AST undefined-name check; git apply --check clean; call-site audit. - Runtime (NPU, TP=16): synthetic_acceptance_length=3, num_speculative_tokens=3 → metrics reported Mean acceptance length: 3.00, per-position 1.000, 1.000, 0.000, draft rate 66.7% — matches config ([1,1,0], (3−1)/3). Reproduced & fixed the int32/int64 triton ternary compile error. - Pending: synthetic × reduce_sample / entropy_verify / block_verify, mixed greedy+random batch, pyTorch fallback path, and synthetic-off regression matrix. vllm serve <model> --speculative-config \ '{"method":"eagle","model":"","num_speculative_tokens":3,"rejection_sample_method":"synthetic","synthetic_acceptance_length":3.0}' - vLLM version: v0.25.1 - vLLM main: vllm-project/vllm@54503ec --------- Signed-off-by: chengduxiaowu <wuchengtai@huawei-partners.com> Co-authored-by: chengduxiaowu <wuchengtai@huawei-partners.com>
1 parent 3d077f9 commit e3bb5f5

6 files changed

Lines changed: 529 additions & 34 deletions

File tree

.github/workflows/configs/nightly_config.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ a2:
3131
- name: MiniMax-M2.5-w8a8-QuaRot-A2
3232
os: linux-aarch64-a2b3-8
3333
config_file_path: MiniMax-M2.5-w8a8-QuaRot-A2.yaml
34+
- name: rejection-sample
35+
os: linux-aarch64-a2b3-1
36+
tests: tests/e2e/nightly/single_node/ops/singlecard_ops/triton/test_rejection_sample.py
3437
multi_node:
3538
test_config:
3639
- name: multi-node-qwen3-235b-dp

docs/source/user_guide/feature_guide/speculative_decoding.md

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ Suffix Decoding can achieve better performance for tasks with high repetition, s
187187
> Suffix Decoding requires Arctic Inference. You can install it with `pip install arctic-inference`.
188188

189189
- Offline inference
190-
190+
191191
```python
192192
from vllm import LLM, SamplingParams
193193

@@ -337,3 +337,56 @@ This entropy-aware threshold is controlled by two parameters:
337337
```
338338
339339
Both features can be enabled independently or together. When used together, the cumulative acceptance from Block Verify is combined with the entropy-adjusted threshold from Entropy Verify.
340+
341+
## Synthetic Rejection Sampling
342+
343+
In addition to the default `standard` rejection sampling (which accepts a draft token when `target_prob / draft_prob >= uniform`), vLLM Ascend supports **synthetic** rejection sampling. In this mode each draft token at position `i` is accepted with a configured probability `rates[i]`, compared against a uniform random draw — **independent of the target/draft probability match**. The first rejected position stops the request and emits a recovered token; if every draft token is accepted, the bonus token is appended.
344+
345+
### Why use it
346+
347+
Synthetic mode **decouples the verifier's acceptance logic from draft-model quality**, letting you drive the speculative-decoding pipeline at a controlled, fixed acceptance rate. It is useful for:
348+
349+
- **Benchmarking** the verify path (kernel + sampler + token assembly) throughput/latency at a known acceptance rate, without depending on a well-trained drafter.
350+
- **Kernel validation** — because acceptance reduces to `uniform < rate`, the result is fully determined by the inputs. The e2e test compares the Ascend Triton kernel (`SYNTHETIC_MODE=True`) against the PyTorch reference under identical inputs, so the two must agree bit-for-bit.
351+
- **Stress-testing** the high-acceptance regime of the speculative pipeline.
352+
353+
> [!WARNING]
354+
> Synthetic mode accepts draft tokens regardless of whether they match the target distribution, so the generated output is **not semantically correct**. Use it for benchmarking and validation only, **never for production serving**.
355+
356+
### How to use
357+
358+
Synthetic mode is configured through `speculative_config`, alongside the proposer settings:
359+
360+
- **`rejection_sample_method`** (str, default: `"standard"`): set to `"synthetic"` to enable.
361+
- **`synthetic_acceptance_rates`** (list[float], optional): per-position acceptance rates. Must have length `num_speculative_tokens`, each entry in `[0, 1]`, and be monotonically non-increasing.
362+
- **`synthetic_acceptance_length`** (float, optional): target mean acceptance length in `[1, num_speculative_tokens + 1]`, resolved internally to an equivalent `synthetic_acceptance_rates` schedule. Mutually exclusive with `synthetic_acceptance_rates`; exactly one must be provided when `rejection_sample_method == "synthetic"`.
363+
364+
**Offline inference**:
365+
366+
```python
367+
from vllm import LLM
368+
369+
llm = LLM(
370+
model="path/to/target/model",
371+
speculative_config={
372+
"method": "eagle3",
373+
"model": "path/to/draft/model",
374+
"num_speculative_tokens": 3,
375+
"rejection_sample_method": "synthetic",
376+
"synthetic_acceptance_rates": [0.9, 0.6, 0.3],
377+
},
378+
)
379+
```
380+
381+
**Online serving**:
382+
383+
```shell
384+
vllm serve path/to/target/model \
385+
--speculative-config '{"method": "eagle3", "model": "path/to/draft/model", \
386+
"num_speculative_tokens": 3, "rejection_sample_method": "synthetic", \
387+
"synthetic_acceptance_rates": [0.9, 0.6, 0.3]}'
388+
```
389+
390+
### Ascend-specific notes
391+
392+
Upstream vLLM's synthetic path draws the per-token uniform numbers with `tl_rand64` (fp64) directly inside the Triton kernel. NPU Triton does **not** support `tl_rand64`/fp64, so vLLM Ascend reimplements synthetic mode in `vllm_ascend/sample/rejection_sampler.py` and `vllm_ascend/ops/triton/reject_sample.py`: the kernels receive the uniform probabilities as an explicit pointer argument instead of generating them internally. The probabilities are produced by `generate_uniform_probs` in fp64 (matching upstream, so the draw never samples exactly `0.0`) and cast to fp32 for the greedy kernels, which the fp32-only Ascend Triton kernels then compare against `rates[pos]`. This is why synthetic mode is available on the Ascend `v1/sample` rejection-sampler path.

0 commit comments

Comments
 (0)