Core¶
Expr represents a calculation whose inputs are not yet known. Function gives that calculation
named inputs and outputs so that you can evaluate it, differentiate it, or generate C code.
The functions guide shows how to use both.
The first sections cover modelling. Verification, rewriting, and text rendering are lower-level interfaces for inspecting or extending the compiler.
Expressions¶
Expr
dataclass
¶
One node of the expression graph: an op, its argument nodes and the resulting TensorType.
Nodes are immutable and interned, so building the same expression twice returns the same
object. Create leaves with sym and const, then combine them with the operators, methods
and builders below.
scalar ¶
Return this expression with a hint to expand its procedure into straight-line scalar code.
The hint overrides the automatic size limits and applies to the entry point too. Only
procedures whose values are all float64 expand, and a block hint in the same function
wins. See the lowering page of How it works for the policy.
block ¶
Return this expression with a hint to keep its procedure as loops over buffers.
The procedure is not expanded into scalar code and survives as its own C procedure. Every other optimization still runs on it.
sym(name, shape=None, *, dtype=dtypes.float64, diff=True)
staticmethod
¶
Create a named symbolic input, the leaf every graph is built from.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
the input's name, which becomes its name on any |
required |
shape
|
int | tuple[int, ...] | None
|
an |
None
|
dtype
|
DType | str
|
the element type, |
float64
|
diff
|
bool
|
whether derivatives with respect to this input are meaningful. Setting it to
|
True
|
const(value, *, dtype=None)
staticmethod
¶
Wrap an array or scalar as a constant node.
Constants are never differentiable: a derivative with respect to one is structurally zero.
ExprOp ¶
Bases: StrEnum
The expression dialect's operation set.
A StrEnum, so an op is a proper enum value and still prints and serializes as its name.
OP_INFO carries the arity and the differentiability of each.
Source code in src/scaly/ir/expr.py
OpInfo
dataclass
¶
What the compiler knows about one ExprOp: its arity (None for variadic) and whether it
has derivative rules. OP_INFO holds one per op.
Source code in src/scaly/ir/expr.py
Functions¶
Function
dataclass
¶
A Python body and declarations, traced once for each binding of its shape holes.
Calling with symbolic leaves embeds the selected instance in a graph. Numerical leaves
evaluate its compiled code. Each call takes one positional argument per declared parameter.
instantiate returns the concrete graph for inspection or ahead-of-time compilation.
instantiate ¶
instantiate(shapes: tuple[int | tuple[int, ...] | TensorType, ...] | None = None) -> ConcreteFunction[SymbolicInputs, NumericalInputs, SymbolicOutputs, NumericalOutputs]
Bind input shape holes in flat leaf order and return the cached graph instance.
A fully declared function needs no arguments. Bare functions must be bound by calling them.
TensorType bindings must agree with the declaration's dtype and differentiability.
symbolic_call ¶
Embed a symbolic call, including the zero-input case.
numerical_call ¶
Bind shapes, compile the instance, and evaluate its numerical outputs.
factory ¶
factory(name: str, inputs: Sequence[str], outputs: Sequence[str | DerivSpec], aux: Any = None) -> Function
Derive named outputs once per binding, with one parameter per selected input leaf.
Shape templates must include every open source input in inputs. Fixed source inputs
may be omitted when the requested outputs do not depend on them. Seeds use fwd:<input>
and weights use lam:<output>, as in ConcreteFunction.factory.
sparsity ¶
Return an output's compact-value pattern without numerical evaluation or compilation.
Pass the same arguments as a call to select its binding. Fully specified functions also
accept no arguments. of selects an output by name and is required for multiple outputs.
Dense outputs return None; sparse derivative patterns index their compact values.
recompile ¶
Invalidate a fully specified function's cache; its next numerical call recompiles.
solver_stats ¶
Return the latest statistics for a solver in the fully specified graph.
Function.instantiate returns a concrete instance, which holds the resolved shapes and
expression graph for inspection and export.
ConcreteFunction
dataclass
¶
A named expression graph: named inputs, named outputs, and the computation between them.
ConcreteFunction is the unit of composition, differentiation and compilation. Calling fn(inputs)
with Expr leaves puts one call node in a larger graph. Lowering may still inline a small
callee when it expands a procedure into scalar code. fn.factory(...) derives a new
ConcreteFunction carrying the requested derivatives. Calling fn(inputs) with array leaves lowers
it, renders C, compiles and caches a shared library, and calls it through the pointer entry that
every generated function shares.
Each call takes one argument per parameter in the input tree. A group remains a tuple-valued
parameter. Empty calls evaluate numerically; symbolic_call() embeds a zero-input graph.
A single-output result is a bare leaf.
Input and output names matter beyond display. Derivatives are requested by name, and the generated C symbols are built from them.
A ConcreteFunction is immutable. Use @scaly.function(...) to build one from a Python body,
or ConcreteFunction.build from expressions already in hand.
results
property
¶
Everything one call computes: the outputs, then the residuals of a custom reverse rule.
result_names
property
¶
The output names, then the first of res:0, res:1, ... that no output takes, one per residual.
nodes
cached
property
¶
Every expression node the results reach, each after its operands.
input_shapes
property
¶
The input leaf shapes in C-signature order.
output_shapes
property
¶
The output leaf shapes in C-signature order.
build
classmethod
¶
build(name: str, input_tree: Tree[Any, Any], inputs: Sequence[Expr], output_tree: Tree[Any, Any], outputs: Sequence[Expr], *, output_sparsities: Sequence[SparsityPattern | None] | None = None, output_coloring_widths: Sequence[int | None] | None = None, device: DeviceSpec | str | None = None, descriptor: Any = None, role: Role | None = None) -> ConcreteFunction[Any, Any, Any, Any]
Build a function from its trees and the expressions at their leaves.
inputs are the INPUT expressions of input_tree's leaves and outputs the
expressions of output_tree's leaves, both in flat leaf order. Every input the outputs
depend on must be among inputs. Sparse outputs carry the pattern of their compact values.
input_map ¶
The symbolic input leaves keyed by their declared names, in declaration order.
output_map ¶
The output expressions keyed by their declared names, in declaration order.
symbolic_call ¶
Embed a call using one symbolic argument per declared parameter.
numerical_call ¶
Compile and evaluate one numerical argument per declared parameter.
recompile ¶
Drop the cached compiled handle and remove the on-disk cache entry for this function.
solver_stats ¶
Return the latest stats for a solver reached by this compiled function.
factory ¶
factory(name: str, inputs: Sequence[str], outputs: Sequence[str | DerivSpec], aux: Mapping[str, Sequence[str]] | None = None) -> ConcreteFunction
Build a new ConcreteFunction whose outputs mix this function's outputs and derivatives of them.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
the new function's name, which also names its generated C symbols. |
required |
inputs
|
Sequence[str]
|
the input names, in order. Besides the declared inputs, |
required |
outputs
|
Sequence[str | DerivSpec]
|
output names and |
required |
aux
|
Mapping[str, Sequence[str]] | None
|
extra outputs usable by name in |
None
|
Returns:
| Type | Description |
|---|---|
ConcreteFunction
|
A |
Raises:
| Type | Description |
|---|---|
ValueError
|
if a name in |
Types¶
TensorType
dataclass
¶
The shape and dtype of an expression, and whether it is differentiable (diff).
Source code in src/scaly/ir/types.py
DType
dataclass
¶
Interned dtype descriptor.
See dtypes for the canonical instances. A DType compares equal to its string name,
so dtypes.float64 == "float64" holds.
Source code in src/scaly/ir/types.py
dtypes ¶
Canonical interned dtype instances. Mirrors the small tinygrad-style registry.
SparsityPattern
dataclass
¶
The structurally nonzero entries of a matrix, as coordinate lists rows and cols.
The entry order is the order of the values stored alongside the pattern. It is not necessarily
row- or column-major. to_csr and to_csc return the permutation into either order.
Source code in src/scaly/ir/types.py
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 | |
to_csr ¶
(row_ptr, col_ind, val_perm): val_perm[k] is the COO position of CSR slot k, so
values_csr[k] = values[val_perm[k]] pairs a compact COO-ordered value buffer with the CSR
indices. The COO order is arbitrary, for example piece by piece for a mapped sparse Jacobian.
Source code in src/scaly/ir/types.py
to_csc ¶
(col_ptr, row_ind, val_perm), the compressed-column counterpart of to_csr.
Source code in src/scaly/ir/types.py
as_dtype ¶
Coerce a dtype name, a DType or None into a canonical DType.
None means float64, scaly's default.
Source code in src/scaly/ir/types.py
Builders¶
dot ¶
Scalar product of two expressions with the same number of entries, whatever their shapes.
Source code in src/scaly/ir/expr.py
sumsqr ¶
norm_2 ¶
stack ¶
Join equally shaped expressions along a new axis, adding one to the rank.
concat ¶
Join expressions along an existing axis. All other dimensions must agree.
split ¶
Split x along axis, into sections equal parts or at the given boundaries.
Source code in src/scaly/ir/expr.py
vec ¶
gather ¶
Read x at flat indices. The result takes the shape of indices.
scatter ¶
Place values at flat indices in a zero tensor of shape.
Repeated indices accumulate. indices must have as many entries as values.
segment_sum ¶
Sum flat values by segment id into a vector of length n.
Segment ids are fixed integers in [0, n). Empty segments are zero, and
repeated ids accumulate in input order.
atan2 ¶
minimum ¶
maximum ¶
print(fmt, *values) ¶
Return the first of values, printing them all each time generated code computes it.
fmt has one {} for each value, and {{ and }} for literal braces. Each print ends
its line. A value prints with 17 significant digits, enough to read a float64 back exactly,
and a tensor prints flat as [a, b, ...]. The print is part of the returned expression, so a
function's outputs have to use it. A print they do not use raises ValueError when Scaly runs
the function body to build its expression graph.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fmt
|
str
|
the text to print, with a |
required |
values
|
Any
|
the |
()
|
vmap ¶
vmap(callee: Function[SI, NI, SO, NO] | ConcreteFunction[SI, NI, SO, NO], length: int) -> Function[SI, NI, SO, NO]
Map independent calls over a leading axis, preserving parameter and output trees.
Each output leaf has shape (length, *callee_shape). Use broadcast for a whole
shared tensor and window for rank-1 overlapping slices of a declared width.
Affine views are read from their base in place. Other views preserve their element order.
PyTorch weights¶
load_torch_state_dict ¶
Load a simple PyTorch state_dict zip checkpoint without importing torch.
Supports the modern torch.save(state_dict, ...) zip layout: data.pkl
stores tensor metadata and data/<n> stores raw CPU storage bytes. This is
intentionally narrow. Unsupported pickle globals fail loudly instead of being
materialized.
Source code in src/scaly/utils/torch_state_dict.py
Verification and rewriting¶
verify_expr ¶
Topologically walk the DAG below root and raise on the first violation.
spec defaults to spec_expr, the full expression-dialect contract.
Source code in src/scaly/ir/expr_spec.py
Spec ¶
Bases: Generic[Node, Op]
A verifier: a set of Rules, indexed by op so checking a node touches only its own.
Rules with op=None apply to every node. Both dialects build their specs from this class.
Source code in src/scaly/ir/spec.py
Rule
dataclass
¶
Bases: Generic[Node, Op]
One verifier check: check returns an error message for a bad node, or None.
The rule applies to nodes whose op is op, or to every node when op is None.
Source code in src/scaly/ir/spec.py
VerifyError ¶
Bases: Exception
Raised by verify_expr and verify_program at the first node failing a rule.
The message names the node, its op and the rule it broke. Both dialects raise this one type.
Pattern
dataclass
¶
One rewrite: nodes with op op (any op when None) that satisfy predicate become replacement(node).
Source code in src/scaly/ir/match.py
PatternMatcher ¶
An op-indexed set of rewrite Patterns.
Indexing by op means a node is only tested against patterns that could match it, which is what keeps rewriting cheap as the pattern set grows.
Source code in src/scaly/ir/match.py
rewrite ¶
rewrite(root: Node, patterns: Iterable[Pattern[Node]] | PatternMatcher[Node], *, rebuild: Callable[[Node, tuple[Node, ...]], Node] | None = None, fixpoint: bool = True, revisit: bool = False, max_steps: int = 1000000, memo: dict[Node, Node] | None = None) -> Node
Apply patterns across a graph, bottom up, and return the rewritten root.
Iterative over an explicit stack, so depth in the graph never becomes depth on the Python
stack. Each node is visited once with its already-rewritten children memoized by identity, so
shared subgraphs stay shared. fixpoint retries the matcher on a node until nothing fires.
revisit instead walks into a replacement's subgraph so nested rewrites collapse in one
pass. max_steps bounds the total number of replacements. rebuild defaults to the
expression adapter rebuild_expr. Program adapters may reuse memo across roots with
identical patterns, rebuild, and options. Expression nodes do not support caller memoization
because their lowering hints depend on traversal provenance.
Source code in src/scaly/ir/match.py
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 | |
simplify ¶
Apply algebraic identities and constant folding until the graph stops changing.
Covers the shared arithmetic identities of passes/arith.py (neutral elements, zero
annihilation, x - x, negation normalization, small constant powers), folding of all-constant
subgraphs, identity reshape, transpose and gather, slice of slice, slice of stack, A.T @ v as
v @ A (and v @ A.T as A @ v), and a matmul with an all-ones vector as sums.
Source code in src/scaly/passes/expr.py
cse ¶
cse_many ¶
Common-subexpression elimination across several outputs at once.
Sharing is found between the outputs as well as inside each, which is the point of asking
for several derivatives from one Function.factory call.
Source code in src/scaly/passes/expr.py
Text¶
format_expr ¶
Render a graph as a topological dump with stable %0, %1, ... names.
For the stable assembly form meant for diffs and tests, use render_expr_assembly.
Source code in src/scaly/ir/expr.py
render_expr_assembly ¶
render_expr_assembly(obj: Function | ConcreteFunction | Expr | Iterable[Expr], *, name: str | None = None) -> str
Render the expression Expr dialect as a compact SSA assembly listing.
When given a ConcreteFunction, the listing is a expr.module containing every transitive expression
callee body before the requested function. This mirrors what C rendering eventually needs.
Source code in src/scaly/ir/text.py
render_program_assembly ¶
Render Program IR as structured assembly with explicit control-flow constructs.