Skip to content

Add K-FORGE: Fisher-guided initialization for preference-based unlearning - #212

Merged
molereddy merged 3 commits into
locuslab:mainfrom
etomoscow:add-kforge
Nov 11, 2026
Merged

molereddy merged 3 commits into
locuslab:mainfrom
etomoscow:add-kforge

Conversation

@etomoscow

@etomoscow etomoscow commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Title: Add K-FORGE: Fisher-guided initialization for preference-based unlearning


Summary

Adds K-FORGE (Kronecker-Fisher One-shot Rank-r Generalized Erasure), accepted at Findings of ICJNLP-AACL 2026, as a community method. K-FORGE is a training-free one-shot edit: it estimates empirical Kronecker-Fisher factors on forget and retain calibration batches, simultaneously diagonalizes the pairs, applies a retain-aware Wiener filter with a retain budget (lambda_tradeoff), truncates to rank r, and folds the negative edit into the checkpoint. The edited checkpoint can be used directly or as an initialization for NPO / SimNPO (their objectives remain unchanged). Follows up on #211.

Changes

  • src/trainer/unlearn/kforge.py — new KFORGE trainer: calibration hooks (valid causal-LM loss positions, eval mode), factor damping (fixed / scale-relative adaptive), wiener_v2 (theorem-backed retain-aware edit) and legacy_v1 (original heuristic, kept for ablation) edit variants, optional spectrum logging, fp32 checkpoint serialization so small edits survive saving. Single-process calibration (multi-process raises a clear error).
  • configs/trainer/KFORGE.yaml — defaults follow the paper configuration (wiener_v2, lambda_tradeoff=0.01, max_target_modules=4 as a memory guard); the per-run paper settings (rank=2, strength=0.45) are passed inline in community/methods/KFORGE/run.sh, following the convention of the other community methods.
  • community/methods/KFORGE/ — README with method description, hyperparameters, results table, known boundaries; run.sh reproducing the two-stage flow (edit → downstream NPO; DOWNSTREAM_TRAINER=SimNPO supported).
  • tests/test_kforge.py — CPU regression tests for the edit math: closed-form diagonal full-rank case (the paper's Theorem 1 reduction), λ=0 full-rank invariant (Δ = −α·W), causal-LM token masking, scale-relative factor damping.
  • docs/links.md, README.md — method link and counts.

Results

Primary endpoint: Forget Q/A Probability (lower is better). TOFU forget10, Llama-3.2-1B-Instruct, n = 3 seeds, mean ± std; full tables in community/methods/KFORGE/README.md.

Steps Method Init Utility ↑ Forget Prob. ↓
50 NPO scratch 0.509 ± 0.004 0.093 ± 0.001
K-FORGE (0.450) 0.565 ± 0.003 0.047 ± 0.003
100 NPO scratch 0.569 ± 0.009 0.043 ± 0.002
K-FORGE (0.450) 0.587 ± 0.002 0.029 ± 0.002
50 SimNPO scratch 0.577 ± 0.006 0.662 ± 0.003
K-FORGE (0.450) 0.572 ± 0.007 0.542 ± 0.002
100 SimNPO scratch 0.582 ± 0.002 0.518 ± 0.010
K-FORGE (0.450) 0.579 ± 0.003 0.413 ± 0.008

Compute-matched comparison with the edit's setup fully charged (FP32, evaluation excluded from timing): K-FORGE-initialized NPO reaches 0.046 forget probability vs 0.059 for scratch at lower total wall time (277.2 s vs 285.5 s) and fewer FLOPs (2.95e15 vs 3.78e15), while improving utility (0.577 vs 0.549).

Known boundaries (documented in the README and paper): direct relearning recovers the forgotten data; MUSE gains vary by metric; Qwen2.5-1.5B is unchanged at reported precision.

Testing

  • python tests/test_kforge.py — CPU regression tests pass (edit math in float64).
  • ruff check / ruff format --check clean on changed src files (repo defaults).
  • End-to-end run.sh on a GPU host (Llama-3.2-1B-Instruct, TOFU forget10) — run output available on request; evaluator harness unchanged from upstream experiment=unlearn/tofu/default.

Checklist (per docs/contributing.md)

  • Trainer implemented and registered (KFORGE)
  • Exact commands documented (community/methods/KFORGE/run.sh)
  • Method folder with README (hyperparameters, model-selection guidance, results)
  • Leaderboard entry + HuggingFace unlearned checkpoints — will add after initial review (happy to discuss format)
  • Paper link in docs/links.md

Reference


Note: the upstream community/leaderboard.md entry and HF checkpoints are intentionally left for the review phase — happy to run whichever benchmark configuration the maintainers prefer for the leaderboard.

…ning

K-FORGE (Kronecker-Fisher One-shot Rank-r Generalized Erasure) is a
training-free one-shot edit that estimates empirical Kronecker-Fisher
factors on forget and retain calibration batches, simultaneously
diagonalizes the pairs, applies a retain-aware Wiener filter with a
retain budget, truncates to rank r, and folds the edit into the
checkpoint. The edited checkpoint can be used directly or as an
initialization for NPO / SimNPO; with setup time and FLOPs charged,
initialized NPO lowers Forget Q/A Probability by 22.3% and improves
utility on TOFU forget10 (Llama-3.2-1B-Instruct, n=3) while using less
total time and compute than training from the pretrained checkpoint.

- register KFORGE trainer (single-process calibration, wiener_v2 and
  legacy_v1 edit variants)
- trainer + experiment configs (paper configuration by default)
- community method folder with README (results, hyperparameters) and a
  two-stage run.sh (edit -> downstream NPO/SimNPO)
- CPU regression tests for the edit math (closed-form diagonal case,
  lambda=0 full-rank invariant, token masking, factor damping)

Accepted at Findings of ICJNLP-AACL 2026. Paper code and experiments:
https://github.com/etomoscow/kron_unlearn

@molereddy molereddy left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for contributing!!

Added some nit comments and couple questions

  1. can you confirm this was run e2e successfully? because eval uses 16 bit - for flash attn - and the train code converts model to 32 bit.
  2. stage 2 loads model with bf16 so saving in 32 bit doesn't seem to make a difference. can you re-check how this works?

Comment thread docs/links.md Resolved
Comment thread community/methods/KFORGE/README.md Resolved
Comment thread src/trainer/unlearn/kforge.py Resolved
- fix conference spelling: IJCNLP-AACL (docs/links.md, method README)
- default the trainer class to edit_variant=wiener_v2 /
  lambda_tradeoff=0.01 to match the trainer config and the paper
- drop configs/experiment/unlearn/tofu/kforge.yaml; the community
  run.sh now carries the paper settings as inline Hydra overrides,
  following the convention of the other community methods
- spell out the edit_weight_dtype trade-off in the method README
@etomoscow

etomoscow commented Nov 8, 2026 •

Copy link
Copy Markdown
Contributor Author

Fixed the nits in 1c750d0:

  • ICJNLP → IJCNLP in docs/links.md and the method README.
  • The trainer class now defaults to edit_variant=wiener_v2 / lambda_tradeoff=0.01, matching the trainer config.
  • Removed configs/experiment/unlearn/tofu/kforge.yaml; run.sh now passes the paper settings as inline Hydra overrides, like the other community methods do.

About the dtype questions:

  • edit_weight_dtype=float32 (the default) upcasts the model to fp32 before folding the edit, so the update runs as w_fp32 + δ and the artifact is saved in fp32. With preserve, the model stays bf16 and add_() runs in bf16. bf16 has an 8-bit mantissa, so a small α·δ can round to zero relative to w: the edit is lost at apply time, before any save.
  • For a bf16-loading stage 2 you're right that one rounding happens either way (at load instead of at save). The default exists for the other consumers: the paper's standalone FP32 evaluations and the quantization audits read the saved artifact, and you can still override downstream dtype (torch_dtype) without the rounding baked into the artifact.
  • e2e: confirmed. We produced the paper results end-to-end with this two-stage flow through the OpenUnlearning harness: stage 1 saves the fp32 artifact, stage 2 and the TOFU evaluation load it with the standard bf16/flash-attn model config (the cast happens at load), and the reported metrics come from that flow.

Happy to flip the default to preserve if you'd rather keep bf16 artifacts; otherwise I'll leave float32 as is.

@molereddy

Copy link
Copy Markdown
Collaborator

thank you for the clarification and fixes, please fix merge conflicts and we should be good to merge!

Both sides added entries to the method list, links table, and trainer
registry; kept both. Bumped the README method count to 16+.
@etomoscow

etomoscow commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor Author

Merge conflicts resolved in 0bb804e

@molereddy
molereddy merged commit 838cc3c into locuslab:main Nov 11, 2026
1 check passed

This branch was successfully deployed

1 active deployment
tests — 0bb804e0 Deployed Nov 11, 2026 by etomoscow via tests (3.11, ubuntu-latest) #159
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants