Skip to content

Commit 28eb653

Browse files
committed
Fuse target temperature in rejection sampler
Avoid materializing an FP32 target-logits buffer for temperature-only speculative decoding requests by applying temperature in the rejection kernels. Assisted-by: OpenAI Codex Signed-off-by: Cheng Rui <286040359@qq.com>
1 parent 6b68db4 commit 28eb653

7 files changed

Lines changed: 547 additions & 41 deletions

File tree

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3+
4+
from functools import partial
5+
6+
import torch
7+
8+
from vllm.triton_utils import triton
9+
from vllm.utils.argparse_utils import FlexibleArgumentParser
10+
from vllm.v1.worker.gpu.sample.gumbel import apply_temperature
11+
from vllm.v1.worker.gpu.spec_decode.rejection_sampler_utils import (
12+
rejection_sample,
13+
)
14+
15+
16+
def make_inputs(
17+
batch_size: int,
18+
num_speculative_steps: int,
19+
vocab_size: int,
20+
temperature: float,
21+
dtype: torch.dtype,
22+
with_draft_logits: bool,
23+
) -> dict[str, torch.Tensor]:
24+
device = "cuda"
25+
num_logits = batch_size * (num_speculative_steps + 1)
26+
target_logits = torch.randn(num_logits, vocab_size, dtype=dtype, device=device)
27+
draft_sampled = torch.randint(
28+
vocab_size,
29+
(batch_size, num_speculative_steps + 1),
30+
dtype=torch.int64,
31+
device=device,
32+
)
33+
draft_sampled[:, 0] = 0
34+
idx_mapping = torch.arange(batch_size, dtype=torch.int32, device=device)
35+
expanded_idx_mapping = idx_mapping.repeat_interleave(num_speculative_steps + 1)
36+
expanded_local_pos = torch.arange(
37+
num_speculative_steps + 1, dtype=torch.int32, device=device
38+
).repeat(batch_size)
39+
40+
inputs = {
41+
"target_logits": target_logits,
42+
"draft_sampled": draft_sampled.flatten(),
43+
"cu_num_logits": torch.arange(batch_size + 1, dtype=torch.int32, device=device)
44+
* (num_speculative_steps + 1),
45+
"pos": torch.arange(num_logits, dtype=torch.int32, device=device),
46+
"idx_mapping": idx_mapping,
47+
"expanded_idx_mapping": expanded_idx_mapping,
48+
"expanded_local_pos": expanded_local_pos,
49+
"temperature": torch.full(
50+
(batch_size,), temperature, dtype=torch.float32, device=device
51+
),
52+
"seed": torch.arange(batch_size, dtype=torch.int64, device=device),
53+
}
54+
if with_draft_logits:
55+
inputs["draft_logits"] = torch.randn(
56+
batch_size,
57+
num_speculative_steps,
58+
vocab_size,
59+
dtype=dtype,
60+
device=device,
61+
)
62+
return inputs
63+
64+
65+
def run_baseline(
66+
inputs: dict[str, torch.Tensor], num_speculative_steps: int
67+
) -> tuple[torch.Tensor, torch.Tensor]:
68+
processed_logits = torch.empty_like(
69+
inputs["target_logits"], dtype=torch.float32
70+
).copy_(inputs["target_logits"])
71+
apply_temperature(
72+
processed_logits,
73+
inputs["expanded_idx_mapping"],
74+
inputs["temperature"],
75+
)
76+
return rejection_sample(
77+
target_logits=processed_logits,
78+
draft_logits=inputs.get("draft_logits"),
79+
num_speculative_steps=num_speculative_steps,
80+
**{
81+
key: value
82+
for key, value in inputs.items()
83+
if key not in ("target_logits", "draft_logits")
84+
},
85+
)
86+
87+
88+
def run_fused(
89+
inputs: dict[str, torch.Tensor], num_speculative_steps: int
90+
) -> tuple[torch.Tensor, torch.Tensor]:
91+
return rejection_sample(
92+
target_logits=inputs["target_logits"],
93+
draft_logits=inputs.get("draft_logits"),
94+
num_speculative_steps=num_speculative_steps,
95+
apply_target_temperature=True,
96+
**{
97+
key: value
98+
for key, value in inputs.items()
99+
if key not in ("target_logits", "draft_logits")
100+
},
101+
)
102+
103+
104+
def assert_outputs_equal(
105+
baseline: tuple[torch.Tensor, torch.Tensor],
106+
fused: tuple[torch.Tensor, torch.Tensor],
107+
num_speculative_steps: int,
108+
) -> None:
109+
baseline_sampled, baseline_num_sampled = baseline
110+
fused_sampled, fused_num_sampled = fused
111+
torch.testing.assert_close(fused_num_sampled, baseline_num_sampled, rtol=0, atol=0)
112+
steps = torch.arange(
113+
num_speculative_steps + 1, device=baseline_sampled.device
114+
).unsqueeze(0)
115+
valid = steps < baseline_num_sampled.unsqueeze(1)
116+
torch.testing.assert_close(
117+
fused_sampled[valid], baseline_sampled[valid], rtol=0, atol=0
118+
)
119+
120+
121+
def measure_peak_memory(callable_) -> int:
122+
torch.cuda.synchronize()
123+
torch.cuda.reset_peak_memory_stats()
124+
allocated = torch.cuda.memory_allocated()
125+
output = callable_()
126+
torch.cuda.synchronize()
127+
peak = torch.cuda.max_memory_allocated() - allocated
128+
del output
129+
return peak
130+
131+
132+
def main(args) -> None:
133+
dtype = getattr(torch, args.dtype)
134+
print(
135+
"batch rows baseline_ms fused_ms speedup "
136+
"baseline_peak_MiB fused_peak_MiB saved_MiB"
137+
)
138+
for batch_size in args.batch_sizes:
139+
inputs = make_inputs(
140+
batch_size,
141+
args.num_speculative_steps,
142+
args.vocab_size,
143+
args.temperature,
144+
dtype,
145+
args.with_draft_logits,
146+
)
147+
baseline_call = partial(run_baseline, inputs, args.num_speculative_steps)
148+
fused_call = partial(run_fused, inputs, args.num_speculative_steps)
149+
150+
baseline_output = baseline_call()
151+
fused_output = fused_call()
152+
torch.cuda.synchronize()
153+
assert_outputs_equal(baseline_output, fused_output, args.num_speculative_steps)
154+
del baseline_output, fused_output
155+
156+
baseline_ms = triton.testing.do_bench(
157+
baseline_call, warmup=args.warmup_ms, rep=args.rep_ms
158+
)
159+
fused_ms = triton.testing.do_bench(
160+
fused_call, warmup=args.warmup_ms, rep=args.rep_ms
161+
)
162+
baseline_peak = measure_peak_memory(baseline_call)
163+
fused_peak = measure_peak_memory(fused_call)
164+
mib = 1024**2
165+
print(
166+
f"{batch_size:5d} {batch_size * (args.num_speculative_steps + 1):4d} "
167+
f"{baseline_ms:11.3f} {fused_ms:8.3f} "
168+
f"{baseline_ms / fused_ms:7.3f}x "
169+
f"{baseline_peak / mib:17.1f} {fused_peak / mib:14.1f} "
170+
f"{(baseline_peak - fused_peak) / mib:9.1f}"
171+
)
172+
173+
174+
if __name__ == "__main__":
175+
parser = FlexibleArgumentParser(
176+
description="Benchmark in-kernel target temperature for rejection sampling."
177+
)
178+
parser.add_argument("--batch-sizes", type=int, nargs="+", default=[1, 8, 32, 128])
179+
parser.add_argument("--num-speculative-steps", type=int, default=4)
180+
parser.add_argument("--vocab-size", type=int, default=151936)
181+
parser.add_argument("--temperature", type=float, default=0.6)
182+
parser.add_argument(
183+
"--dtype", choices=["float32", "float16", "bfloat16"], default="bfloat16"
184+
)
185+
parser.add_argument("--with-draft-logits", action="store_true")
186+
parser.add_argument("--warmup-ms", type=int, default=100)
187+
parser.add_argument("--rep-ms", type=int, default=500)
188+
main(parser.parse_args())

0 commit comments

Comments
 (0)