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, the NumPy evaluation rule 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), its NumPy
evaluation where it has one, and whether it has derivative rules. OP_INFO holds one per op.
Source code in src/scaly/ir/expr.py
Functions¶
Function ¶
A named expression graph: named inputs, named outputs, and the computation between them.
Function 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
Function 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.
__call__ takes the whole declared input tree as one argument and dispatches on its leaves to
symbolic_call or numerical_call. Call those directly when the distinction matters. A
one-leaf tree is the bare value on both sides, as described for scaly.L, so a single-output
result must not be destructured.
Input and output names matter beyond display. Derivatives are requested by name, and the generated C symbols are built from them.
Use @scaly.function(...) to build one from a Python body.
input_shapes
property
¶
The input leaf shapes in C-signature order.
output_shapes
property
¶
The output leaf shapes in C-signature order.
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 node using the declared symbolic input and output structures.
numerical_call ¶
Compile and evaluate using the declared numerical input and output structures.
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) -> Function
Build a new Function 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 |
|---|---|
Function
|
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
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 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 | |
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.
atan2 ¶
minimum ¶
maximum ¶
vmap ¶
Create an ExprOp.VMAP node: length independent calls of callee whose i-th argument list is
sliced out of outer tensors with per-input (start, stride) strides.
inputs is a sequence ordered to match callee.inputs, or a mapping from callee input name
to the same entries. An entry is usually a bare outer tensor: one of size length * formal.size
is cut into length contiguous chunks, one of size formal.size is broadcast to every
iteration. The explicit (outer_tensor, start, stride) tuple covers overlapping or offset
windows: the i-th iteration reads outer[start + i*stride : start + i*stride + formal.size],
and stride=0 broadcasts the same slice every iteration.
Only the outer tensors must be rank-1. Callee formals and outputs may be rank-2 (as well as scalar
or rank-1). Each iteration reads a flat slice of formal.size values and the produced node has
shape (length * callee.outputs[output].size,), with iteration outputs concatenated flat.
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 the expression Expr dialect as a compact SSA assembly listing.
When given a Function, 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.