Handling Differentiability

Every Tesseract defines its interface through Pydantic BaseModel classes (InputSchema and OutputSchema). These schemas describe the structure, shapes, and dtypes of all array fields, and crucially, which fields are differentiable.

The Differentiable[...] annotation

Fields wrapped with Differentiable[...] participate in automatic differentiation. Fields without it are treated as constants by PyTorch’s autograd, even if their values change between calls.

from pydantic import BaseModel
from tesseract_core.runtime import Array, Differentiable, Float32

class InputSchema(BaseModel):
    x: Differentiable[Array[(3,), Float32]]   # differentiable
    label: Array[(1,), Float32]               # non-differentiable

class OutputSchema(BaseModel):
    loss: Differentiable[Array[(), Float32]]  # differentiable
    metadata: Array[(4,), Float32]            # non-differentiable

Fields can be arbitrarily nested (dicts, lists, and nested models). Differentiable[...] applies per-leaf:

class InputSchema(BaseModel):
    params: dict[str, Differentiable[Array[(None,), Float32]]]  # all leaves differentiable
    config: dict[str, Array[(None,), Float32]]                  # all leaves non-differentiable

When apply_tesseract is called, Tesseract-Torch inspects these annotations to determine which inputs participate in the autograd graph and which outputs are returned as torch.Tensor with grad_fn.

Non-differentiable inputs

Non-differentiable inputs are passed through to the Tesseract as static values. They do not participate in gradient computation. If you provide a torch.Tensor for a non-differentiable field, it will be detached and converted to NumPy before being sent to the Tesseract.

To differentiate only with respect to specific inputs, simply provide torch.Tensor with requires_grad=True only for the fields you want gradients through:

x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)   # will get gradients
label = torch.tensor([0.0])                                # no gradients

result = apply_tesseract(tess, {"x": x, "label": label})
result["loss"].backward()
print(x.grad)  # has gradients

Non-differentiable outputs

Output fields not marked as Differentiable[...] are returned as plain NumPy arrays or Python scalars. Only differentiable output fields are returned as torch.Tensor instances that participate in autograd.

If you need to use a non-differentiable output in downstream PyTorch computation, convert it explicitly:

result = apply_tesseract(tess, inputs)
loss = result["loss"]           # torch.Tensor with grad_fn
metadata = result["metadata"]   # NumPy array, no grad_fn

# Convert if needed for downstream use
metadata_tensor = torch.as_tensor(metadata)