Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
e028f2a
chore: gitignore .worktrees/
dirmeier Jul 9, 2026
2b09b11
refactor: move model zoo (DiT, MLP, edm, rfm, samplers) from blaxbird…
dirmeier Jul 10, 2026
3d01169
refactor(samplers): add SAMPLERS registry and get_sampler_fn
dirmeier Jul 10, 2026
26d2cf1
refactor(parameterizations): add EDMParameterization.denoise
dirmeier Jul 10, 2026
a64c8d1
feat(types): add ObjectiveFns contract, wire edm/rfm to return it
dirmeier Jul 10, 2026
c3930b2
refactor(trainer): derive model from optimizer.model, drop redundant …
dirmeier Jul 10, 2026
3dd82a8
refactor(checkpointer): restore_best_fn/restore_last_fn take/return o…
dirmeier Jul 10, 2026
c0b71eb
feat(checkpointer): make checkpoint criterion_key and best_mode confi…
dirmeier Jul 10, 2026
2322ebb
fix(dit): actually use context for class conditioning instead of sile…
dirmeier Jul 10, 2026
745c521
feat(examples): class-conditional CIFAR-10 generation, exercising the…
dirmeier Jul 10, 2026
bf6f553
feat(unet): add ResBlock, AttentionBlock, Downsample, Upsample buildi…
dirmeier Jul 10, 2026
6f5bc66
feat(unet): assemble full UNet with down/mid/up path and Small/Base/L…
dirmeier Jul 10, 2026
54fd6ab
feat(_common): export UNet and presets from _common
dirmeier Jul 10, 2026
f02a77b
test(unet): verify gradients flow through the full network
dirmeier Jul 10, 2026
1b3f418
feat(hooks): add get_ema_hook, first entry in the new blaxbird.hooks …
dirmeier Jul 10, 2026
95e3556
feat: export get_ema_hook from blaxbird
dirmeier Jul 10, 2026
9af82e7
test(hooks): verify get_ema_hook integrates with train_fn's hooks=
dirmeier Jul 10, 2026
efd2170
feat(examples): add LR warmup/cosine-decay schedule and gradient clip…
dirmeier Jul 10, 2026
dc5559e
feat(trainer): derive sharding from nnx.with_partitioning via mesh, r…
dirmeier Jul 11, 2026
b1cb0f9
feat(examples): add FSDP+TP 2D mesh sharding demo
dirmeier Jul 11, 2026
fdec12b
docs: update README for mesh-based sharding and optimizer-only train(…
dirmeier Jul 11, 2026
76ba103
feat(llm_reference): add shared RoPE, masking, RMSNorm, TP-sharded GQ…
dirmeier Jul 11, 2026
58f6662
feat(llm_reference): assemble GemmaDense with interleaved local/globa…
dirmeier Jul 11, 2026
75140a4
feat(llm_reference): add MLAAttention with decoupled RoPE
dirmeier Jul 11, 2026
e6f7bd6
feat(llm_reference): assemble DeepSeekMLA, verify 2D fsdp+tp sharding
dirmeier Jul 11, 2026
26ef1f3
feat(llm_reference): add SparseMoEFFN with capacity-based dispatch/co…
dirmeier Jul 11, 2026
5c1356f
feat(llm_reference): assemble MixtralSMoE, verify 3D fsdp+tp+expert s…
dirmeier Jul 11, 2026
22319d2
feat(llm_reference): add causal_lm training objective, shared across …
dirmeier Jul 11, 2026
a084e99
feat(llm_reference): add shared autoregressive generation loop
dirmeier Jul 11, 2026
5451aa0
docs(llm_reference): add multi-mesh smoke-test script and README
dirmeier Jul 12, 2026
f6b6bbd
feat(llm): add Gemma4/DeepSeek4/Qwen3Next reference architectures
dirmeier Jul 14, 2026
f9e9c1d
chore: reorg examples, drop unused code, make wandb optional
dirmeier Jul 14, 2026
50ca3a0
feat: add lms
dirmeier Jul 14, 2026
792a6e3
chore: bump version to 0.2.0
dirmeier Jul 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
- precommit
strategy:
matrix:
python-version: [ 3.12 ]
python-version: [ 3.13 ]
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
Expand All @@ -32,18 +32,18 @@ jobs:
version: "latest"
- name: Install dependencies
run: |
uv sync --dev
uv sync --all-groups --all-extras
- name: Run lints
run: |
make lints
uv run ruff check blaxbird examples

tests:
runs-on: ubuntu-latest
needs:
- lints
strategy:
matrix:
python-version: [ 3.12 ]
python-version: [ 3.13 ]
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
Expand All @@ -55,11 +55,11 @@ jobs:
version: "latest"
- name: Install dependencies
run: |
uv sync --dev
uv sync --all-extras
- name: Run tests
run: |
make tests
uv run pytest
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v5
uses: codecov/codecov-action@v3
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
2 changes: 1 addition & 1 deletion .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.11]
python-version: [ 3.13 ]
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -155,3 +155,6 @@ cython_debug/
.vscode/

.DS_Store

# git worktrees for isolated feature work
.worktrees/
7 changes: 7 additions & 0 deletions .gitlint
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
[general]
ignore=body-is-missing
contrib=contrib-title-conventional-commits

[title-min-length]
min-length=10

[title-max-length]
line-length=72

[body-max-line-length]
line-length=72

[title-must-not-contain-word]
words=wip,todo
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ repos:
hooks:
- id: mypy
args: ["--ignore-missing-imports"]
files: "(blaxbird)"
files: "(blaxbird|examples)"

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.3.0
Expand Down
2 changes: 1 addition & 1 deletion .python-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.11.9
3.12.10
11 changes: 0 additions & 11 deletions Makefile

This file was deleted.

165 changes: 124 additions & 41 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,15 @@

> A high-level API to build and train NNX models

## About

`Blaxbird` [blækbɜːd] is a high-level API to easily build NNX models and train them on CPU or GPU.
`Blaxbird` [blæksbɜːd] is a high-level API to easily build NNX models and train them on CPU or GPU.

Using `blaxbird` one can
- concisely define models and loss functions without the usual JAX/Flax verbosity,
- easily define checkpointers that save the best and most current network weights,
- distribute data and model weights over multiple processes or GPUs,
- define hooks that are periodically called during training.

In addition, `blaxbird` offers high-quality implementations of common neural network modules and algorithms, such as:

- MLPs, DiTs, UNets,
- Flow Matching and Denoising Score Matching (EDM schedules) models with Euler and Heun samplers,
- Consistency Distillation/Matching models.

## Example
## Quickstart

To use `blaxbird`, one only needs to define a model, a loss function, and train and validation step functions:
```python
Expand Down Expand Up @@ -62,20 +54,37 @@ train = train_fn(
eval_every_n_steps=10,
n_eval_batches=10
)
train(jr.key(2), model, optimizer, train_itr, val_itr)
train(jr.key(2), optimizer, train_itr, val_itr)
```

See the entire self-contained example in [examples/mnist_classification](examples/mnist_classification).
## Examples

Full self-contained examples (flow matching, small LMs training, ...), can be found in [examples](examples/).

## Usage
## Installation

To install the package from PyPI, call:

```bash
pip install blaxbird
```

To install the latest GitHub <RELEASE>, just call the following on the command line:

```bash
pip install git+https://github.com/dirmeier/blaxbird@<RELEASE>
```

## API

`train_fn` is a higher order function with the following signature:

```python
def train_fn(
*,
fns: tuple[Callable, Callable],
shardings: Optional[tuple[jax.NamedSharding, jax.NamedSharding]] = None,
mesh: jax.sharding.Mesh | None = None,
data_partition_spec: jax.sharding.PartitionSpec = jax.sharding.PartitionSpec(),
n_steps: int,
eval_every_n_steps: int,
n_eval_batches: int,
Expand All @@ -85,6 +94,8 @@ def train_fn(
...
```

The returned `train` callable has signature `train(rng_key, optimizer, train_itr, val_itr) -> None` -- it derives the model from `optimizer.model`, so there is no separate `model` argument.

We briefly explain the more ambiguous argument types below.

### `fns`
Expand All @@ -109,26 +120,33 @@ The loss function that is called by both computes a *scalar* loss value. B
While `train_step` returns has to return the loss and gradients, `val_step` only needs
to return the loss.

### `shardings`
### `mesh` and `data_partition_spec`

To specify how data and model weights are distributed over devices and processes,
`blaxbird` uses JAX' [sharding](https://docs.jax.dev/en/latest/notebooks/Distributed_arrays_and_automatic_parallelization.html) functionality.

`shardings` is again specified by a tuple, one for the model sharding, the other for the data sharding.
An example is shown below, where we only distributed the data over `num_devices` devices.
You can, if you don't want to distribute anything, just set the argument to `None` or not specify it.
`mesh` is a `jax.sharding.Mesh` describing your device topology. Per-parameter sharding is derived from each parameter's own `nnx.with_partitioning`
annotation (see the [flax.nnx docs](https://flax.readthedocs.io/en/latest/)) via `nnx.get_named_sharding` -- parameters without such an annotation default to fully replicated, so a mesh with no annotated parameters gives plain data parallelism. `data_partition_spec` controls how each training/eval batch is sharded across `mesh` (defaults to `PartitionSpec()`, fully replicated). You can, if you don't want to distribute anything, just leave `mesh` as `None` or not specify it.

An example is shown below, sharding only the data over `num_devices` devices
(the model has no `with_partitioning` annotations, so it stays fully
replicated):

```python
def get_sharding():
def get_mesh():
num_devices = jax.local_device_count()
mesh = jax.sharding.Mesh(
return jax.sharding.Mesh(
mesh_utils.create_device_mesh((num_devices,)), ("data",)
)
model_sharding = jax.NamedSharding(mesh, jax.sharding.PartitionSpec())
data_sharding = jax.NamedSharding(mesh, jax.sharding.PartitionSpec("data"))
return model_sharding, data_sharding

mesh = get_mesh()
```

Pass `mesh=mesh, data_partition_spec=jax.sharding.PartitionSpec("data")` to
`train_fn`. For real FSDP/tensor-parallel sharding, annotate your model's
layers with `nnx.with_partitioning` -- see
[examples/fsdp_tp_demo](examples/fsdp_tp_demo) for a worked 2D-mesh example.

### `hooks`

`hooks` is a list of callables which are periodically called during training.
Expand Down Expand Up @@ -205,6 +223,53 @@ hook_save, *_ = get_default_checkpointer(
)
```

#### An EMA `hook`

We provide a hook for tracking an exponential moving average (EMA) of a
model's weights, constructed via `get_ema_hook`.

The signature is:

```python
def get_ema_hook(
model: nnx.Module, decay: float = 0.999
) -> tuple[Callable, Callable]
```

Its arguments are:
- `model`: the model from which the EMA state is initialized.
- `decay`: the EMA decay rate.

It returns a tuple `(hook_fn, get_ema_model_fn)`:
- `hook_fn(step, *, model, **kwargs) -> None`: updates the tracked EMA
weights every training step.
- `get_ema_model_fn(model: nnx.Module) -> nnx.Module`: returns a new,
independent `nnx.Module` with the same structure as `model` but with the
tracked EMA parameter values.

For instance, you would construct and use the EMA hook like this:

```python
from blaxbird import get_ema_hook

ema_hook, get_ema_model = get_ema_hook(model, decay=0.999)

train = train_fn(
fns=(train_step, val_step),
n_steps=n_steps,
eval_every_n_steps=eval_every_n_steps,
n_eval_batches=n_eval_batches,
hooks=[ema_hook],
)
train(jr.key(1), optimizer, train_itr, val_itr)

ema_model = get_ema_model(optimizer.model)
```

Note: EMA state is not integrated with `get_default_checkpointer` --
saving and restoring EMA state alongside model checkpoints is not
covered here.

### Restoring a run

You can also use `get_default_checkpointer` to restart the run where you left off.
Expand All @@ -225,44 +290,62 @@ You can then do either of:
model = CNN(rngs=nnx.rnglib.Rngs(jr.key(1)))
optimizer = nnx.Optimizer(model, optax.adam(1e-4))

model, optimizer = restore_best(model, optimizer)
model, optimizer = restore_last(model, optimizer)
optimizer = restore_best(optimizer)
optimizer = restore_last(optimizer)
```

`restore_best`/`restore_last` take and return `optimizer` only -- the wrapped
model (`optimizer.model`) and `opt_state` are both updated in place on the
same optimizer instance, since `nnx.Optimizer` already owns the model it
wraps.

### Doing training

After having defined train functions, hooks and shardings, you can train your model like this:
After having defined train functions, hooks and a mesh, you can train your model like this:

```python
train = train_fn(
fns=(train_step, val_step),
n_steps=n_steps,
eval_every_n_steps=eval_every_n_steps,
n_eval_batches=n_eval_batches,
shardings=(model_sharding, data_sharding),
mesh=mesh,
data_partition_spec=jax.sharding.PartitionSpec("data"),
hooks=hooks,
log_to_wandb=False,
)
train(jr.key(1), model, optimizer, train_itr, val_itr)
train(jr.key(1), optimizer, train_itr, val_itr)
```

Self-contained examples that also explain how the data loaders should look like can be found
in [examples](examples).
## Contributing

## Installation
Contributions in the form of pull requests are more than welcome. A good way to
start is to check out issues labelled
[good first issue](https://github.com/dirmeier/surjectors/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22).

To install the package from PyPI, call:
In order to contribute:

```bash
pip install blaxbird
```
1) Clone `surjectors` and install `uv` from [here](https://docs.astral.sh/uv/getting-started/installation/).
2) Install all dependencies using `uv sync --all-groups`.
3) Install the Git hooks:

To install the latest GitHub <RELEASE>, just call the following on the command line:
```bash
uv run pre-commit install -t pre-commit -t commit-msg
```
4) Create a new branch locally, e.g. `git checkout -b feature/my-new-feature`.
5) Implement your contribution and ideally a test case.
6) Check your work (see below).
7) Submit a PR 🙂.

```bash
pip install git+https://github.com/dirmeier/blaxbird@<RELEASE>
```
### Development commands

## Author
The project uses `uv` for everything:

Simon Dirmeier <a href="mailto:simd@mailbox.org">simd@mailbox.org</a>
```bash
uv sync --all-groups
uv run pytest
uv run ruff format blaxbird examples
uv run ruff check --fix blaxbird examples
uv run mypy blaxbird examples
uv run pre-commit run --all-files
```
5 changes: 3 additions & 2 deletions blaxbird/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
"""blaxbird: A high-level API for building and training Flax NNX models."""

__version__ = "0.1.1"
__version__ = "0.2.0"

from blaxbird._src.checkpointer import get_default_checkpointer
from blaxbird._src.hooks import get_ema_hook
from blaxbird._src.trainer import train_fn

__all__ = ["get_default_checkpointer", "train_fn"]
__all__ = ["get_default_checkpointer", "get_ema_hook", "train_fn"]
Loading
Loading