main
py 201 lines 7.54 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 from typing import Literal
5
6 import numpy as np
7 import torch
8
9 from vllm.logger import init_logger
10 from vllm.sampling_params import SamplingParams
11 from vllm.v1.sample.ops.topk_topp_sampler import apply_top_k_top_p
12 from vllm.v1.watermarking.watermarker import RandomSampler, Watermarker
13 from vllm.v1.worker.gpu.buffer_utils import UvaBackedTensor
14 from vllm.v1.worker.gpu.sample.sampler import Sampler
15 from vllm.v1.worker.gpu.sample.watermark import repeated_context_mask
16
17 logger = init_logger(__name__)
18
19
20 class GPUWatermarkSampler(Sampler):
21 def __init__(
22 self,
23 watermarker: Watermarker,
24 *args,
25 deduplicate_contexts: Literal["none", "single_turn", "all"] = "single_turn",
26 deduplicate_contexts_max_history: int | None = 8192,
27 **kwargs,
28 ) -> None:
29 super().__init__(*args, **kwargs)
30 self.watermarker = watermarker
31 self.deduplicate_contexts = deduplicate_contexts
32 self.deduplicate_contexts_max_history = deduplicate_contexts_max_history
33 self.watermarking = UvaBackedTensor(
34 self.sampling_states.max_num_reqs, dtype=torch.bool
35 )
36 self.watermarking.np.fill(True)
37 self.watermarking.copy_to_uva()
38
39 def add_request(self, req_idx: int, sampling_params: SamplingParams) -> None:
40 super().add_request(req_idx, sampling_params)
41 self.watermarking.np[req_idx] = sampling_params.watermarking
42 if sampling_params.watermarking and sampling_params.temperature == 0:
43 logger.warning_once(
44 "Watermarking is enabled, but greedy decoding "
45 "(temperature=0) cannot be watermarked. This request will use "
46 "ordinary greedy sampling."
47 )
48
49 def apply_staged_writes(self) -> None:
50 super().apply_staged_writes()
51 self.watermarking.copy_to_uva()
52
53 def _sample_random(
54 self,
55 processed_logits: torch.Tensor,
56 expanded_idx_mapping: torch.Tensor,
57 idx_mapping_np: np.ndarray,
58 pos: torch.Tensor,
59 top_k: torch.Tensor | None,
60 top_p: torch.Tensor | None,
61 use_fused_sampler: bool,
62 ) -> tuple[torch.Tensor, torch.Tensor]:
63 enabled = self.watermarking.np[idx_mapping_np] & (
64 self.sampling_states.temperature.np[idx_mapping_np] != 0
65 )
66 if not np.any(enabled):
67 return super()._sample_random(
68 processed_logits,
69 expanded_idx_mapping,
70 idx_mapping_np,
71 pos,
72 top_k,
73 top_p,
74 use_fused_sampler,
75 )
76
77 processed_logits = apply_top_k_top_p(processed_logits, top_k, top_p)
78 contexts = self._get_contexts(expanded_idx_mapping)
79 repeated_contexts = None
80 if self.deduplicate_contexts != "none":
81 repeated_contexts = self._get_repeated_contexts(
82 expanded_idx_mapping, contexts
83 )
84
85 temperatures = self.sampling_states.temperature.gpu[expanded_idx_mapping]
86 needs_mixed_sampling = repeated_contexts is not None or not np.all(enabled)
87 skip_mask = None
88 if needs_mixed_sampling:
89 watermarking = self.watermarking.gpu[expanded_idx_mapping] & (
90 temperatures != 0
91 )
92 if repeated_contexts is not None:
93 watermarking &= ~repeated_contexts
94 skip_mask = ~watermarking
95
96 random_sampler = RandomSampler(
97 expanded_idx_mapping=expanded_idx_mapping,
98 temperatures=self.sampling_states.temperature.gpu,
99 seeds=self.sampling_states.seeds.gpu,
100 positions=pos,
101 use_fp64=self.use_fp64_gumbel,
102 )
103 output = self.watermarker.sample(
104 processed_logits,
105 contexts,
106 random_sampler,
107 skip_mask=skip_mask,
108 )
109 sampled = output.token_ids
110 output_logits = output.logits
111 sampled = torch.where(
112 temperatures == 0,
113 processed_logits.argmax(dim=-1),
114 sampled,
115 )
116 return sampled, output_logits
117
118 def _get_repeated_contexts(
119 self,
120 expanded_idx_mapping: torch.Tensor,
121 contexts: torch.Tensor,
122 expanded_local_pos: torch.Tensor | None = None,
123 ) -> torch.Tensor:
124 repeated = repeated_context_mask(
125 self.req_states.all_token_ids.gpu,
126 expanded_idx_mapping,
127 self.req_states.prompt_len.gpu,
128 self.req_states.total_len.gpu,
129 contexts,
130 self.deduplicate_contexts_max_history,
131 include_prompt=self.deduplicate_contexts == "all",
132 skip_partial_context=(
133 self.deduplicate_contexts == "all" and expanded_local_pos is None
134 ),
135 history_offsets=expanded_local_pos,
136 local_positions=expanded_local_pos,
137 num_speculative_steps=(
138 self.num_speculative_tokens if expanded_local_pos is not None else 0
139 ),
140 )
141 return repeated
142
143 def _get_contexts(
144 self,
145 expanded_idx_mapping: torch.Tensor,
146 expanded_local_pos: torch.Tensor | None = None,
147 draft_sampled: torch.Tensor | None = None,
148 ) -> torch.Tensor:
149 """Build contexts for flattened verification rows.
150
151 With committed tokens ``[10, 11]``, drafts ``[20, 21]``, and width 2,
152 local positions ``[0, 1, 2]`` use contexts ``[10, 11]``, ``[11, 20]``,
153 and ``[20, 21]``. The first verification row precedes the first draft.
154 """
155 context_width = self.watermarker.context_width
156 req_indices = expanded_idx_mapping.to(torch.int64)
157 valid_reqs = req_indices >= 0
158 safe_req_indices = req_indices.clamp_min(0)
159 total_lens = self.req_states.total_len.gpu[safe_req_indices].to(torch.int64)
160 prompt_lens = self.req_states.prompt_len.gpu[safe_req_indices].to(torch.int64)
161 if expanded_local_pos is None:
162 expanded_local_pos = torch.zeros_like(req_indices)
163 else:
164 expanded_local_pos = expanded_local_pos.to(torch.int64)
165 offsets = torch.arange(
166 -context_width, 0, dtype=torch.int64, device=req_indices.device
167 )
168 positions = (
169 total_lens.unsqueeze(-1) + expanded_local_pos.unsqueeze(-1) + offsets
170 )
171 committed = positions < total_lens.unsqueeze(-1)
172 valid_committed = (
173 valid_reqs.unsqueeze(-1)
174 & committed
175 & (positions >= prompt_lens.unsqueeze(-1))
176 )
177 committed_contexts = self.req_states.all_token_ids.gpu[
178 safe_req_indices.unsqueeze(-1),
179 positions.clamp(0, self.req_states.all_token_ids.gpu.shape[1] - 1),
180 ]
181 contexts = torch.where(valid_committed, committed_contexts, -1)
182 if draft_sampled is None:
183 return contexts
184
185 row_indices = torch.arange(
186 len(req_indices), dtype=torch.int64, device=req_indices.device
187 )
188 draft_offsets = positions - total_lens.unsqueeze(-1)
189 draft_indices = (
190 row_indices.unsqueeze(-1)
191 - expanded_local_pos.unsqueeze(-1)
192 + draft_offsets
193 + 1
194 )
195 valid_drafts = (
196 valid_reqs.unsqueeze(-1)
197 & ~committed
198 & (draft_offsets < expanded_local_pos.unsqueeze(-1))
199 )
200 draft_contexts = draft_sampled[draft_indices.clamp(0, len(draft_sampled) - 1)]
201 return torch.where(valid_drafts, draft_contexts, contexts)