feat: add a PyTorch backend alongside Keras - #2
Merged
Conversation
PyTorch has no fit(), so TorchTrainer takes a step function that runs one round and returns its metrics. Everything around that - the Target, the round loop, the timeout, the Ctrl+C handling, the checkpointing and the resume behaviour - is shared with the Keras trainer, which is unchanged. The shared parts moved into an internal _base module. No public name changed and the existing 55 tests pass untouched. Backends are now imported lazily, so having only one of the two frameworks installed is enough: importing TorchTrainer never loads TensorFlow, and asking for a trainer whose framework is missing raises an ImportError naming the extra to install. TensorFlow stays a hard requirement for the 2.x line so existing installs keep working; it moves to the `tensorflow` extra in 3.0.0. Adds 39 tests (94 total), a CI job that installs each framework on its own, and a PyTorch MNIST example. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The test job segfaulted (exit 139) as soon as a torch test ran after the Keras ones. The traceback is entirely inside torch._dynamo, and the loaded extension modules include cuda.bindings.*: the default PyTorch wheel bundles CUDA libraries that clash with the ones TensorFlow loads once both are imported into the same process. The runners have no GPU, so the CPU wheel is the correct choice regardless, and it is a much smaller download. The backend-isolation jobs already passed - they only ever import one framework - but they now use the CPU build too, for consistency and speed. Also documents the hazard in the README, since a user installing both frameworks can hit it independently of this package. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds a PyTorch backend. The Keras API is untouched and no existing checkpoint needs converting.
The shape of it
PyTorch has no
fit, soTorchTrainertakes the training step from you and calls it once per round:Everything around the step — the
Target, the round loop, the timeout, theCtrl+Chandling, the checkpointing and the resume behaviour — is shared with the Keras trainer. One round is one call to your step function, so you choose the granularity at which the target and timeout are checked.There is no
compile(): the shadow copy holding the best weights is deep-copied in the constructor, so inference works straight away.Three decisions worth reviewing
Backends are now imported lazily.
import infinite_trainingno longer imports TensorFlow at all, and referencingTorchTrainernever imports it either. Having only one framework installed is enough. Asking for a trainer whose framework is missing raises anImportErrornaming the extra:TensorFlow stays a hard dependency for 2.x. Moving it to an extra now would break every existing
pip install infinite-training, which is not a minor-version change. So[tensorflow]and[torch]extras are both declared, TensorFlow also stays independencies, and the CHANGELOG and README say it moves to the extra in 3.0.0. A torch-only user can already opt out withpip install --no-deps infinite-trainingplusnumpy torch. If you would rather take the break now, this is the piece to change.Checkpoints are NumPy state dicts, not
torch.savearchives. This keeps the storage format consistent with the Keras side, lets a checkpoint written on a GPU resume on a CPU with no device map, and keeps the file readable without PyTorch installed. There is a test that theint64buffer inBatchNormround-trips with its dtype intact.Shared internals
The loop, the target and timeout bookkeeping, the value history and the checkpoint paths moved into an internal
_basemodule that both backends use.InfiniteTrainerlost about 70 lines and gained four small hooks; no public name changed, and the existing 55 tests pass without modification.Testing
collect_ignore.ruff checkandruff format --checkclean;uv buildplustwine checkpass on both artifacts.The isolation runs are the ones that matter for the lazy-import claim, so there is a new CI job that installs each framework on its own, asserts the other is genuinely absent, and runs the suite.
Coverage for the PyTorch path includes the stop conditions, best-weight tracking for both minimised and maximised targets, the metrics contract (missing key, non-mapping return, sequence values, tensors, NumPy scalars, empty sequences),
KeyboardInterruptstill checkpointing, resume from disk, parent-directory creation, and that inference runs in eval mode underno_gradand restores the previous training mode afterwards.One thing I got wrong while writing this
My first version of the multi-round test targeted a loss below 0.24 on the XOR-shaped fixture with no timeout. A single
Linear(2, 1)cannot separate XOR, so its MSE floor is exactly 0.25 — the test hung rather than failed. It now targets 0.26 with a 60 second backstop, and the reason is in a comment so nobody re-tightens it.Version
Bumped to 2.2.0. Additive feature, no breaking change.