SKEEP-003 addendum: placement, eager lifetime, and graph-level planning β resolved
Status: Resolved 2026-08-26 Β· Issues #1131 / #1133 / #1134 / #1135 Β· Umbrella #932
SKEEP-003 shipped the storage model (M0βM2, then the #1109 weight-form arc) and left three questions open under the P7/P8 umbrella (#1131). This page records the answers and where each one landed. A terminology note first: the SKEEP’s own phase list uses "P7" for compiled parity and "P8" for faΓ§ade removal; the issues used "P7/P8" for device placement and graph-level memory planning. This page names issues, not P-numbers.
1. Who chooses a tensor’s device placement? A resolver. (#1133)
Placement joined WeightForm as a resolved decision β a pure function of three inputs, none of
which the model author knows:
-
what will be held β the resolved
WeightForm(encoding Γ order Γ shape Γ residency), -
what the profile says β
PlannerProfile.domainForand its off-heap threshold, -
what the platform can do β
StorageCapabilities(mmap? off-heap?), injectable so a test can resolve for a platform it is not running on.
AllocationResolver.resolve(weight, profile, platform) produces the AllocationSpec;
AllocationResolver.explain(…) renders the same decision with its reason, so a plan can say
where every tensor lands and why, before a byte of payload is read. Consumers β the plan
(PlanTensor.allocation), the loader (ResolvedGguf), a context β carry and obey; none decide.
Mapping requires all three conditions: the form asks MAPPED, the platform can map, and the bytes
really are the file’s bytes β a dequantized or feed-ordered weight is a load-time copy, and a copy
cannot be paged from a file it no longer matches. Everything else falls to the profile’s
heap/off-heap threshold over the bytes actually held.
No graph is needed. Placement of weights is per-tensor and depends on nothing downstream; activations are a lifetime question (Β§2), not a device question. Every backend today is CPU; device arbitration policy would be untestable speculation, deferred until a second device exists.
The user always wins. The precedence order, explicit and documented on the loader:
per-tensor weightFormFor > uniform weightForm > the resolver (the three legacy parameters were removed outright in #1159).
WeightForm(DequantizeTo(FP32), residency = HEAP) β everything dense, on the managed heap β is a
supported one-liner, not a fight with the planner.
Deleted, as this SKEEP scheduled: @Place/@Weights (declared, retained, read by nothing β they
asked the model author, who does not know the three deciding facts), the dead
tensor.storage.MemoryPlanner (name-collided with the live memory.plan.*), StorageSpec, and
Placement.Residency. Residency vocabulary reconciled by deletion: lifetime is ScopeKind
(MODEL/FORWARD/AMBIENT, enforced by Storage/Scope), weight staging is
WeightForm.WeightResidency (HEAP/MAPPED, decided by the resolver). Placement itself stays β
the KV-cache stores carry it as device/domain intent.
2. Does eager execution need its own buffer-lifetime mechanism? Yes β and it existed. (#1135)
The Scope split is the mechanism. Eager lifetimes follow call structure β the generation loop
knows where a step ends β so ForwardScope.reset() at step boundaries replaces graph liveness
analysis: one pre-sized slab, bump allocation, overflow accounting for slab sizing, retain() as
the sanctioned escape, and a use-after-reset that throws StorageClosedException instead of
corrupting.
What was missing was any reader of ExecutionContext.memoryScope. The creation path is now that
reader: zeros/ones/full/fromFloatArray draw dense-FP32 bytes from the active scope
(StorageFloatTensorData), opt-in via ctx.forwardScope(slabFloats) { scoped, scope → β¦ },
with Scope.Ambient β the default everywhere β byte-for-byte the old path.
Two boundaries held on purpose:
-
ScratchPoolstays, unmerged. It is untyped intra-kernel workspace inside one op invocation;memoryScopegoverns inter-op activation lifetime across a step. Different layers; neither replaces the other. -
StorageFloatTensorDatais not aFloatArrayTensorData. Slab slices have a nonzeroarrayOffset; the ops fast paths that unwrapbufferassume offset 0. Kernels that want zero-copy take the view, which carries the offset.
Op outputs draw from the active scope too since #1146 (TensorDataFactory.adoptFloatArray,
ScopedTensorDataFactory), with offset-aware FP32 fast paths and Panama vector kernels (#1173)
so slab-backed tensors keep SIMD speed.
3. Would graph-level memory planning duplicate IREE? Yes. (#1134)
On the recorded path (tape β ComputeGraph β StableHLO text + .irpa), iree-compile performs
buffer allocation, liveness and scheduling on the very MLIR SKaiNET emits β and it does so after
fusion and layout decisions SKaiNET cannot see, so an upstream planner’s liveness answers would be
invalidated by the consumer. Meanwhile nothing in this repository executes IREE at all: the
integration is a format contract (text-shape assertions in tests, IrpaWriter for the weights
sidecar), with compilation and execution in external tooling.
Resolution: core decides and carries; planning stays downstream. Decisions made by core
resolvers (WeightFormResolver, AllocationResolver) must survive to the MLIR so the downstream
compiler can honour them β that is carriage, not planning.
|
Dependency rule
|
The carry work landed as the #1178/#1179/#1180 arc (0.49.0): TensorRef carries identity,
encoding and block order; TensorSpec metadata accessors follow the untyped precedent; the
structural skainet.tensor_layouts module attribute and ExternalParameterRef.blockOrder declare
the facts to the consumer; and LayoutAssignmentPass runs on the production path via
HloGenerator.generate(target = β¦), populating the pre-built ResolvedComputeGraph seams. Until
the conformity pipeline exists (#1148, the future IREE-validation SKEEP), the carriage tests are
the metadata’s reader.
Where everything landed
| Question | Issue | Landed as |
|---|---|---|
Delete the dead placement machinery |
#1142 |
PR #1152 |
|
#1143 |
PR #1153 (re-landed #1155) |
Loader consults the resolution ( |
#1144 |
PR #1154 (re-landed #1155) |
|
#1145 |
PR #1156 |
Op outputs through the scope + offset-aware SIMD |
#1146, #1173 |
landed (PRs #1168/#1175) |
Layout/placement carriage to StableHLO |
#1147 (#1178/#1179/#1180) |
landed (PRs #1182/#1183/#1184) |
|
#1148 |
open (follow-up) |
The SKEEP-003 items "`StorageSpec`, MemoryPlanner and the annotations are deleted unless wired"
and "one planner per context, consulted by the creation path" are both discharged β the first by
#1142, the second in the resolver-owned form described above (a per-context planner object turned
out to be the wrong shape; a stateless resolver plus the context’s scope covers both halves).