Skip to content

Make the PyTorch model picklable (module-level gelu activation)#47

Merged
weihaokong merged 1 commit into
mainfrom
fix-pytorch-pickle-activation
Jul 6, 2026
Merged

Make the PyTorch model picklable (module-level gelu activation)#47
weihaokong merged 1 commit into
mainfrom
fix-pytorch-pickle-activation

Conversation

@weihaokong

Copy link
Copy Markdown
Collaborator

Problem

Pickling the PyTorch TabFM model fails:

AttributeError: Can't pickle local object 'get_activation.<locals>.<lambda>'

get_activation("gelu") returns a lambda, which is stored as self.act on the gelu MLP heads. The transformer FFNs use swiglu (F.silu, picklable), but every model carries at least one gelu MLP — the y-encoder and decoder (the regression path also adds a y-embedder) — so the model as a whole is unpicklable.

This matters because AutoGluon / TabArena save the fitted estimator (which holds the model) with stdlib pickle. TabArena currently works around it with a custom __getstate__/__setstate__ that drops and reloads the network (autogluon/tabarena#434); this fix lets that workaround be removed.

Repro on main:

import pickle
from tabfm.src.pytorch import model
m = model.TabFM(embed_dim=8, max_classes=3, col_num_blocks=1, col_nhead=2,
                col_num_inds=8, row_num_blocks=1, row_nhead=2, row_num_cls=2,
                icl_num_blocks=1, icl_nhead=2, ff_factor=2, feature_group_size=2,
                is_classifier=True)
pickle.dumps(m)  # AttributeError

Fix

Promote the gelu activation to a module-level _gelu_tanh function. Module-level functions pickle by reference; the computation is unchanged (F.gelu(x, approximate="tanh")), so weights and outputs are identical — this is purely a serialization fix.

Testing

Adds PyTorchModelPickleTest (torch-only): classifier and regressor model pickle round-trips, plus a forward-output equivalence check after unpickling. The regressor case exercises all three gelu MLP heads.

pytest tabfm/src/classifier_and_regressor_pytorch_test.py passes (5 tests).

`get_activation("gelu")` returned a lambda, stored as `MLP.act` on the
encode/decode heads, so pickling the model raised
`Can't pickle local object 'get_activation.<locals>.<lambda>'`.
AutoGluon / TabArena save the fitted estimator (which holds the model)
with stdlib pickle, so the model must be picklable. Promote the gelu
activation to a module-level `_gelu_tanh` (picklable by reference, and
numerically identical to the previous lambda).

Add pickle round-trip tests for the classifier and regressor models,
plus a forward-output equivalence check after unpickling.
@weihaokong
weihaokong merged commit 4d9081a into main Jul 6, 2026
9 checks passed
weihaokong pushed a commit that referenced this pull request Jul 6, 2026
The first predict memoizes nnx.jit-compiled step functions on the
estimator instance (_predict_step_compiled_with_cat /
_predict_step_compiled_no_cat in _batch_forward). Those closures cannot
be pickled, so saving a fitted TabFMClassifier/TabFMRegressor with
stdlib pickle crashes with "Can't pickle local object
'..._batch_forward.<locals>._predict_step_fn'" once predict has run.
That is the AutoGluon / TabArena save path (they pickle the fitted
estimator), same motivation as the PyTorch-side fix in #47.

Drop the memoized functions from __getstate__ on both estimators: they
are pure caches and are rebuilt lazily on the next predict. Restored
estimators produce identical predictions.
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.

2 participants