DEV Community

Cover image for The Backdoor That Survived “Forget Me”: Can LLMs Audit Machine Unlearning?
BlueFlameKS
BlueFlameKS

Posted on

The Backdoor That Survived “Forget Me”: Can LLMs Audit Machine Unlearning?

Kaggle Benchmarking Challenge Submission

Both LLMs Found the Unlearning Bug. My Grader Nearly Got It Wrong.

This is a submission for the Kaggle Benchmarking Challenge

What I Benchmarked

The script claimed it could make a model forget a user's data. It even calculated a loss on the data that should be forgotten. Then it multiplied that loss by zero. That was the starting point for Unlearning Security Audit, a two-case pilot built with Kaggle's kaggle_benchmarks library. I wanted to see whether LLMs could audit and repair a misleading PyTorch unlearning routine. Machine unlearning means trying to remove the influence of particular training examples from a model. It matters when someone requests deletion of their data or when training data contains a malicious pattern. A backdoor is one such pattern: the model behaves normally on clean inputs but gives an attacker-chosen answer when a particular trigger appears.

My question was simple: could an LLM tell the difference between code that claims to forget and code that actually applies a forgetting update?

I gave each model two buggy cases:

Case What the code does Why it is a problem
Masked forget loss Replaces forget-set labels with zero, then multiplies the forget loss by 0.0. The forget term contributes no gradient. Training continues on retained data without an update aimed at reversing the unwanted learning.
Zeroed targets Removes the loss mask but still replaces forget-set labels with zero. This trains the model toward class zero. Changing labels is not the same as removing the examples' influence.

Each model had to explain the bug, rewrite the code using original labels and a gradient-ascent baseline, and suggest how to test whether the backdoor survived. I suggested gradient ascent in the prompt, so the benchmark tests auditing and implementing a specified repair rather than inventing an unlearning method. Eight automated checks per case covered Python syntax, the loss calculation, original labels, optimizer steps, diagnosis, evaluation planning, and limitations.

Models Tested

I ran the same cases against:

  • Gemini 2.5 Pro — google/gemini-2.5-pro
  • Gemini 2.5 Flash — google/gemini-2.5-flash

I chose them to compare answers and response times within the same model family, using the same prompts and checks. The export contains five responses because Pro's masked-loss case was run twice. I selected the latest response for each model and case, giving four responses, with two cases per model. The earlier response remains in the raw export and also passed the original checks.

Findings

1. Both models caught the conceptual problem

I expected that one model might tidy up the code while missing why it failed to unlearn. That did not happen in these responses. Both models identified the problem and used the original forget-set labels in the requested repair. All four selected responses passed the mathematical objective check:

retain_loss = F.cross_entropy(model(x_r), y_r)
forget_loss = F.cross_entropy(model(x_f), y_f)
objective = retain_loss - alpha * forget_loss
Enter fullscreen mode Exit fullscreen mode

A normal optimizer minimizes this objective. The positive retain term encourages good performance on retained examples. The negative forget term encourages increased loss on the examples selected for forgetting. alpha controls the balance. This is an ascent-based baseline, not a guarantee that a user's influence or a backdoor has been removed.

2. The biggest surprise was a mistake in my grader

The first scores seemed to show a clear gap: Pro passed every check on both cases, while Flash passed every check on only one case.

Model Original cases passing all checks After manual review of flagged checks Mean response time
Gemini 2.5 Pro 2/2 2/2 38.34 seconds
Gemini 2.5 Flash 1/2 2/2 22.05 seconds

When I read Flash's failed answer, it had explained the masked-loss bug:

By multiplying forget_loss by 0.0, the script effectively removes any contribution of the forget data's gradients to the overall loss calculation.

My text check looked for wording such as “zero” or “mask” and missed the numeric 0.0 explanation.

Flash also said the method:

cannot mathematically guarantee complete and irreversible erasure

The limitation check still failed it because it did not use the expected words “approximate” or “heuristic.”

I treated these as two false negatives in the grader. I preserved the original flags and documented the manual correction against the saved answer.

Eight original automated checks across the four selected responses
The two flagged checks belong to Flash's masked-loss answer. Manual review found that the response addressed both points.

After reviewing those flags, both models met the rubric on both cases. The apparent score gap came from brittle language checks. This small test does not establish that the models are equally capable overall.

3. Increasing forget loss did not remove the backdoor in the toy experiment

I also ran a separate synthetic logistic-classifier experiment. This was not a run of either LLM's generated PyTorch repair.
I measured clean accuracy and trigger attack success: how often the model gave the attacker's target answer on triggered test inputs.

Method Clean accuracy Trigger attack success
Poisoned checkpoint 100% 100%
Masked forget loss 100% 100%
Retain descent + forget ascent, 80 steps 100% 100%
Retraining on retained data only 100% 0%

Separate synthetic experiment showing clean accuracy, trigger attack success, and forget loss

After 80 ascent-based repair steps, forget loss rose from 0.0545 to 0.1649, but the trigger still worked on 100% of the triggered test inputs. Retraining on retained data only removed the trigger behavior in this simple setup while preserving clean accuracy. The lesson was that a loss moving in the intended direction is not enough evidence of successful unlearning. The security behavior needs its own test.

Flash returned sooner in these calls

Flash's selected calls took 23.35 and 20.75 seconds. Pro's took 43.83 and 32.84 seconds.

Response times for the four selected calls.

Flash was faster in both paired cases and met the same rubric after manual review. With only one selected response per case, I would not treat this as a reliable latency ranking.

What I would measure next

I would add more buggy scripts, repeat the calls, and have explanations reviewed without showing the model names. I would also publish improved language checks as a new grader version, keeping the original results visible. Next, I would execute generated repairs in an isolated environment and measure clean accuracy, trigger attack success, and performance against retraining. I started by asking whether the models could audit an unlearning script. I ended up learning that the evaluator needed an audit too.

My Benchmark

Technical basis: Kaggle Benchmarks SDK and Ascent Fails to Forget.

AI tools assisted with implementation, analysis, figures, and writing. Results come from saved runs; manual corrections and response selection are disclosed above.

Top comments (0)