Tracing and training#
Tools own their parameters and expose stable operation bindings. Construct a
trainer with Trainer.from_tool(model) for its declared objective, or
Trainer.from_ops(operations, losses=...) for an explicitly supervised program.
Both replay supported local tensor paths with gradients.
Install tensorcode[tools] for owned models. See the quickstart
for a complete runnable example.
Train an owned tool#
from tensorcode import training
# Construct or load model first; all parameters must already exist.
trainer = training.Trainer.from_tool(model, lr=0.001)
experience = trainer.capture(inputs, targets, source="review:42")
experience.save("experience.json", operations=trainer.operations, release=True)
experience = training.load_experience("experience.json", operations=trainer.operations)
losses = trainer.fit([experience], epochs=10)
model.save_pretrained("./model")
trainer.save_checkpoint("./training", progress={"next_example": 43})Trainer.from_tool(tool, *, optimizer=None, lr=0.001) uses the tool's declared training
operation and objective. Supply an optimizer or parameter factory to override
SGD. This factory applies the tool's training-mode policy. capture performs no
optimizer step; it stores snapshotted inputs and
explicit sourced targets. step returns a loss; fit returns one per update.
Investigator and Decision targets identify a supplied hypothesis by ID or index,
or provide a finite nonnegative distribution summing to one. Scene targets
identify a supplied visual description by ID or index in ranking mode; Scene
language mode instead accepts reviewed target text.
Planner targets contain candidate_id and the observed numeric outcome, or one
observed outcome per candidate. Chatbot inputs and targets are equal-length text
lists; teacher-forced cross-entropy supervises the decoder. Feedback never becomes
input evidence merely because it shares an objective envelope.
Resume training#
model = ModelClass.from_pretrained("./model")
trainer = training.Trainer.from_tool(model, lr=0.001)
progress = trainer.load_checkpoint("./training")
experience = training.load_experience("experience.json", operations=trainer.operations)
trainer.fit([experience], epochs=1)Replace ModelClass with the same concrete tool class used to save the model.
Reconstruct the same optimizer type and parameter groups when using a custom
optimizer. load_checkpoint restores model and optimizer state, trainer.steps,
Python RNG, PyTorch CPU RNG and saved CUDA RNG; it returns your progress mapping.
Record data cursors in progress. NumPy RNG, schedulers and external data-loader
state are not automatically captured. Exact stochastic continuation also requires
a compatible device topology and execution environment.
training.json selects separate checksummed safetensors holding model, optimizer
and RNG tensors; keep those files together when moving a training checkpoint.
Module training/evaluation modes are also restored. This is a separate resumable
artifact. save_pretrained exports only
the model. Experiences and chatbot sessions are saved separately. Restoring model
weights does not restore an optimizer or a conversation.
Compose and trace individual operations#
Tracing records boundaries and dependencies independently of tools or harnesses. It preserves native tensor gradients, snapshots external inputs and distinguishes feedback from predictions. Plain Python transformations, remote services and discrete choices do not become differentiable because they were observed.
from tensorcode import trace, training
with trace() as session:
prediction = head(encoder(text))
session.supervise(prediction, "target label", source="human:review-42")
operations = {"encoder": encoder, "head": head}
session.save("experience.json", operations=operations, release=True)
experience = training.load_experience("experience.json", operations=operations)
trainer = training.Trainer.from_ops(operations, lr=0.01)
losses = trainer.fit([experience], epochs=10)
trainer.save_checkpoint("./program-training", progress={"epochs": 10})from_ops binds an already-constructed program and does not switch its modules
into training mode. Configure those modes explicitly when needed. An operation
trainer does not infer a single input/output objective; use trace() and
supervise() as above. Its capture() method rejects that ambiguous usage.
The owned vector lifecycle, hypothesis, plan, and Banking77 programs illustrate direct vector operation composition with upfront initialization and restoration across processes.
Capture and supervision#
trace() returns a public tensorcode.Trace. tensorcode.InputRef and
tensorcode.OutputRef are the supported reference types for explicit dependencies
and annotations. Captured calls and supervision records remain inspectable;
the capture engine itself is private. A trace can be collected, inspected, saved
and replayed without constructing any trainer or packaged tool.
session.ref(output) returns an OutputRef. Equal-valued independent outputs are not merged. Scalars and ambiguous aliases need explicit session.calls[index].output handles. session.example(target) extracts the dependency closure and its external roots; it does not release memory by itself. Context, supported dataclass fields and container elements retain dependencies.
session.supervise(output_or_ref, target, *, loss='cross_entropy', source='human') stores a supervision record (output, target, loss, source) with snapshotted target data. session.supervisions exposes the records; the record type itself is internal. Source must be a nonempty provenance string; feedback is never inferred from the output itself.
Mutation of captured intermediates is rejected before reuse, save or release. Unsupported mutation through .data, external storage aliases or native code can evade tensor version counters. Inference-mode tensors use a conservative content stamp, which can require a device copy/synchronization.
Awaited calls preserve live tensors and native gradients. Keep inputs and context immutable until the call finishes, including tensor views and their base tensors. Detected mutation rejects the capture before an output becomes replayable; a failed call cannot be persisted as valid experience. Version counters detect ordinary tensor/view writes, while structural stamps detect lasting container changes. A transient container change restored before completion, or an untracked storage write, can evade these checks; they do not make concurrent mutation safe.
Failed or cancelled calls remain failures. Await pending work before referencing, releasing or saving its outputs. Tasks retaining a session context cannot start new captures after the session closes.
Portable experience and replay#
session.save(path, *, operations, codecs=None, release=False) atomically writes versioned JSON after validation. Bind stable names to the exact captured operation instances. Failed calls, missing bindings, malformed/unsupported payloads and mutated outputs are rejected.
training.load_experience(path, *, operations, codecs=None) returns a session using already-constructed supplied operations. It validates operation configuration fingerprints, required bindings, artifact version, DAG references and codec tags. It never imports artifact-named classes, unpickles code or restores callbacks.
session.replay(target, *, inputs=None, boundary='error') recomputes pure operations with current parameters. External effects are rejected by default. Explicit boundary='recorded' uses detached captured external results without invoking them. Replacement inputs cannot cross a recorded boundary. Training uses recorded boundaries as constants; gradients do not cross them.
session.release() drops live results, autograd graphs, object lookup records and mutation stamps after validating the whole session. External roots, DAG, supervision and detached external boundary results remain. Preserve output handles first, or use supervision.output; identity lookup of released outputs is unavailable. save(..., release=True) persists before releasing.
Configuration and codecs#
Operations expose configuration() -> JSON-safe data for stable semantic choices. Fingerprints also cover qualified operation type, replay capability and tensor-state shape/dtype. Vector configurations additionally include spaces, label/vocabulary order, relevant module behavior, parameter/buffer structure and gradient flags, excluding learned values. Updated weights can therefore be used intentionally when loading compatible experience.
Explicit custom module configuration is authoritative. Conservative fallback accepts supported JSON behavior fields and rejects opaque behavior state. Ambiguous callbacks, closures or stateful callables need explicit configuration metadata. These declarations cannot prove that arbitrary application code retained its semantics.
Built-in codecs cover primitive values, bytes, lists/tuples, mappings and finite dense real tensors. Tensors restore on CPU. Application dataclasses require an explicit allowlist on both save and load, such as codecs={'record-v1': Record}. This covers roots, context, targets, boundary values and dataclass dependency trees. Reconstructible pure intermediate outputs need no stored codec. Immutable mapping proxies become data mappings, which registered dataclass constructors can refreeze.
Registered classes and operation bindings are trusted application code. JSON validation avoids executable deserialization; it is not resource isolation for extremely large untrusted artifacts. Sparse/complex/nonfinite tensor payloads and arbitrary opaque objects are unsupported. Applications targeting another device must explicitly adapt restored inputs/targets.
Optimizers, losses and checkpoints#
Trainer.from_ops(operations, *, optimizer=None, lr=0.01, losses=None) defaults to SGD. Supply an optimizer instance or a factory accepting the deduplicated trainable parameters. Optimizer ownership must match those parameters exactly, without duplicate shared parameters. Trainers expose operations, parameters and optimizer, plus settable steps and progress (a JSON dict restored with checkpoints); Trainer.from_tool also exposes tool. Owned text operations declare a teacher-forced objective, so Trainer.from_tool(operation) trains them directly.
trainer.step(session) averages the experience's explicit losses, performs one optimizer update and returns a float. trainer.fit(sessions, *, epochs=1) returns one loss per session update. Cross-entropy accepts integer indices or prediction label strings; MSE requires exactly matching shapes. Register custom in-process losses with losses={'name': callback}; callbacks are never serialized. Nondifferentiable, parameter-disconnected and nonfinite losses/gradients are rejected. Multiple supervised outputs replay separately, so stochastic operations can produce separate samples within a step.
Both trainer factories expose save_checkpoint(directory, progress=...) and
load_checkpoint(path). New saves contain model and optimizer state, module
modes, steps, supplied progress, and supported Python/PyTorch random state.
Restoration validates configurations, state keys, tensor shapes/dtypes, shared
parameter aliases and values, optimizer ownership/layout, slot shapes and step
counters before applying state. Failed restoration rolls back earlier changes.
Optimizer checkpoints support SGD, Adam and AdamW. Scheduler and external
data-loader state require explicit application handling.
The existing standalone tensorcode.checkpoint JSON format contains only
model/optimizer state. Loading such a file cannot restore absent random state,
modes or progress. Use a complete directory checkpoint for resumable training.
Importing tensorcode.training does not load torch. Tensor decoding, training and tensor checkpoints require the vec extra.
Complete training checkpoints store native tensors in safetensors rather than
JSON arrays. Each save atomically switches training.json to a checksummed
immutable tensors-<uuid>.safetensors generation. Previous generations remain
available; remove an obsolete checkpoint directory to reclaim them. Model,
optimizer, module modes and Python/torch random state are restored together.
Calibrate verifier scores#
from tensorcode.training.calibration import (
TemperatureCalibration, evaluate_calibration, fit_threshold,
)
# held_out_logits: [examples, classes]; labels: integer class indices.
calibration = TemperatureCalibration()
report = calibration.fit(held_out_logits, labels)
calibrated_logits = calibration(test_logits)
metrics = evaluate_calibration(calibrated_logits, test_labels)For an owned Investigator verifier, fit model.verifier.calibration so its
buffers persist with save_pretrained. model.verification_loss(pairs, labels)
trains source-wise NLI from premise/hypothesis pairs and explicit named labels;
model.proposal_loss(inputs, targets) trains reviewed proposal generation. Investigator selects the objective explicitly through a mode envelope:
from tensorcode import training
from tensorcode.tools.investigator import Investigator
model = Investigator.from_pretrained("./investigator-with-verifier")
trainer = training.Trainer.from_tool(model, lr=0.0001)
experience = trainer.capture({
"mode": "verification",
"inputs": [{"premise": "The connection was refused.",
"hypothesis": "The connection succeeded."}],
}, ["contradiction"], source="authored-example:review-17")
experience.save("verification.json", operations=trainer.operations, release=True)
experience = training.load_experience("verification.json", operations=trainer.operations)
trainer.fit([experience], epochs=1)
model.save_pretrained("./updated-investigator")
trainer.save_checkpoint("./verification-training", progress={"next_review": 18})
restored = Investigator.from_pretrained("./updated-investigator")This single authored pair illustrates the lifecycle, not a sufficient training
set. Modes are verification, proposal, rank and retrieval (see
owned retrieval); ordinary ranking inputs without an
envelope retain the ranking objective. Planner accepts proposal feedback for its
plan generator. Proposal feedback is reviewed
text, verification feedback is named NLI labels, and ranking feedback identifies
supplied alternatives. Changing verifier weights invalidates its calibration;
refit on a separate held-out set before reporting calibrated scores.
fit_threshold(confidence_scores, correctness_labels, max_error=...) returns an
empirical threshold/coverage/error report. Scores are finite confidence values;
correctness labels are explicit booleans. A None threshold means abstain on all.
Selection and reported error use the same calibration sample, so use separate test
data to evaluate the resulting policy. These utilities do not certify truth or
new-input error rates, and calibration should be repeated after weight updates.
Train owned retrieval#
An Investigator with retrieval_encoder configuration owns
model.episodic_encoder. Positive relationships belong in targets, never appended
to query/document text. The boolean matrix has shape [queries, documents] and
must include at least one positive per query:
from tensorcode import training
from tensorcode.tools.investigator import Investigator
model = Investigator.from_pretrained("./investigator-with-retrieval")
trainer = training.Trainer.from_tool(model, lr=0.0001)
experience = trainer.capture({
"mode": "retrieval",
"inputs": {
"queries": ["How was the service restored?"],
"documents": ["Restoring the database connection recovered the service.",
"The maintenance window starts tomorrow."],
},
}, [[True, False]], source="authored-example:retrieval-review-17")
experience.save("retrieval.json", operations=trainer.operations, release=True)
loaded = training.load_experience("retrieval.json", operations=trainer.operations)
trainer.fit([loaded], epochs=1)
model.save_pretrained("./updated-retrieval-model")
trainer.save_checkpoint("./retrieval-training", progress={"next_review": 18})
restored = Investigator.from_pretrained("./updated-retrieval-model")This authored pair demonstrates plumbing, not retrieval quality. Direct
model.retrieval_loss({"queries": ..., "documents": ...}, positive_mask) uses the
same contrastive objective. Multiple positives receive equal target weight.
Unmarked documents act as negatives, so review annotations accordingly. A frozen
retrieval foundation must be made trainable before updating it. Rebuild existing
episodic indexes after changing its weights; snapshots restore raw source records
and rebuild embeddings, while live indexes reject stale fingerprints.