2424from transformers .models .gpt_neox .modeling_gpt_neox import GPTNeoXForCausalLM
2525from transformers .models .gptj .modeling_gptj import GPTJForCausalLM
2626
27+ from pyrit .executor .promptgen .gcg .attack .base .progressive_schedule import (
28+ ProgressiveScheduleController ,
29+ ProgressiveScheduleState ,
30+ ScheduleTransitionAction ,
31+ )
2732from pyrit .executor .promptgen .gcg .experiments .log import (
2833 log_gpu_memory ,
2934 log_loss ,
@@ -80,24 +85,6 @@ class OptimizationRunState:
8085 stop_reason : StopReason | None = None
8186
8287
83- @dataclass
84- class ProgressiveScheduleState :
85- """
86- Typed schedule state for ``ProgressiveMultiPromptAttack``.
87-
88- Tracks how many goals and workers have been admitted so far, together with
89- the shared step counter and the loss carried between progressive rounds.
90- Exposed as ``ProgressiveMultiPromptAttack.last_schedule_state`` after a call
91- to ``ProgressiveMultiPromptAttack.run``.
92- """
93-
94- goals_admitted : int
95- workers_admitted : int
96- steps_completed : int = 0
97- loss : float = float ("inf" )
98- stop_inner_on_success : bool = False
99-
100-
10188@dataclass
10289class RngBundle :
10390 """Per-run RNG state bundle for deterministic GCG execution."""
@@ -1507,23 +1494,25 @@ def run(
15071494 },
15081495 )
15091496
1510- schedule = ProgressiveScheduleState (
1511- goals_admitted = 1 if self .progressive_goals else len (self .goals ),
1512- workers_admitted = 1 if self .progressive_models else len (self .workers ),
1513- stop_inner_on_success = self .progressive_goals ,
1497+ controller = ProgressiveScheduleController (
1498+ total_goals = len (self .goals ),
1499+ total_workers = len (self .workers ),
1500+ progressive_goals = self .progressive_goals ,
1501+ progressive_models = self .progressive_models ,
1502+ n_steps = n_steps ,
1503+ control_weight = control_weight ,
1504+ incr_control = incr_control ,
1505+ stop_on_success = stop_on_success ,
1506+ verbose = verbose ,
15141507 )
1515- # Whether ``schedule.loss`` currently reflects an inner run's measured
1516- # loss, as opposed to the ``inf`` sentinel written when a new round is
1517- # admitted. Tracked explicitly so a legitimately non-finite inner loss
1518- # (non-finite model loss or numeric overflow) is not mistaken for an
1519- # unupdated sentinel value.
1520- loss_is_measured = False
1521-
1522- while schedule .steps_completed < n_steps :
1508+
1509+ while not controller .is_complete :
1510+ controller .before_inner_run ()
1511+ schedule = controller .state
15231512 attack = self .managers ["MPA" ](
1524- self .goals [: schedule . goals_admitted ],
1525- self .targets [: schedule . goals_admitted ],
1526- self .workers [: schedule . workers_admitted ],
1513+ self .goals [: controller . active_goal_count ],
1514+ self .targets [: controller . active_goal_count ],
1515+ self .workers [: controller . active_worker_count ],
15271516 self .control ,
15281517 self .test_prefixes ,
15291518 self .logfile ,
@@ -1532,17 +1521,15 @@ def run(
15321521 self .test_targets ,
15331522 self .test_workers ,
15341523 )
1535- if schedule .goals_admitted == len (self .goals ) and schedule .workers_admitted == len (self .workers ):
1536- schedule .stop_inner_on_success = False
15371524 attack ._rng_bundle = rng_bundle
15381525 inner_result : tuple [str , float , int ] = attack .run (
1539- n_steps = n_steps - schedule . steps_completed ,
1526+ n_steps = controller . remaining_steps ,
15401527 batch_size = batch_size ,
15411528 topk = topk ,
15421529 temp = temp ,
15431530 allow_non_ascii = allow_non_ascii ,
15441531 target_weight = target_weight ,
1545- control_weight = control_weight ,
1532+ control_weight = controller . control_weight ,
15461533 anneal = anneal ,
15471534 anneal_from = schedule .steps_completed ,
15481535 prev_loss = schedule .loss ,
@@ -1553,28 +1540,13 @@ def run(
15531540 random_seed = random_seed ,
15541541 )
15551542 control , inner_loss , inner_steps = inner_result
1556- schedule .loss = inner_loss
1557- loss_is_measured = True
1558-
1559- schedule .steps_completed += inner_steps
15601543 self .control = control
15611544
1562- # Once the step budget is spent, stop preparing further rounds:
1563- # admissions and their sentinel resets would strand ``inf`` on
1564- # ``schedule.loss`` for a run that legitimately ends right here.
1565- prepare_next_round = schedule .steps_completed < n_steps
1566-
1567- if schedule .goals_admitted < len (self .goals ):
1568- if prepare_next_round :
1569- schedule .goals_admitted += 1
1570- schedule .loss = np .inf
1571- loss_is_measured = False
1572- elif schedule .workers_admitted < len (self .workers ):
1573- if prepare_next_round :
1574- schedule .workers_admitted += 1
1575- schedule .loss = np .inf
1576- loss_is_measured = False
1577- elif schedule .workers_admitted == len (self .workers ) and stop_on_success :
1545+ action = controller .advance_after_inner_run (
1546+ inner_loss = inner_loss ,
1547+ inner_steps = inner_steps ,
1548+ )
1549+ if action == ScheduleTransitionAction .FINALIZE_AND_STOP :
15781550 self ._finalize_progressive_run (
15791551 attack = attack ,
15801552 step = schedule .steps_completed ,
@@ -1583,27 +1555,11 @@ def run(
15831555 verbose = verbose ,
15841556 )
15851557 break
1586- elif prepare_next_round and isinstance (control_weight , (int , float )) and incr_control :
1587- if control_weight <= 0.09 :
1588- control_weight += 0.01
1589- schedule .loss = np .inf
1590- loss_is_measured = False
1591- if verbose :
1592- logger .info (f"Control weight increased to { control_weight :.5} " )
1593- else :
1594- schedule .stop_inner_on_success = False
1595-
1596- # The inner run must have produced a measured loss whenever any
1597- # optimization happened; guards against silent carry-over regressions.
1598- # Whether the loss was measured is tracked explicitly (a completed
1599- # inner run may legitimately report a non-finite loss), never inferred
1600- # from the numeric value.
1601- if schedule .steps_completed > 0 :
1602- assert loss_is_measured , "schedule.loss was never updated by the inner run"
16031558
1604- self .last_schedule_state = schedule
1559+ controller .validate_post_run ()
1560+ self .last_schedule_state = controller .state
16051561
1606- return self .control , schedule .steps_completed
1562+ return self .control , controller . state .steps_completed
16071563
16081564
16091565class IndividualPromptAttack :
0 commit comments