Adding an analysis job¶
Coming Soon
Heyoooo so I ran out of time writing docs as I have to do actual machine learning. I'll eventually catch this up but as of right now here's my friend gpt-6-astra who will do the talking.
An analysis is a trainer-backed job that replaces training with inspection. Its model, configuration, initialization, and checkpoint surgery follow the same lifecycle as the trainer it assumes.
from dataclasses import dataclass
from theseus.analysis import AnalysisBase
from theseus.experiments.models.gpt import PretrainGPT
from theseus.training.base import BaseTrainerConfig
from theseus.model.attention.base import SelfAttention
from theseus.config import field
from theseus.model.debug import DebugInputs
from theseus.registry import analysis
@dataclass
class InspectConfig(BaseTrainerConfig):
target: str = field("analysis/target", default="blocks_0/attn")
@analysis("my-model/analyze/activation")
class InspectActivation(AnalysisBase, PretrainGPT):
CONFIG = InspectConfig
TARGET = SelfAttention
NAME = "activation"
def select(self, paths: list[str]) -> str:
if self.args.target not in paths:
raise ValueError(f"Choose a discovered path: {paths}")
return self.args.target
def analyze(self, layer, inputs: DebugInputs):
# Bound the result before the framework gathers it.
return inputs.x[0, :64, :128]
def plot(self, results):
return self.heatmap(results, xlabel="Feature", ylabel="Token")
Put the analysis before the trainer in the inheritance list. AnalysisBase
checks that run() resolves to an analysis. The @analysis decorator also
registers the class as a job, so no second decorator is needed.
CONFIG is one composed dataclass schema. All its values are available through
self.args. Include the trainer fields needed to construct the model even when
the job will only inspect it. TARGET identifies candidate module types, and
select() chooses an exact discovered path. analyze() runs at the selected
forward invocation with a bound Flax module and its original arguments. It must
return a JAX-compatible PyTree of arrays, scalars, or None. plot() is also
required: it receives gathered CPU results and returns a Matplotlib figure, a
JSON value, or None. Both methods may read fixed configuration from self.args.
Only plot() may render, decode tokens, convert to NumPy, or access mutable
trainer state. Numerical work must use the supplied layer and inputs, not
self.state or a separately fetched batch.
AnalysisBase.run() saves the artifact under self.spec.result() using NAME
and uploads it to Comet when configured. There is no separate export path.
The same output is stored as a node artifact with filename and content
(the exact bytes). Query it with store.query().node(node).artifact().select().
Every host performs abstract discovery and compiled analysis. The framework
gathers the returned PyTree, preserving global array shapes, then calls plot()
synchronously on host zero and writes the artifact. Select/crop/reduce results in
analyze() before returning them; do not return a full activation history unless
you intend to materialize it on the CPU. All hosts must agree on selections and
collective order.
Initialized or checkpointed state¶
Construct with Analysis(spec) to analyze newly initialized weights. Pass a
checkpoint node as base= to load it through the normal checkpoint and model
surgery path. Invoke the job normally, or let quick.create() perform setup:
from theseus.quick import quick
with quick("./results") as q:
q.build(InspectActivation, "inspect")
q.find().spec(run="training-run").checkpoint().latest().resume()
analysis_job = q.create()
analysis_job()
Construction does not allocate training state. Idempotent setup(resume=False)
initializes or restores it; the job's __call__ owns host synchronization and
execution. resume=True preserves the saved node and batch, so analysis results
attach to that checkpoint. Training advances once before consuming its next
batch. Use branch() to start a new lineage instead.
For integration during training, set ANALYSIS = [InspectActivation] on the
trainer. The borrowed analysis shares its trainer's node and observes live state
replacements. It cannot independently set up, tick, or replace that state.
training/analyze enables this integration. Each analysis retains its compiled
function across invocations; current state and the newly retrieved batch are
dynamic arguments. The trainer remains responsible for lifecycle and batch
advancement. Calling batch() again follows the existing node cache; it does not
implicitly advance the training cursor.
Select several module calls¶
select(paths) can also return a nonempty list of discovered paths. In that
case analyze(layers, inputs) receives two lists in the same order, even for a
one-item selection. The first invocation at every selected path is captured in
one compiled trace. The empty string identifies the root module. Arguments are
only those of the selected module call; no raw batch is injected into them.
Use the supplied bound modules' methods for numerical work. Their scopes and
tracers must not be retained after analyze() returns.
Trainer trace boundary¶
Trainer implementers may override the pure classmethod
trace(state, batch, key, *, sharding: ParameterShardingContext). State includes
the current parameters and any trainer-specific state; batch is the placed
microbatch, and key is an explicit RNG. The method must not retrieve data or
read mutable trainer attributes. The default calls the trainer's existing
forward() under the supplied parameter execution sharding rules.
Both find()/debug() and scheduled analysis retrieve data through the existing
batch() method and prepare it outside the trace. Debugging executes eagerly;
scheduled analysis discovers paths with jax.eval_shape and caches a JIT of
trace plus analyze(). Stable shapes, dtypes, and static configuration reuse the
compilation; different shapes or state structures can compile again. Change
configuration by constructing a new analysis instance, not by mutating the
configuration captured by an existing compiled function.
The example's target path is a starting choice; replace it with a path discovered
for your actual model. Give different attached analyses different NAME values
so their artifacts do not collide. See Analysis System for a
a minimal activation plot and inline training example.