thinc
Thinc is Explosion's functional neural-network library and the model layer beneath spaCy. Instead of subclassing modules, you compose typed Model objects with combinators such as chain, clone, concatenate, residual, and with_array. It includes NumPy and CuPy backends, optimizers, losses, schedules, a registry-driven configuration system, custom backprop callbacks, serialization, and wrappers intended to connect models from larger frameworks.
Thinc makes sense inside the Explosion ecosystem or for developers who specifically value its typed functional model composition. It is a specialist toolkit, not the calm default for a greenfield deep-learning platform.
Use it if
- You are extending spaCy or another Explosion stack component that already represents models with Thinc
- You need concise composition across ragged, padded, list, and array data shapes with runtime shape validation
- You want an explicit forward function and backprop callback for research on unusual model wiring
- You need model constructors and hyperparameters resolved from versioned config registries
- You want the default deep-learning choice for a new team; PyTorch, JAX, or TensorFlow has a much larger training, deployment, hardware, and model ecosystem
- You need maximum performance from native layers; Thinc's own model documentation says individual components may be slower than PyTorch or TensorFlow and recommends wrappers for expensive LSTM or transformer work
- You do not want to write gradient plumbing; custom forward functions must return a backprop callback, and the docs warn that custom combinators must pass gradients correctly
- You expect a batteries-included trainer; the docs say Thinc does not provide accuracy calculation utilities, and training loops, evaluation tallies, batching, loss gradients, and optimizer calls remain explicit
- You need settled distributed-training support or current first-class integrations for every framework; the docs call Ray support still under development, while PyPI extras still constrain TensorFlow below 2.6 and MXNet below 1.6
Setup reality
`pip install thinc` is not a tiny pure-Python install. Version 9.1.1 requires Python 3.9 or newer, NumPy 2, Blis, murmurhash, cymem, preshed, Pydantic, confection, catalogue, srsly, wasabi, and packaging. PyPI publishes wheels for common CPython and operating-system combinations; building from source needs a compiler for C extensions, and the README tells source builders to install the full requirements and disable build isolation. GPU support is optional and depends on selecting the CuPy package that matches the installed CUDA toolkit, a common source of resolver and binary errors. Framework extras are not a promise of modern versions: the published metadata includes old upper bounds for TensorFlow and MXNet, while PyTorch is less tightly constrained. Static model type checking also needs the documented mypy plugin configuration. The conceptual setup is just as important: layers do not infer all dimensions until `model.initialize(X=..., Y=...)` receives representative sample data. Prediction uses `predict`; training uses `begin_update`, sends the output gradient through the returned callback, and then calls `finish_update` with an optimizer. Configuration files use confection's INI-like syntax plus registered, versioned factory names, so importing the module that registers custom functions must happen before resolving a config. Finally, the repository's default branch is `v8.3.x` and its latest GitHub release is 8.3.13 even though PyPI's latest package is 9.1.1; pin the line used by spaCy or your application instead of assuming the highest number is compatible.
Patterns
Compose a small feed-forward modelcompose-feed-forward
from thinc.api import Relu, Softmax, chain
model = chain(
Relu(nO=64, dropout=0.2),
Relu(nO=32, dropout=0.1),
Softmax(nO=3),
)`chain` expresses sequential flow. Input widths can remain unset until initialization sees representative arrays.
Infer dimensions and run predictioninitialize-and-predict
import numpy as np
X = np.zeros((4, 20), dtype='float32')
Y = np.zeros((4, 3), dtype='float32')
model.initialize(X=X, Y=Y)
predictions = model.predict(X)Initialize with realistic input and output shapes before training or serialization. Type hints on layers enable additional runtime validation.
Backpropagate and update one batchtrain-one-batch
from thinc.api import Adam, CategoricalCrossentropy
optimizer = Adam(learn_rate=0.001)
loss_calc = CategoricalCrossentropy()
guesses, backprop = model.begin_update(X)
d_guesses, loss = loss_calc(guesses, truths)
backprop(d_guesses)
model.finish_update(optimizer)The loss helper returns an output gradient and a scalar loss. Pass the gradient to the callback before applying the optimizer.
Train over shuffled minibatchesiterate-minibatches
for X_batch, Y_batch in model.ops.multibatch(32, train_X, train_Y, shuffle=True):
guesses, backprop = model.begin_update(X_batch)
d_guesses, loss = loss_calc(guesses, Y_batch)
backprop(d_guesses)
model.finish_update(optimizer)Thinc supplies batching primitives, not a high-level trainer. You still own epochs, metrics, checkpoints, early stopping, and logging.
Grow batch size with a scheduleuse-variable-batches
from thinc.api import compounding
batch_sizes = compounding(1.0, 32.0, 1.001)
for X_batch, Y_batch in model.ops.multibatch(
batch_sizes, train_X, train_Y, shuffle=True
):
train_batch(model, X_batch, Y_batch)The schedule is an iterator and advances as batches are drawn. Recreate it when beginning an independent training run.
Scope custom model composition operatorsdefine-operators
from thinc.api import Model, Relu, Softmax, chain
with Model.define_operators({'>>': chain}):
model = Relu(64) >> Relu(32) >> Softmax(3)Operator bindings are process-global while the context is active. Keep the context narrow and prefer plain combinators when custom notation obscures reviews.
Create a layer with an explicit gradientdefine-custom-layer
from thinc.api import Model
def scale_forward(model, X, is_train):
factor = model.attrs['factor']
Y = X * factor
def backprop(dY):
return dY * factor
return Y, backprop
def Scale(factor: float):
return Model('scale', scale_forward, attrs={'factor': factor})
layer = Scale(0.5)A forward function must accept model, input, and training flag, then return output plus a callback that maps output gradients back to input gradients.
Resolve registered objects from a config fileresolve-config
from thinc.api import Config, registry
config = Config().from_disk('./config.cfg')
resolved = registry.resolve(config)
model = resolved['model']
optimizer = resolved['optimizer']Import modules that register custom factories before calling `resolve`, and version registry names so old configs remain reproducible.
Register a versioned optimizer factoryregister-factory
import thinc
from thinc.api import Adam
@thinc.registry.optimizers('project.adam.v1')
def make_optimizer(learn_rate: float, beta1: float):
return Adam(learn_rate=learn_rate, beta1=beta1)Type annotations drive config validation. Changing behavior should normally produce a new registry name instead of silently changing an old config.
Persist initialized model parameterssave-and-load-model
from pathlib import Path
model.to_disk(Path('artifacts/model'))
restored = build_same_architecture()
restored.initialize(X=sample_X, Y=sample_Y)
restored.from_disk(Path('artifacts/model'))Rebuild the compatible architecture before loading weights. Named references must point within the serialized model tree.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| torch | PyPI | You want the broadest Python model, accelerator, training, and deployment ecosystem |
| jax | PyPI | You want composable transformations, automatic differentiation, and XLA-oriented functional array programs |
| tensorflow | PyPI | You need TensorFlow or Keras tooling, serving formats, and its established production stack |