Skip to content

Fixing fine-tuning script issues#1068

Merged
alanprior merged 10 commits into
mainfrom
alan/fix-finetune-1064
Jun 29, 2026
Merged

Fixing fine-tuning script issues#1068
alanprior merged 10 commits into
mainfrom
alan/fix-finetune-1064

Conversation

@alanprior

@alanprior alanprior commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Fix #1064 — fine-tuning silently worse than the base model

What & why

Users reported that FinetunedTabPFN{Classifier,Regressor} made performance worse, not better. Root cause was two bugs that made fine-tuning look broken regardless of the actual training, plus example hyperparameters that no longer suit the current default model:

  1. Wrong model generation. The fine-tuner was hard-coded to TabPFN V2.5, while a default TabPFNClassifier()/TabPFNRegressor() (the natural baseline, and the examples' "Default"
    print) loads V3. So users were comparing a fine-tuned older model against a newer base — worse by construction.
  2. Degraded-weights fallback. With early stopping, if no epoch beat the default model on validation, the wrapper returned the last (degraded) epoch's weights instead of the original
    — i.e. it could hand back a model strictly worse than the base it started from.

After the fixes, fine-tuning is never silently worse than base, and the shipped examples now improve over base:

  • Higgs: ROC 0.8211 → 0.8279, log-loss 0.5157 → 0.5057
  • California: MSE 0.1388 → 0.1363

Issue

#1064

Motivation and Context


Public API Changes

  • No Public API changes

How Has This Been Tested?

The script now produces improved results, apart from some internal evaluation.

Checklist

  • The changes have been tested locally.
  • Documentation has been updated (if the public API or usage changes).
  • A changelog entry has been added (see changelog/README.md), or "no changelog needed" label requested.
  • The code follows the project's style guidelines.
  • I have considered the impact of these changes on the public API.

Two issues made `FinetunedTabPFN*` look worse than the base model:

1. Early stopping kept the last (degraded) epoch's weights when no epoch
   improved over the default model, because the best-model snapshot was only
   taken on an improvement. Seed it with the default weights so the original
   model is restored as a floor.

2. Fine-tuning was hardcoded to model version V2.5 while a default
   TabPFNClassifier/TabPFNRegressor now loads V3, so comparing them mixed
   model generations. Fine-tuning now defaults to the package default version
   (settings.tabpfn.model_version) and accepts an optional `model_version`.

Adds regression tests for both; existing finetuning tests are pinned to V2.5
since they mock the V2.5 architecture.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@alanprior alanprior requested a review from a team as a code owner June 23, 2026 20:09
@alanprior alanprior requested review from TuanaCelik and removed request for a team and TuanaCelik June 23, 2026 20:09

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request updates the fine-tuning wrappers to dynamically target the package default model version instead of a hardcoded older version, and adds support for an optional model_version override. It also ensures that if fine-tuning with early stopping does not yield any improvement over the default model, the original base weights are restored. The reviewer identified a potential synchronization issue in distributed data parallel (DDP) training where the default model state is only snapshot on the main process, which could lead to weight desynchronization across ranks if no epoch improves; they suggested seeding the snapshot on all ranks instead.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/tabpfn/finetuning/finetuned_base.py Outdated
alanprior and others added 7 commits June 23, 2026 23:12
Move it out of the _fit closure so it's a reusable helper that takes the
model explicitly, alongside the other module-level finetuning helpers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The finetuning tests mock the V2.5 architecture (`TabPFNV2p5`) for speed, so
they must run on V2.5 now that fine-tuning defaults to the package default
version (V3). Replace the scattered `model_version=ModelVersion.V2_5` literals
with a single documented `FINETUNE_TEST_MODEL_VERSION` constant in tests/utils.py,
and note the spots where the pin isn't strictly required.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Supersedes the V2.5-pin approach: instead of pinning the fine-tuned version to
V2.5 to keep the V2.5-architecture mock valid, repoint the mocks to `TabPFNV3`
and drop the pins, so the tests exercise the default version users actually get.
Remove the FINETUNE_TEST_MODEL_VERSION constant.

Also make the regressor checkpoint test deterministic by mocking
`_evaluate_model` with a strictly-improving (decreasing-mse) side effect, the
same way the classifier checkpoint tests do — it previously relied on the random
mocked forward happening to beat the default model.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…path

Parametrize the model_version-tracks-default test over FinetunedTabPFNClassifier
and FinetunedTabPFNRegressor (it checks shared FinetunedTabPFNBase behavior, so
it should run for both). The no-improvement weight-restoration test exercises
the same shared `_fit`; add a comment noting the regressor inherits the
identical path rather than duplicating the (slow, model-loading) test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The classifier example fine-tuned with lr=2e-5 and the default 10k
context, which on the large Higgs dataset improved validation but not the
held-out test set. Lower the LR to 1e-5 (also matching the regressor
example) and raise the fine-tuning context to 30k so each step sees more
of Higgs's 90k train rows — this makes fine-tuned beat the base model on
test, not just validation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Previously the default-weights seed was gated on is_main_process, so when no
epoch beat the default model, only rank 0 restored the base weights while other
ranks kept the last (degraded) epoch — a silent cross-rank desync. best_metric
and primary_metric are already broadcast and DDP keeps weights all-reduced, so
seeding on every rank makes all ranks restore the same base weights.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@alanprior alanprior changed the title Fix fine-tuning regressions surfaced by #1064 Fixing fine-tuning script issues Jun 24, 2026
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@psinger-prior psinger-prior left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

lgtm!

Comment thread src/tabpfn/finetuning/finetuned_classifier.py
@alanprior alanprior removed the request for review from anuragg1209 June 29, 2026 09:08
@alanprior alanprior added this pull request to the merge queue Jun 29, 2026
Merged via the queue into main with commit b52b42f Jun 29, 2026
15 checks passed
@oscarkey oscarkey deleted the alan/fix-finetune-1064 branch June 30, 2026 13:00
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.

finetune leads to worse performance

2 participants