Kernels and Compute¶
This page explains how Diffract kernels work: configuration, dependencies, aggregation, and parallel execution.
Fields vs kernels¶
A field is a named value stored on each parameter (e.g.,
frob_norm,stable_rank).A kernel is a function that produces one or more fields.
When you call:
session.compute.apply("stable_rank")
Diffract finds the kernel that produces stable_rank and executes it (plus any
dependencies).
Run session.compute.list_available_kernels(verbose=True) to list every registered
kernel with its required and produced fields.
Configuring kernels¶
Some kernels accept configuration parameters. Use session.compute.configure_kernel()
before computing:
from diffract import Session
session = Session(profile="ram")
with session:
session.compute.configure_kernel("hard_rank", rtol=1e-6)
session.compute.apply("hard_rank")
Kernel configuration is process-global: the registry is a module-level singleton shared by all sessions in the process. Configuration set in one session applies to every other session and resets only when the interpreter restarts.
Reconfiguring after results are stored¶
Configuration takes effect when a kernel executes; changing it does not invalidate
results that are already stored. apply selects work by field presence alone: a
parameter that already has the kernel’s output fields is skipped, without comparing the
stored values to the current configuration (stored values do not record the
configuration they were computed with). Re-applying after configure_kernel is
therefore a silent no-op that leaves the previous values in place. To recompute under
the new configuration, erase the stored fields first:
with session:
session.compute.configure_kernel("hard_rank", rtol=1e-9)
session.compute.apply("hard_rank") # computes with rtol=1e-9
session.compute.configure_kernel("hard_rank", rtol=0.5)
session.compute.apply("hard_rank") # no-op: "hard_rank" is already stored
session.results.erase("hard_rank")
session.compute.apply("hard_rank") # recomputes with rtol=0.5
Two caveats when erasing:
Erase a multi-output kernel’s fields as a group. A parameter is selected for computation only when all of a kernel’s output fields are absent. After erasing only
weights_svals,applycannot restore it, because the sibling fields written by the same kernel still exist. Erase the whole produce group together:erase("weights_lsvs", "weights_svals", "weights_rsvs").Stale dependents are not erased for you. Fields computed from the reconfigured kernel’s outputs keep their stored values. Pass
erase_dependent_also=Trueto also erase every stored field downstream of the ones you name.
When possible, configure kernels before the first apply; treat reconfiguration as an
explicit erase-then-apply cycle.
Dependency resolution¶
Each kernel declares its required input fields and output fields. Diffract builds a dependency graph and executes kernels in topological order.
Example:
Kernel A:
produce_fields=("x",)Kernel B:
require_fields=("x",),produce_fields=("y",)
Computing y automatically executes A first, then B.
Kernel signatures¶
Diffract infers kernel metadata from the Python function signature:
Parameters without defaults → required input fields
Parameters with defaults → configurable keyword arguments
def hard_rank(esd, *, rtol: float = 1e-5) -> int: ...
Here, esd is a required field; rtol is configurable via
session.compute.configure_kernel("hard_rank", rtol=...).
Note: *args and **kwargs are not allowed in kernel signatures.
Apply levels¶
Kernels operate at one of three levels:
Level |
Scope |
Example |
|---|---|---|
|
Per parameter |
|
|
Per model (aggregates by |
|
|
Per parameter name across models |
|
Cross-model kernels compare exactly two models¶
The alignment family — l_overlap, r_overlap, and the agreement metrics derived
from them (l_agreement, max_l_agreement, avg_l_agreement,
avg_max_l_agreement, and their right-basis r_* counterparts) — is
pairwise: each compares one layer between two model versions. Scope the session to the pair with filter(model_ids=[a, b]) before
applying:
with session:
pair = session.filter(model_ids=["checkpoint_1000", "checkpoint_2000"])
pair.compute.apply("l_agreement") # l_overlap is resolved automatically
Applying a pairwise kernel with any other number of models in scope raises
ScopeValidationError before any work runs, naming the scope and the fix:
from diffract.session import ScopeValidationError
with session: # three models in scope
try:
session.compute.apply("l_overlap")
except ScopeValidationError as err:
print(err)
# Cannot produce 'l_overlap': it is a binary cross-model kernel that
# requires exactly two models in scope, but the current scope has 3:
# ['a', 'b', 'c']. Restrict the scope to a model pair, e.g.
# session.filter(model_ids=['a', 'b']).compute.apply('l_overlap').
The requirement is transitive: l_agreement reads l_overlap, so requesting
l_agreement from a non-pair scope raises the same error and names the offending
dependency (l_overlap).
To compare more than two models, apply the kernel once per pair:
from itertools import combinations
with session:
checkpoints = ["step_1000", "step_2000", "step_3000"]
for earlier, later in combinations(checkpoints, 2):
session.filter(model_ids=[earlier, later]).compute.apply("avg_l_agreement")
See the alignment metrics reference for the mathematics of the overlap and agreement fields.
Contextual field names¶
Aggregated kernels write results with a deterministic suffix to avoid collisions:
some_metric@models[m1,m2]@params[layer.0.weight]
When you call results.export_aggregates("some_metric", ...), Diffract matches both the
base name and contextual variants.
Kernel outputs¶
Diffract normalizes kernel return values:
Return type |
Behavior |
|---|---|
|
Used as-is: |
|
Mapped positionally to |
scalar |
Stored under the single declared field |
This allows one kernel to produce multiple fields in a single pass.
Execution flow¶
For per-parameter kernels:
Filter to parameters missing the target fields
Group the pending parameters into chunks sized by an approximate read budget, derived from the cache manager’s available bytes with headroom
For each chunk: prefetch the required input fields into memory, then execute the kernel on every parameter in the chunk
Chunk sizes are estimated from stored field metadata (shape and dtype), so large fields such as weight matrices produce small chunks and scalar fields produce large ones.
Worker pool configuration:
[parallel.thread_pool]
max_workers = 8
[parallel.process_pool]
max_workers = 8
Parallelism¶
Parallelism is controlled by:
parallel.thread_pool.max_workers— threads for view filtering and field prefetchingparallel.process_pool.max_workers— worker processes for kernel executionEach kernel’s
KernelExecutionProtocol(SEQUENTIALorPARALLEL)
If a kernel is marked PARALLEL, its tasks run on a ProcessPoolExecutor sized by
parallel.process_pool.max_workers.
Practical notes:
Kernel implementations must be picklable (safe for multiprocessing)
Prefer top-level functions; avoid closures with non-serializable state
Parallelism has overhead — it helps when per-task computation is substantial
Registering custom kernels¶
Built-in kernels live in diffract/core/compute/kernels/. Register your own with the
@session.compute.kernel() decorator — required input fields and configurable keyword
arguments are inferred from the function signature:
import torch
from diffract import Session
model = torch.nn.Sequential(torch.nn.Linear(32, 64))
session = Session(profile="ram")
with session:
@session.compute.kernel()
def scaled_frob_norm(frob_norm: float, *, scale: float = 1.0) -> float:
return frob_norm * scale
session.models.add(model, model_id="m1")
session.compute.configure_kernel("scaled_frob_norm", scale=2.0)
session.compute.apply("scaled_frob_norm")
metrics = session.results.export_metrics("scaled_frob_norm", export_format="dict")
By default the kernel is named after the function, produces a single field with the same
name, runs at PARAMETER level, and executes sequentially. The decorator accepts
name, require_fields, produce_fields, apply_level, execution_protocol, and
restrictions for full control. For example, an IN_MODEL kernel receives each
required field as a tuple of per-parameter values and writes one aggregate per model:
import numpy as np
from diffract.core.compute.execution import KernelApplyLevel, KernelExecutionProtocol
with session:
@session.compute.kernel(
name="total_frob_norm",
apply_level=KernelApplyLevel.IN_MODEL,
execution_protocol=KernelExecutionProtocol.SEQUENTIAL,
)
def total_frob_norm(frob_norm: tuple[float, ...]) -> float:
return float(np.sum(np.square(frob_norm)))
session.compute.apply("total_frob_norm")
aggregates = session.results.export_aggregates(
"total_frob_norm", export_format="dict"
)
Because the registry is process-global, a kernel registered through one session is
available to every session in the process. Kernels marked PARALLEL must be picklable
top-level functions (see Parallelism).
Built-in fields reference¶
Every built-in field – its formula, apply level, required inputs, and configuration – is documented in the metric catalog, generated from the kernel registry so it never drifts from the registered kernels. The per-category pages there give the mathematics, conventions, and pitfalls.
Fit method¶
The fit kernels – power_law_fit, truncated_power_law_fit, and exponential_fit –
accept a fit_method keyword argument selecting the fitting implementation:
"auto"(default) – the accelerated taichi implementation when thetaichiextra is installed and the ESD has at least 100 points; thepowerlawlibrary otherwise. If taichi is installed but fails to initialize,autofalls back to thepowerlawlibrary."powerlaw"– always thepowerlawlibrary."diffract"– always the accelerated implementation; raises if taichi is unavailable or fails to initialize.
The 100-point floor is statistical, not performance: the accelerated fitter only
considers xmin candidates that leave a tail of at least 50 points (the reliability
bound of Clauset et al.), so below roughly twice that it cannot select a tail at all. On
small ESDs (roughly 100-300 points) this constrained search yields more conservative
estimates than the powerlaw library’s unrestricted one, which minimizes KS over
arbitrarily small tails; pass fit_method="powerlaw" for exact parity with it. For the
same reason, the p-value kernels return NaN when the fitted tail has fewer than 50
points.
Select the implementation per kernel:
session.compute.configure_kernel("power_law_fit", fit_method="powerlaw")
session.compute.apply("pl_alpha")