Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
148 changes: 148 additions & 0 deletions docs/content/creating-tesseracts/pipelines.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# Composing Tesseracts into pipelines

A single Tesseract packages one computation. Real work usually involves several components: a mesher feeds a solver, an encoder feeds a model, a simulation feeds a post-processor. This page is about the step _after_ you've built your Tesseracts, that is, calling them from your own code and chaining them into a larger workflow.

Tesseract Core is deliberately unopinionated about what you build on top. A project that uses Tesseracts might be a training loop, an optimization routine, a notebook, a web service, or a full application, with as much or as little logic of its own as you like. What follows is the small amount of generic advice that applies regardless: _how to call and chain Tesseracts well_.

The [Design Patterns](design-patterns.md) page covers the complementary question of how to split a workflow into Tesseracts in the first place. Read that for the "how many, how granular" decisions, read this for "how do I wire them together."

## Choosing how to call a Tesseract

Every Tesseract exposes the same three interfaces: a [CLI, a REST API, and a Python SDK](../using-tesseracts/use.md). For composing Tesseracts into a program, there are two approaches worth knowing, and which one you reach for depends on whether you're working inside an autodiff framework.

### If you're using JAX or PyTorch: use the framework bindings

This is the recommended default when your surrounding code already lives in JAX or PyTorch. **[Tesseract-JAX](https://github.com/pasteurlabs/tesseract-jax)** and **[Tesseract-Torch](https://github.com/pasteurlabs/tesseract-torch)** wrap a Tesseract so it behaves like a native operation in that framework — traceable, and above all _differentiable_, so gradients flow through the containerized computation and into the rest of your program.

Both expose the same one-function interface, `apply_tesseract(tesseract, inputs)`. With JAX:

```python
import jax
import jax.numpy as jnp
from tesseract_core import Tesseract
from tesseract_jax import apply_tesseract

t = Tesseract.from_image("vectoradd_jax")
t.serve()

x = jnp.ones((1000,))
y = jnp.ones((1000,))

def vector_sum(x, y):
res = apply_tesseract(t, {"a": {"v": x}, "b": {"v": y}})
return res["vector_add"]["result"].sum()

vector_sum(x, y) # call it like any JAX function
jax.jit(vector_sum)(x, y) # ... jit it
jax.grad(vector_sum)(x, y) # ... and differentiate through it

t.teardown() # stop the container when you're done
```

The PyTorch equivalent integrates with autograd instead:

```python
import torch
from tesseract_core import Tesseract
from tesseract_torch import apply_tesseract

t = Tesseract.from_image("vectoradd_torch")
t.serve()

x = torch.ones(1000, requires_grad=True)
y = torch.ones(1000)

res = apply_tesseract(t, {"a": {"v": x}, "b": {"v": y}})
loss = res["vector_add"]["result"].sum()
loss.backward() # gradients propagate back through the Tesseract
print(x.grad)

t.teardown() # stop the container when you're done
```

```{tip}
These bindings are what makes Tesseracts *differentiable software components*. If your workflow is an optimization, calibration, or training problem, reaching for Tesseract-JAX or Tesseract-Torch usually means you get end-to-end gradients for free. See the [Differentiable Programming guide](../misc/differentiable-programming.md).
```

### Otherwise: use the Python SDK

When you're not inside JAX or PyTorch (a plain Python script, a service, a batch job), the [`Tesseract`](#tesseract_core.Tesseract) SDK class is the general-purpose way to invoke a Tesseract:

```python
import numpy as np
from tesseract_core import Tesseract

with Tesseract.from_image("scaler") as scaler:
result = scaler.apply({"vector": np.array([1.0, 2.0, 3.0]), "scale_factor": 2.0})

print(result["scaled_vector"]) # -> [2. 4. 6.]
```

[`Tesseract.from_image`](#tesseract_core.Tesseract.from_image) references a built image by name; the `with` block starts the container and tears it down on exit. `apply` takes a dict of inputs and returns a dict of outputs: NumPy in, NumPy out, no manual serialization.

```{note}
To call a Tesseract that already runs elsewhere (a shared service, a GPU node, a remote deployment), use [`Tesseract.from_url(...)`](#tesseract_core.Tesseract.from_url) instead of `from_image`. The calling code is otherwise identical, so a workflow developed locally moves to distributed execution without a rewrite.
```

## Chaining Tesseracts

A Tesseract's outputs are ordinary values (a dict of arrays), so you feed them into the next call like any other Python data. There's no special pipeline object to learn.

With the SDK:

```python
with Tesseract.from_image("scaler") as scaler, \
Tesseract.from_image("normalizer") as normalizer:
scaled = scaler.apply({"vector": vector, "scale_factor": 2.0})
normalized = normalizer.apply({"vector": scaled["scaled_vector"]})
```

And the same shape inside a differentiable JAX program. Call `apply_tesseract` for each step and let the framework thread gradients through the whole chain:

```python
def pipeline(vector):
scaled = apply_tesseract(scaler, {"vector": vector, "scale_factor": 2.0})
normalized = apply_tesseract(normalizer, {"vector": scaled["scaled_vector"]})
return normalized["normalized_vector"]

jax.grad(lambda v: pipeline(v).sum())(vector) # gradient through both Tesseracts
```

Because chaining is just data flow, ordinary control flow works too. Put a Tesseract call in a loop, behind a conditional, inside `jax.lax.scan`, or wherever your program needs it.

When a chain misbehaves, test each Tesseract in isolation first. A component you've verified on its own against a known input/output pair is a fixed point you can trust while debugging the workflow around it.
Comment on lines +112 to +113

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a link to some docs on tesseract test or do you still feel this is too experimental.


The one thing that makes or breaks chaining is at the _interface_, not the call site: a step composes cleanly with the next only if its output fields line up with the downstream input schema. Design for that.

```{seealso}
[Designing good interfaces](design-patterns.md#designing-good-interfaces) — matching one Tesseract's `OutputSchema` to the next's `InputSchema` is what keeps chains readable. Design interfaces with the downstream consumer in mind.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I'm not so keen on this, it feel a bit strong and prefer certain design patterns unnecessarily. The example above breaks this pattern (outputs from one Tesseract are used in another but that's it, the name changes, you still have to extract it, etc.

```

## Building a multi-Tesseract project

Once a project grows past a couple of Tesseracts, some structure pays off: a consistent place for each component, a way to share code between them, and a build-and-test loop that covers the whole set. Rather than assemble this by hand, the [`cookiecutter-tesseract`](https://github.com/pasteurlabs/cookiecutter-tesseract) template generates a ready-made project with all of it wired up:

```bash
$ pip install cookiecutter
$ cookiecutter github:pasteurlabs/cookiecutter-tesseract
```
Comment on lines +125 to +128

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we give uv installation instruction as well/instead (see https://github.com/cookiecutter/cookiecutter)


The generated project gives you an opinionated, working structure for a multi-Tesseract codebase, including:

- **A two-layer project layout** that separates the individual Tesseracts (each independently built and tested) from the application code that uses them.
- **A one-command workflow** for scaffolding, building, and testing components, so you don't call the underlying tooling by hand.
- **A shared-code package** that every component can depend on, so common helpers live in one place instead of being copied into each Tesseract.
- **Per-component test fixtures** for checking each Tesseract in isolation, plus a place for tests of the application layer.
- **Pre-build hooks** for components that need setup before their container builds (fetching weights, compiling an extension).
- **Continuous integration** that builds and tests every component across supported Python versions, catching a broken interface the moment it stops matching its consumer.

Take the parts that fit your project and leave the rest. For anything beyond a handful of Tesseracts, starting from the template usually beats reinventing this plumbing.

## What's next

- [Design Patterns](design-patterns.md) — how to split a workflow into Tesseracts and design their interfaces.
- [Interacting with Tesseracts](../using-tesseracts/use.md) — the full SDK, CLI, and REST interfaces.
- [Differentiable Programming](../misc/differentiable-programming.md) — propagating gradients through a composed, multi-Tesseract program.
- [Tesseract-JAX](https://github.com/pasteurlabs/tesseract-jax) and [Tesseract-Torch](https://github.com/pasteurlabs/tesseract-torch) — the framework bindings.
- [Performance](../misc/performance.md) — minimizing container and data-transfer overhead in chained workflows.
- Questions? Ask on the [Tesseract User Forums](https://si-tesseract.discourse.group/).
1 change: 1 addition & 0 deletions docs/content/introduction/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ Changelog <https://github.com/pasteurlabs/tesseract-core/releases>

../creating-tesseracts/create.md
../creating-tesseracts/design-patterns.md
../creating-tesseracts/pipelines.md
../creating-tesseracts/advanced.md
../creating-tesseracts/llm-assistance.md
../creating-tesseracts/deploy.md
Expand Down
Loading