Overview
Minimal framework for ML modeling, supporting advanced dataset operations and streamlined training workflows.
Install
The trainlib package can be installed from PyPI:
pip install trainlib
Development
- Initialize/synchronize the project with
uv sync, creating a virtual environment with base package dependencies. - Depending on needs, install the development dependencies with
uv sync --extra dev.
Testing
- To run the unit tests, make sure to first have the test dependencies
installed with
uv sync --extra test, then runmake test. - For notebook testing, run
make install-kernelto make the environment available as a Jupyter kernel (to be selected when running notebooks).
Documentation
- Install the documentation dependencies with
uv sync --extra doc. - Run
make docs-build(optionally preceded bymake docs-clean), and serve locally withmake docs-serve.
Development remarks
-
Across
Trainer/Estimator/Dataset, I've considered aParamSpec-based typing scheme to better orchestrate alignment in theTrainer.train()loop, e.g., so we can statically check whether a dataset appears to be fulfilling the argument requirements for the estimator'sforward()/metrics()methods. Something likeclass Estimator[**P](nn.Module): def forward( self, input: Tensor, *args: P.args, **kwargs: P.kwargs, ) -> Tensor: ... class Trainer[**P]: def __init__( self, estimator: Estimator[P], ... ): ...
might be how we begin threading signatures. But ensuring dataset items can match
Pis challenging. You can consider a "packed" object where we obfuscate passing data throughP-signatures:class PackedItem[**P]: def __init__(self, *args: P.args, **kwargs: P.kwargs) -> None: self._args = args self._kwargs = kwargs def apply[R](self, func: Callable[P, R]) -> R: return func(*self._args, **self._kwargs) class BatchedDataset[U, R, I, **P](Dataset): @abstractmethod def _process_item_data( self, item_data: I, item_index: int, ) -> PackedItem[P]: ... def __iter__(self) -> Iterator[PackedItem[P]]: ...
Meaningfully shaping those signatures is what remains, but you can't really do this, not with typical type expression flexibility. For instance, if I'm trying to appropriately type my base
TupleDataset:class SequenceDataset[I, **P](HomogenousDataset[int, I, I, P]): ... class TupleDataset[I](SequenceDataset[tuple[I, ...], "?"]): ...
Here there's no way for me to shape a
ParamSpecto indicate arbitrarily many arguments of a fixed type (Iin this case) to allow me to unpack my item tuples into an appropriatePackedItem(sinceParamSpecisn't exactly controllable in that way; it's meant make a generic signature expressible as a type variable, not to express particular shapes of the types in that signature).Until this (among other issues) becomes clearer, I'm setting up around a simpler
TypedDicttype variable. We won't have particularly strong static checks for item alignment insideTrainer, but this seems about as good as I can get around the current infrastructure.
Typing planning
There are currently two package-wide typing strategies geared toward future Python version bumps and type system changes:
-
Using
Unpackwith a type variable with aTypedDictupper bound, rather than with aTypedDictdirectly. This is commented on extensively intrainlib/estimator.py, but the short version is we want our key objects to remain generic in their method signatures when they are expected to operate on session-specific structured batch data. For instance, you can imagine anEstimator'sforwardmethod accepting a typed batch likedef forward(self, batch: BatchKwargs): ... # or def forward(self, **kwargs: Unpack[Batchwargs]): ...
These are both currently supported patterns. Problems arise, however, when we want an inherited class to specifically operate on an inherited type of
BatchKwargsclass BatchKwargs(TypedDict): inputs: Tensor class ExtraBatchKwargs(BatchKwargs): inputs: Tensor labels: Tensor class A(Estimator): def forward(self, batch: BatchKwargs): ... # want to require Estimator subclass handles both inputs and labels class B(A): def forward(self, batch: ExtraBatchKwargs): ...
Note how
ExtraBatchKwargsdoesn't useNotRequiredfor the newlabelsattribute; the intention here is to actually require thatBshould handle more attributes thanA. That breaks LSP, so this doesn't work. You'd need to either break out theEstimatorhierarchies, or decide thatBcould have inputs typed asBatchKwargsand possibly implement some extra handling to check if batches of typeExtraBatchKwargsare passed in (note that passing in a batch of typeExtraBatchKwargstoA.forwardis perfectly fine) and supply labels. Or... you make the class generic in the batch type, and during inheritance you can establish increasingly tight upper bounds on the batch type to accomplish precisely the desired effect. That is, you can formally require subclasses handle new batch attributes without breaking LSP. This could look like:class A[Kw: BatchKwargs](Estimator): def forward(self, batch: Kw): ... class B[Kw: ExtraBatchKwargs](A[Kw]): def forward(self, batch: Kw): ...
This formally allows
Bto inherit fromAwhile depending on new, non-optional attributes in its batch type; you can achieve that with refined type upper bound in a generic subtype. So this pattern works now, but what doesn't work is further unpackingKwin this pattern into kwargs. That is,class A[Kw: BatchKwargs](Estimator): def forward(self, **kwargs: Unpack[Kw]): ... class B[Kw: ExtraBatchKwargs](A[Kw]): def forward(self, **kwargs: Unpack[Kw]): ...
As sound as this is (and it is sound, described in PEP 827),
Unpackisn't supported for type variables; it has to be used directly withTypedDictreferences. I have the latter in place across the package anticipating eventual support for this pattern, but things are subject to change. I may simply toggle back to the currently supported pattern provided it's very similar, and if/when PEP 827 is integrated, the change will be as simply as adding back theUnpackstatements and keyword expansions. -
At the time of writing,
trainlibattempts to remain compatible with Python 3.12. This means the convenient lazy evaluation of type annotations can't be fully embraced across the package, but we use__future__where possible to make the transition easy when eventually bumping to 3.14+ (since the imports can just be removed at that time). For instance, class definitions that need to reference the class type itself shouldn't use string annotations but instead direct type references:# not class RecordIndex: @overload def __getitem__(self, key: slice) -> "RecordIndex": ... # but from __future__ import annotations class RecordIndex: @overload def __getitem__(self, key: slice) -> RecordIndex: ...
Metadata
Release files for trainlib 0.6.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| trainlib-0.6.1.tar.gz | 125.1 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| trainlib-0.6.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 259.5 kB
Release files / trainlib-0.6.1.tar.gz
| Download URL | trainlib-0.6.1.tar.gz |
|---|---|
| Size | 125.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0a1d10d71edbbf86d92724429b76d51983605f407ede16bb24bbf3696b224d44
|
|
BLAKE2b-256 checksum How to use checksums |
78424a7b30e086bc63805672f9d8de9bea4e41359a234225c8096186c1c20852
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Arch Linux","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / trainlib-0.6.1-py3-none-any.whl
| Download URL | trainlib-0.6.1-py3-none-any.whl |
|---|---|
| Size | 134.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7f6358e4407fbf86b2ca2d98d964f8abc2736927c16c8b553d3c97502cb6242a
|
|
BLAKE2b-256 checksum How to use checksums |
b26de8f262e3d7bf2d1beff3faed488ad7b3a0e1ea950cc17fc691db31f6e4ed
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Arch Linux","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Security for trainlib 0.6.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.