1717 get_embedding_matrix ,
1818 get_embeddings ,
1919)
20+ from pyrit .auxiliary_attacks .gcg .default_implementations import (
21+ CrossEntropyLoss ,
22+ LengthPreservingFilter ,
23+ StandardGCGSampling ,
24+ )
25+ from pyrit .auxiliary_attacks .gcg .extension_protocols import CandidateFilter , LossFunction , SamplingStrategy
2026
2127logger = logging .getLogger (__name__ )
2228
@@ -125,6 +131,99 @@ def sample_control(
125131class GCGMultiPromptAttack (MultiPromptAttack ):
126132 """GCG-specific multi-prompt attack that implements the GCG optimization step."""
127133
134+ def __init__ (
135+ self ,
136+ goals : list [str ],
137+ targets : list [str ],
138+ workers : list [Any ],
139+ control_init : str = "! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! !" ,
140+ test_prefixes : list [str ] | None = None ,
141+ logfile : str | None = None ,
142+ managers : dict [str , Any ] | None = None ,
143+ test_goals : list [str ] | None = None ,
144+ test_targets : list [str ] | None = None ,
145+ test_workers : list [Any ] | None = None ,
146+ * ,
147+ sampling : SamplingStrategy | None = None ,
148+ loss : LossFunction | None = None ,
149+ candidate_filter : CandidateFilter | None = None ,
150+ ) -> None :
151+ super ().__init__ (
152+ goals ,
153+ targets ,
154+ workers ,
155+ control_init ,
156+ test_prefixes ,
157+ logfile ,
158+ managers ,
159+ test_goals ,
160+ test_targets ,
161+ test_workers ,
162+ )
163+ self ._sampling = sampling
164+ self ._loss = loss
165+ self ._candidate_filter = candidate_filter
166+
167+ def _resolve_sampling (self ) -> SamplingStrategy :
168+ sampling = getattr (self , "_sampling" , None )
169+ if sampling is not None :
170+ return sampling
171+ return StandardGCGSampling ()
172+
173+ def _resolve_loss (self , * , target_weight : float , control_weight : float ) -> LossFunction :
174+ loss = getattr (self , "_loss" , None )
175+ if loss is not None :
176+ return loss
177+ return CrossEntropyLoss (target_weight = target_weight , control_weight = control_weight )
178+
179+ def _resolve_candidate_filter (self , * , filter_cand : bool ) -> CandidateFilter :
180+ candidate_filter = getattr (self , "_candidate_filter" , None )
181+ if candidate_filter is not None :
182+ return candidate_filter
183+ return LengthPreservingFilter (filter = filter_cand )
184+
185+ def _sample_control_candidates (
186+ self ,
187+ * ,
188+ worker_index : int ,
189+ gradient : torch .Tensor ,
190+ batch_size : int ,
191+ topk : int ,
192+ temp : float ,
193+ allow_non_ascii : bool ,
194+ ) -> torch .Tensor :
195+ sampler = self ._resolve_sampling ()
196+ prompt_manager = self .prompts [worker_index ]
197+ return sampler .sample_candidates (
198+ gradient = gradient ,
199+ control_tokens = prompt_manager .control_toks ,
200+ batch_size = batch_size ,
201+ top_k = topk ,
202+ temperature = temp ,
203+ allow_non_ascii = allow_non_ascii ,
204+ non_ascii_tokens = prompt_manager .disallowed_toks ,
205+ )
206+
207+ def _filter_control_candidates (
208+ self ,
209+ * ,
210+ worker_index : int ,
211+ control_cand : torch .Tensor ,
212+ filter_cand : bool ,
213+ ) -> list [str ]:
214+ candidate_filter = self ._resolve_candidate_filter (filter_cand = filter_cand )
215+ return candidate_filter .filter_candidates (
216+ candidate_tokens = control_cand ,
217+ tokenizer = self .workers [worker_index ].tokenizer ,
218+ current_control = self .control_str ,
219+ )
220+
221+ def _get_control_length (self , * , control : str ) -> int | None :
222+ try :
223+ return len (self .workers [0 ].tokenizer (control ).input_ids [1 :])
224+ except (AttributeError , TypeError , ValueError ):
225+ return None
226+
128227 def step (
129228 self ,
130229 * ,
@@ -158,6 +257,7 @@ def step(
158257 """
159258 main_device = self .models [0 ].device
160259 control_cands = []
260+ loss_function = self ._resolve_loss (target_weight = target_weight , control_weight = control_weight )
161261
162262 for j , worker in enumerate (self .workers ):
163263 worker (self .prompts [j ], "grad" , worker .model )
@@ -171,20 +271,40 @@ def step(
171271 grad = torch .zeros_like (new_grad )
172272 if grad .shape != new_grad .shape :
173273 with torch .no_grad ():
174- control_cand = self .prompts [j - 1 ].sample_control (grad , batch_size , topk , temp , allow_non_ascii )
274+ control_cand = self ._sample_control_candidates (
275+ worker_index = j - 1 ,
276+ gradient = grad ,
277+ batch_size = batch_size ,
278+ topk = topk ,
279+ temp = temp ,
280+ allow_non_ascii = allow_non_ascii ,
281+ )
175282 control_cands .append (
176- self .get_filtered_cands (
177- j - 1 , control_cand , filter_cand = filter_cand , curr_control = self .control_str
283+ self ._filter_control_candidates (
284+ worker_index = j - 1 ,
285+ control_cand = control_cand ,
286+ filter_cand = filter_cand ,
178287 )
179288 )
180289 grad = new_grad
181290 else :
182291 grad += new_grad
183292
184293 with torch .no_grad ():
185- control_cand = self .prompts [j ].sample_control (grad , batch_size , topk , temp , allow_non_ascii )
294+ control_cand = self ._sample_control_candidates (
295+ worker_index = j ,
296+ gradient = grad ,
297+ batch_size = batch_size ,
298+ topk = topk ,
299+ temp = temp ,
300+ allow_non_ascii = allow_non_ascii ,
301+ )
186302 control_cands .append (
187- self .get_filtered_cands (j , control_cand , filter_cand = filter_cand , curr_control = self .control_str )
303+ self ._filter_control_candidates (
304+ worker_index = j ,
305+ control_cand = control_cand ,
306+ filter_cand = filter_cand ,
307+ )
188308 )
189309 del grad , control_cand
190310 gc .collect ()
@@ -205,14 +325,14 @@ def step(
205325 worker (self .prompts [k ][i ], "logits" , worker .model , cand , return_ids = True )
206326 logits , ids = zip (* [worker .results .get () for worker in self .workers ])
207327 loss [j * batch_size : (j + 1 ) * batch_size ] += sum (
208- target_weight * self .prompts [k ][i ].target_loss (logit , id ).mean (dim = - 1 ).to (main_device )
328+ loss_function .compute_loss (
329+ logits = logit ,
330+ token_ids = id ,
331+ target_slice = self .prompts [k ][i ]._target_slice ,
332+ control_slice = self .prompts [k ][i ]._control_slice ,
333+ ).to (main_device )
209334 for k , (logit , id ) in enumerate (zip (logits , ids ))
210335 )
211- if control_weight != 0 :
212- loss [j * batch_size : (j + 1 ) * batch_size ] += sum (
213- control_weight * self .prompts [k ][i ].control_loss (logit , id ).mean (dim = - 1 ).to (main_device )
214- for k , (logit , id ) in enumerate (zip (logits , ids ))
215- )
216336 del logits , ids
217337 gc .collect ()
218338
@@ -229,7 +349,9 @@ def step(
229349 del control_cands , loss
230350 gc .collect ()
231351
232- logger .info (f"Current length: { len (self .workers [0 ].tokenizer (next_control ).input_ids [1 :])} " )
352+ current_length = self ._get_control_length (control = next_control )
353+ if current_length is not None :
354+ logger .info (f"Current length: { current_length } " )
233355 logger .info (next_control )
234356
235357 return next_control , cand_loss .item () / len (self .prompts [0 ]) / len (self .workers )
0 commit comments