Short project exploring how a Gemma-2-2B model finetuned for calculator tool calls behaves internally, and an explainable and friendly fine-tuning method of Transcoders to match with the fine-tuned model to do mechanistic interpretability studies. The repo contains code to generate tool-calling data, finetune the model and SAEs, and run behavioral / mechanistic evaluations.
This project centers around two core questions:
To address these questions, this project studies a Gemma-2-2B causal language model finetuned to use a calculator tool via <tool_call>calculator(expr)</tool_call> generations. On top of an existing Gemma-Scope SAE (“Transcoder”), we train small delta transcoders and then intervene on the model’s MLP activations at specific layers. We systematically compare three conditions—raw model, base SAE only (no_delta), and base+delta (full / scaled alphas)—across in-distribution tool prompts, non-tool “copy” prompts, and out-of-distribution (OOD) tool prompts. The code reproduces the dataset generation, model finetuning, Experiment 1 (SAE validation) and Experiments 2A/2B (behavioral and mechanistic probing).
From the repo root:
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
This will install PyTorch, Hugging Face Transformers/PEFT, a local modified copy of transformer_lens, and plotting / data utilities.
data/
models/gemma_2b_toolformer_merged_v4/
damaoo/gemma2b-toolformersrc/
exp1/: SAE / Transcoder validation experiments.exp2/: Tool-calling behavioral & mechanistic experiments (2A/2B).gen_dataset*/: Dataset generation scripts.model_finetune/: Gemma Toolformer LoRA training / merge / validation.transcoder_finetune/: Delta Transcoder finetuning & validation.transformer_lens/: Local modified copy of the TransformerLens library.All commands assume you are in the repo root and the venv is activated.
cd src/exp1
# Evaluate base+delta transcoders (example hyperparameters)
python exp1_eval_transcoders.py \
--val_file ../../data/transcoder_finetune/sae_validation.txt \
--percentile 0.50 \
--delta_root /path/to/delta_checkpoints_root \
--d_new 128
# Aggregate CSV / JSON summaries
python exp1_aggregate_results.py
Outputs go under src/exp1/exp1_outputs_* and src/exp1/aggregate/.
cd src/exp2
python exp2A_eval_no_delta.py \
--val_file ../../data/exp2_combined_dataset_ood.jsonl \
--percentile 0.50 \
--delta_root /path/to/delta_checkpoints_root \
--d_new 128 \
--layers 16,20,24
This:
raw (no SAE),no_delta (base SAE only),full (base + delta SAE),tool (in-distribution tool prompts),copy (non-tool numeric prompts),tool_ood (OOD tool prompts),exp2A_outputs/exp2A_metrics_*.{csv,json},exp2A_outputs/generations/*.jsonl,exp2A_outputs/graphs/*.png.cd src/exp2
python exp2B_delta_scaling.py \
--val_file ../../data/exp2_combined_dataset_ood.jsonl \
--percentile 0.50 \
--delta_root /path/to/delta_checkpoints_root \
--d_new 128 \
--layers 20,24 \
--delta_alphas 0.0,0.25,0.5,0.75,1.0
This runs recon = base + α·delta for multiple α and records how cross-entropy and tool metrics change as we turn the delta “up and down”. Outputs are stored in exp2B_outputs/.
For details on dataset generation and finetuning (model and Transcoder/DeltaTranscoder), see SETUP.md.
This section summarizes the main quantitative and qualitative findings.
Looking directly at the neurons inside a model is confusing because a single neuron might be responsible for both "French grammar" and "pictures of cats" at the same time. This is known as the "Superposition" phenomenon.
The Role of SAE/Transcoder: They act like a "dictionary" that can translate the chaotic neuron signals into millions of distinct and clear "features" (for example, one feature represents the "concept of addition", and another represents "code brackets").
Unique Advantages of Transcoder:
Why Propose Delta Transcoders?
When you fine-tune a model to learn to use a calculator, there are minor changes in the model's "brain".
My solution: Delta Transcoders
Advantages Summary (Good, Explainable, Friendly):
tool): prompts that explicitly request a calculation and are labeled to expect <tool_call>calculator(expr)</tool_call>.copy): prompts containing numbers but meant to be repeated or copied without using the tool.tool_ood): prompts requiring the calculator tool but with out-of-distribution phrasing or numeric regimes.Each subset uses 20 evaluation examples for fast, controlled analysis in exp2A/2B.
For each subset, layer, and condition, we record:
mean_ce: token-level cross-entropy (teacher forcing).tool_call_rate: fraction of examples where the generated output contains <tool_call>.valid_tool_format_rate: fraction with a well-formed <tool_call>...</tool_call> block.math_correct_rate: fraction where the predicted calculator(expr') evaluates to the same numeric value as the gold expr.Additionally, we track:
mean_abs_base_acts: mean |activation| per token in the base SAE subspace.mean_abs_delta_acts: mean |activation| per token in the delta subspace.have a small eval dataset, Half of them are prompts related to tool invocation; Half of them are general prompts (with numbers) that do not require tools.
For each prompt, record:
The cosine similarity between MLP_recon and MLP_orig of this layer (averaged over tokens)
l2_ratio = ||recon - orig|| / ||orig||
The entire model's CE loss
Expect to see:
Base - only: cos is significantly low and l2_ratio is relatively large. The tool invocation behavior has been significantly weakened Fused: cos was significantly higher and l2_ratio decreased; The tool invocation behavior is basically aligned with the Raw (with a very small ΔCE), indicating that a small number of delta features have patched the offset between the "original SAE and fine-tuned model".
I ran experiment 1 over 9 sets of transcoders with different d_new (numbers of added features) and pctl (original transcoder ranked by the percentile of the avg number of features activated).
Here I put up the visualization of mean cos and ΔCE, compared between using my fine-tuned transcoder and the origianl transcoder for d_new = 32 pctl = 0.5


Other d_new and pctl combinations follow similar patterns to this.
At the vast majority of layers, cos_full > cos_base; CE_full << CE_base: → This indicates that delta is indeed correcting the mismatch between the base and the fine-tuned model.
At a very few layers, cos_full is slightly lower but CE_full is still significantly better: → This suggests that in these layers, the delta features may be making a trade-off in the "overall MLP reconstruction", sometimes sacrificing a bit of the average cosine similarity to reduce the error in key tokens or key directions.
More on explaing the layer 24, 25 outlier:
How my transcoder is trained: Fixing base SAE, learn the delta features to make full_recon_norm = base(x_norm) + delta(x_norm) approach mlp_target_norm (the actual MLP output of the fine-tuned model), while keeping the activation of delta sparse.
@torch.no_grad()
def evaluate(model, dataloader, norm_factor, sparsity_lambda, device):
model.eval()
total_loss = 0.0
for mlp_input, mlp_target in dataloader:
mlp_input, mlp_target = mlp_input.to(device), mlp_target.to(device)
mlp_input_norm = mlp_input / norm_factor
mlp_target_norm = mlp_target / norm_factor
full_recon_norm, delta_acts = model(mlp_input_norm)
reconstruction_loss = F.mse_loss(full_recon_norm, mlp_target_norm)
sparsity_loss = delta_acts.abs().mean()
total_loss += (reconstruction_loss + sparsity_lambda * sparsity_loss).item()
return total_loss / len(dataloader)
For any d_new, from 32 → 64 → 128:
At the same time, mean_l2_full also decreases as d_new increases.
As the percentile increases, the effect becomes worse (p=0.75 is significantly worse than p=0.25/0.5). When comparing the three percentiles for a fixed d_new:
This indicates that choosing a higher percentile (0.75) version of the base SAE makes it harder for the fine-tuned model to adapt. Note that I cannot say how it is related to the level of interpretability when using attribution graph.


(See src/exp1/aggregate/exp1_combo_summary.{csv,json} for detailed metrics.)
for the sake of time, continued with only d_new 128 pctl 0.5
eval dataset:
in addition to tool_call prompts and generic prompts, include a new category OOD out of distribution prompts.
For example:
Instead of computing the answer, construct a calculator invocation that will evaluate 953+789 when executed.
Prepare a tool call for the calculator so that it can evaluate 553+487. Do not simplify the expression by hand.
These are prompts that should trigger a tool call, but have not appeared in the training dataset.
For a certain layer L, if the delta subspace of the SAE in this layer is turned off and only the base subspace is retained, how much impact will it have on the "tool invocation task" (T_train/T_ood) and the "copy task" (G_copy), respectively?
ΔCE_T_NO_DELTA = CE_T_train(NO-DELTA) - CE_T_train(RAW)
ΔCE_G_NO_DELTA = CE_G_copy(NO-DELTA) - CE_G_copy(RAW)
focus is on the degree of deterioration of NO-DELTA relative to RAW.
Count separately on T_train and T_ood:
tool_call_rate: Whether the output contains <tool_call>; valid_tool_format_rate: Whether the output forms a complete <tool_call>calculator(...) </tool_call> mode; math_correct_rate: Parse calculator(...) For inner expressions, check if the result is correct.
For each layer L, compare:
tool_call_rate_T(RAW) vs NO-DELTA vs FULL
mean|acts_base| and mean|acts_delta| (averaged by token), calculate the average for T_train / G_copy / T_ood respectively. Intuitive explanation: If the mean|delta| on layer L is significantly higher on T_train than on G_copy: → The delta subspace is more inclined towards tool invocation tasks .





no_delta significantly increases mean_ce across all 3 subsets.tool and tool_ood, tool_call_rate and math_correct_rate often collapse toward 0 under no_delta, meaning the model largely stops calling the calculator or produces incorrect tool usage.copy, no_delta keeps tool_call_rate near 0 at mid/high layers, but at some early layers (e.g., layer 0) can cause degenerate behavior (e.g., always calling the tool), illustrating that naive SAE replacement can be catastrophic.full = base + delta recovers almost perfect tool_call_rate ≈ 1.0, valid_tool_format_rate ≈ 1.0, and math_correct_rate close to raw on the tool subset.no_delta show much lower tool metrics, demonstrating that delta features are necessary to maintain good tool behavior in the finetuned model.tool_ood, raw already has lower valid_tool_format_rate and math_correct_rate than in-distribution.full improves over no_delta but still underperforms raw, indicating limited generalization of the finetuned tool behavior to OOD phrasing.full condition, both mean_abs_delta_acts and mean_abs_base_acts activate more drastic at layers 20-25.Experiment 2B studies recon = base + α·delta with α ∈ {0.0, 0.25, 0.5, 0.75, 1.0} at a few critical layers (e.g., 20, 24):



tool subset:
tool_call_rate and math_correct_rate generally increasetool_ood subset:
copy subset:
tool_call_rate remains near 0 at mid/high layers, showing that delta does not indiscriminately cause tool hallucinations on non-tool prompts.base + delta, it provides a relatively localized and controllable way to reintroduce tool behavior.data/, src/gen_dataset*/).src/model_finetune/).src/transcoder_finetune/).src/exp1/, src/exp2/).Python
99.6%
Short project exploring how a Gemma-2-2B model finetuned for calculator tool calls behaves internally, and an explainable and friendly fine-tuning method of Transcoders to match with the fine-tuned model to do mechanistic interpretability studies. The repo contains code to generate tool-calling data, finetune the model and SAEs, and run behavioral / mechanistic evaluations.
This project centers around two core questions:
To address these questions, this project studies a Gemma-2-2B causal language model finetuned to use a calculator tool via <tool_call>calculator(expr)</tool_call> generations. On top of an existing Gemma-Scope SAE (“Transcoder”), we train small delta transcoders and then intervene on the model’s MLP activations at specific layers. We systematically compare three conditions—raw model, base SAE only (no_delta), and base+delta (full / scaled alphas)—across in-distribution tool prompts, non-tool “copy” prompts, and out-of-distribution (OOD) tool prompts. The code reproduces the dataset generation, model finetuning, Experiment 1 (SAE validation) and Experiments 2A/2B (behavioral and mechanistic probing).
From the repo root:
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
This will install PyTorch, Hugging Face Transformers/PEFT, a local modified copy of transformer_lens, and plotting / data utilities.
data/
models/gemma_2b_toolformer_merged_v4/
damaoo/gemma2b-toolformersrc/
exp1/: SAE / Transcoder validation experiments.exp2/: Tool-calling behavioral & mechanistic experiments (2A/2B).gen_dataset*/: Dataset generation scripts.model_finetune/: Gemma Toolformer LoRA training / merge / validation.transcoder_finetune/: Delta Transcoder finetuning & validation.transformer_lens/: Local modified copy of the TransformerLens library.All commands assume you are in the repo root and the venv is activated.
cd src/exp1
# Evaluate base+delta transcoders (example hyperparameters)
python exp1_eval_transcoders.py \
--val_file ../../data/transcoder_finetune/sae_validation.txt \
--percentile 0.50 \
--delta_root /path/to/delta_checkpoints_root \
--d_new 128
# Aggregate CSV / JSON summaries
python exp1_aggregate_results.py
Outputs go under src/exp1/exp1_outputs_* and src/exp1/aggregate/.
cd src/exp2
python exp2A_eval_no_delta.py \
--val_file ../../data/exp2_combined_dataset_ood.jsonl \
--percentile 0.50 \
--delta_root /path/to/delta_checkpoints_root \
--d_new 128 \
--layers 16,20,24
This:
raw (no SAE),no_delta (base SAE only),full (base + delta SAE),tool (in-distribution tool prompts),copy (non-tool numeric prompts),tool_ood (OOD tool prompts),exp2A_outputs/exp2A_metrics_*.{csv,json},exp2A_outputs/generations/*.jsonl,exp2A_outputs/graphs/*.png.cd src/exp2
python exp2B_delta_scaling.py \
--val_file ../../data/exp2_combined_dataset_ood.jsonl \
--percentile 0.50 \
--delta_root /path/to/delta_checkpoints_root \
--d_new 128 \
--layers 20,24 \
--delta_alphas 0.0,0.25,0.5,0.75,1.0
This runs recon = base + α·delta for multiple α and records how cross-entropy and tool metrics change as we turn the delta “up and down”. Outputs are stored in exp2B_outputs/.
For details on dataset generation and finetuning (model and Transcoder/DeltaTranscoder), see SETUP.md.
This section summarizes the main quantitative and qualitative findings.
Looking directly at the neurons inside a model is confusing because a single neuron might be responsible for both "French grammar" and "pictures of cats" at the same time. This is known as the "Superposition" phenomenon.
The Role of SAE/Transcoder: They act like a "dictionary" that can translate the chaotic neuron signals into millions of distinct and clear "features" (for example, one feature represents the "concept of addition", and another represents "code brackets").
Unique Advantages of Transcoder:
Why Propose Delta Transcoders?
When you fine-tune a model to learn to use a calculator, there are minor changes in the model's "brain".
My solution: Delta Transcoders
Advantages Summary (Good, Explainable, Friendly):
tool): prompts that explicitly request a calculation and are labeled to expect <tool_call>calculator(expr)</tool_call>.copy): prompts containing numbers but meant to be repeated or copied without using the tool.tool_ood): prompts requiring the calculator tool but with out-of-distribution phrasing or numeric regimes.Each subset uses 20 evaluation examples for fast, controlled analysis in exp2A/2B.
For each subset, layer, and condition, we record:
mean_ce: token-level cross-entropy (teacher forcing).tool_call_rate: fraction of examples where the generated output contains <tool_call>.valid_tool_format_rate: fraction with a well-formed <tool_call>...</tool_call> block.math_correct_rate: fraction where the predicted calculator(expr') evaluates to the same numeric value as the gold expr.Additionally, we track:
mean_abs_base_acts: mean |activation| per token in the base SAE subspace.mean_abs_delta_acts: mean |activation| per token in the delta subspace.have a small eval dataset, Half of them are prompts related to tool invocation; Half of them are general prompts (with numbers) that do not require tools.
For each prompt, record:
The cosine similarity between MLP_recon and MLP_orig of this layer (averaged over tokens)
l2_ratio = ||recon - orig|| / ||orig||
The entire model's CE loss
Expect to see:
Base - only: cos is significantly low and l2_ratio is relatively large. The tool invocation behavior has been significantly weakened Fused: cos was significantly higher and l2_ratio decreased; The tool invocation behavior is basically aligned with the Raw (with a very small ΔCE), indicating that a small number of delta features have patched the offset between the "original SAE and fine-tuned model".
I ran experiment 1 over 9 sets of transcoders with different d_new (numbers of added features) and pctl (original transcoder ranked by the percentile of the avg number of features activated).
Here I put up the visualization of mean cos and ΔCE, compared between using my fine-tuned transcoder and the origianl transcoder for d_new = 32 pctl = 0.5


Other d_new and pctl combinations follow similar patterns to this.
At the vast majority of layers, cos_full > cos_base; CE_full << CE_base: → This indicates that delta is indeed correcting the mismatch between the base and the fine-tuned model.
At a very few layers, cos_full is slightly lower but CE_full is still significantly better: → This suggests that in these layers, the delta features may be making a trade-off in the "overall MLP reconstruction", sometimes sacrificing a bit of the average cosine similarity to reduce the error in key tokens or key directions.
More on explaing the layer 24, 25 outlier:
How my transcoder is trained: Fixing base SAE, learn the delta features to make full_recon_norm = base(x_norm) + delta(x_norm) approach mlp_target_norm (the actual MLP output of the fine-tuned model), while keeping the activation of delta sparse.
@torch.no_grad()
def evaluate(model, dataloader, norm_factor, sparsity_lambda, device):
model.eval()
total_loss = 0.0
for mlp_input, mlp_target in dataloader:
mlp_input, mlp_target = mlp_input.to(device), mlp_target.to(device)
mlp_input_norm = mlp_input / norm_factor
mlp_target_norm = mlp_target / norm_factor
full_recon_norm, delta_acts = model(mlp_input_norm)
reconstruction_loss = F.mse_loss(full_recon_norm, mlp_target_norm)
sparsity_loss = delta_acts.abs().mean()
total_loss += (reconstruction_loss + sparsity_lambda * sparsity_loss).item()
return total_loss / len(dataloader)
For any d_new, from 32 → 64 → 128:
At the same time, mean_l2_full also decreases as d_new increases.
As the percentile increases, the effect becomes worse (p=0.75 is significantly worse than p=0.25/0.5). When comparing the three percentiles for a fixed d_new:
This indicates that choosing a higher percentile (0.75) version of the base SAE makes it harder for the fine-tuned model to adapt. Note that I cannot say how it is related to the level of interpretability when using attribution graph.


(See src/exp1/aggregate/exp1_combo_summary.{csv,json} for detailed metrics.)
for the sake of time, continued with only d_new 128 pctl 0.5
eval dataset:
in addition to tool_call prompts and generic prompts, include a new category OOD out of distribution prompts.
For example:
Instead of computing the answer, construct a calculator invocation that will evaluate 953+789 when executed.
Prepare a tool call for the calculator so that it can evaluate 553+487. Do not simplify the expression by hand.
These are prompts that should trigger a tool call, but have not appeared in the training dataset.
For a certain layer L, if the delta subspace of the SAE in this layer is turned off and only the base subspace is retained, how much impact will it have on the "tool invocation task" (T_train/T_ood) and the "copy task" (G_copy), respectively?
ΔCE_T_NO_DELTA = CE_T_train(NO-DELTA) - CE_T_train(RAW)
ΔCE_G_NO_DELTA = CE_G_copy(NO-DELTA) - CE_G_copy(RAW)
focus is on the degree of deterioration of NO-DELTA relative to RAW.
Count separately on T_train and T_ood:
tool_call_rate: Whether the output contains <tool_call>; valid_tool_format_rate: Whether the output forms a complete <tool_call>calculator(...) </tool_call> mode; math_correct_rate: Parse calculator(...) For inner expressions, check if the result is correct.
For each layer L, compare:
tool_call_rate_T(RAW) vs NO-DELTA vs FULL
mean|acts_base| and mean|acts_delta| (averaged by token), calculate the average for T_train / G_copy / T_ood respectively. Intuitive explanation: If the mean|delta| on layer L is significantly higher on T_train than on G_copy: → The delta subspace is more inclined towards tool invocation tasks .





no_delta significantly increases mean_ce across all 3 subsets.tool and tool_ood, tool_call_rate and math_correct_rate often collapse toward 0 under no_delta, meaning the model largely stops calling the calculator or produces incorrect tool usage.copy, no_delta keeps tool_call_rate near 0 at mid/high layers, but at some early layers (e.g., layer 0) can cause degenerate behavior (e.g., always calling the tool), illustrating that naive SAE replacement can be catastrophic.full = base + delta recovers almost perfect tool_call_rate ≈ 1.0, valid_tool_format_rate ≈ 1.0, and math_correct_rate close to raw on the tool subset.no_delta show much lower tool metrics, demonstrating that delta features are necessary to maintain good tool behavior in the finetuned model.tool_ood, raw already has lower valid_tool_format_rate and math_correct_rate than in-distribution.full improves over no_delta but still underperforms raw, indicating limited generalization of the finetuned tool behavior to OOD phrasing.full condition, both mean_abs_delta_acts and mean_abs_base_acts activate more drastic at layers 20-25.Experiment 2B studies recon = base + α·delta with α ∈ {0.0, 0.25, 0.5, 0.75, 1.0} at a few critical layers (e.g., 20, 24):



tool subset:
tool_call_rate and math_correct_rate generally increasetool_ood subset:
copy subset:
tool_call_rate remains near 0 at mid/high layers, showing that delta does not indiscriminately cause tool hallucinations on non-tool prompts.base + delta, it provides a relatively localized and controllable way to reintroduce tool behavior.data/, src/gen_dataset*/).src/model_finetune/).src/transcoder_finetune/).src/exp1/, src/exp2/).Python
99.6%