Structural autoencoders¶
This tutorial executes a structural reconstruction loss through the same
entry boundary as an ordinary QIEC computation. The source is the gallery's
term_autoencoder.qvr, which
declares a typed lambda-term signature, an encoder, a decoder, and a loss.
The important boundary is the typed attachment path (TAP): the stable
module records Compute requests and their types, while Program.run attaches
the compiled PyTorch modules and preserves their autograd graph.
1. Compile and inspect the entries¶
import torch
from quivers.dsl import load
from quivers.structural import bound_var, make_term
torch.manual_seed(0)
structural = load("docs/examples/source/term_autoencoder.qvr")
names = [entry.name for entry in structural.entry_points()]
assert names == ["Enc", "Dec", "Dec__nll", "reconstruct"]
print(names)
Enc, Dec, and Dec__nll are host-computation entries. reconstruct is the
loss graph that calls the first and third entries in sequence.
2. Construct typed host data¶
The signature describes simply typed lambda terms. Build
λx : base. plus x with the structural host API:
term = make_term(
"Lam",
make_term("Base", "base"),
make_term("App", make_term("Const", "plus"), bound_var(0)),
)
bound_var(0) refers to the nearest binder. The encoder checks constructors,
argument arities, binder scope, and data vocabulary while it folds the term.
3. Invoke the checked loss¶
run = structural.run("reconstruct", term)
assert torch.is_tensor(run.value)
assert run.value.shape == ()
assert run.result.runtime == "core+structural"
classic_loss = structural.losses.evaluate({"term": term})
torch.testing.assert_close(run.value, classic_loss)
print(float(run.value.detach()))
The checked entry and classic loss registry produce the same scalar. The
runtime label names both providers: core evaluates the stable computation,
and structural handles the encoder and decoder attachments.
4. Differentiate through the entry boundary¶
for parameter in structural.parameters():
parameter.grad = None
run.value.backward()
assert any(parameter.grad is not None for parameter in structural.parameters())
print(len(structural.state_dict()), "checkpoint tensors")
The attachment handler returns a PyTorch tensor without converting it to a
plain scalar, so the autograd edges to both modules survive the reference
machine. The compiled encoder and decoder are registered under stable names;
an optimizer over structural.parameters() and a checkpoint from
structural.state_dict() thus include them.
5. Understand serialization and targets¶
Serializing structural.qiec writes attachment descriptors, stable IDs, and
checked types. It does not serialize the PyTorch modules. A different process
may deserialize the core graph, but it must compile or otherwise attach
compatible encoder and decoder providers before invoking reconstruct.
Current probabilistic-programming targets do not carry these neural
attachments. Checking one produces
qiec:capability:neural-attachment:<entry>:
This refusal preserves the loss graph. Silently emitting only the source declarations would suggest that the target can compute a reconstruction value when it cannot.
The Term Autoencoder example develops the encoder and decoder rules in more detail. The generated-computation reference specifies the attachment boundary.