2929from transformers import AutoTokenizer
3030from act_replay import (load_hf_model , _LMWrap , _attach_one , _install_one ,
3131 map_gguf_to_hf , assistant_delimiters , batch_response_mask ,
32- reassign_targets , lr_warmup_cosine )
32+ reassign_targets , lr_warmup_cosine ,
33+ collect_target_hessians , gptq_reassign_targets )
3334from act_replay_student import select_targets
3435from gguf_state import open_ml8_gguf , list_ml8_names
3536from kl_loss import topk_teacher , kl_topk
@@ -58,7 +59,8 @@ def main():
5859 import argparse
5960 ap = argparse .ArgumentParser ()
6061 ap .add_argument ("--arms" , default = "frozen,mse,pv" ,
61- help = "comma list of arms: frozen,mse,pv (pv expands over --pv-fracs)" )
62+ help = "comma list of arms: frozen,mse,pv,gptq "
63+ "(pv expands over --pv-fracs; gptq = Axis B v2 full-H re-solve)" )
6264 ap .add_argument ("--pv-fracs" , default = "0.1" ,
6365 help = "comma list of pv trust-region fractions (one pv arm each)" )
6466 ap .add_argument ("--eval-interval" , type = int , default = 1 ,
@@ -164,7 +166,9 @@ def run_arm(label, reassign_mode, reassign_frac, eval_interval, max_steps):
164166 opt .param_groups [1 ]["lr" ] = base [1 ] * mult
165167 opt .step (); opt .zero_grad (); step += 1
166168 flips = 0
167- if reassign_mode != "none" and step % REASSIGN_INTERVAL == 0 :
169+ # mse/pv reassign in-loop; gptq (Axis B v2) is a single post-Axis-A
170+ # re-solve done after the loop, so it trains frozen here.
171+ if reassign_mode in ("mse" , "pv" ) and step % REASSIGN_INTERVAL == 0 :
168172 flips = reassign_targets (tlist , reassign_mode , frac = reassign_frac )
169173 # Eval cadence: reassign_interval is a multiple of eval_interval in
170174 # practice, so every reassign step also reports its post-flip KL.
@@ -176,6 +180,15 @@ def run_arm(label, reassign_mode, reassign_frac, eval_interval, max_steps):
176180 break
177181 sps = max_steps / (time .time () - t0 )
178182 print (f"[arm { label } ] start { k0 :.4f} -> final { kf :.4f} ({ sps :.3f} steps/s)" , flush = True )
183+ # Axis B rung A: ONE full-H GPTQ index re-solve against the now-tuned centroids.
184+ # Collect the rotated activation Hessian fresh for THIS arm's tuned state, then
185+ # re-solve indices (sequential, H^-1-compensated — cannot diverge like pv).
186+ if reassign_mode == "gptq" :
187+ print (f"[arm { label } ] collecting rotated Hessians ({ len (targets )} targets)..." , flush = True )
188+ H_by_name = collect_target_hessians (targets , train_w , model , dev )
189+ nflip = gptq_reassign_targets (targets , H_by_name , percdamp = 0.05 , act_order = True )
190+ kf = holdout_kl ()
191+ print (f"[arm { label } ] post-GPTQ-reassign KL { kf :.4f} ({ nflip } indices changed)" , flush = True )
179192 return k0 , kf
180193
181194 # Build arm specs from CLI: frozen/mse once, pv once per requested frac.
@@ -188,6 +201,8 @@ def run_arm(label, reassign_mode, reassign_frac, eval_interval, max_steps):
188201 elif a == "pv" :
189202 for fr in PV_FRACS :
190203 arm_specs .append ((f"pv_f{ fr :g} " , "pv" , fr ))
204+ elif a == "gptq" :
205+ arm_specs .append (("gptq" , "gptq" , 0.0 ))
191206 results = {}
192207 for label , mode , fr in arm_specs :
193208 results [label ] = run_arm (label , mode , fr , EVAL_INTERVAL , STEPS )
0 commit comments