# Scheduling implementation notes The [scheduling overview](scheduling.md) maps ownership and summarizes costs/complexity. Normative graph, pipeline, synchronization, and target contracts are linked there; the details below describe current implementation choices. ## Publication, order, and local materialization [`materializeScheduledCompute`](../../src/PIM/Dialect/Spatial/Passes/Transforms/MergeComputeNodes/ScheduledComputeMaterialization.cpp) records graph IDs for publication identity, not execution order. Class lanes retain graph encounter order independently of physical core IDs, preserving regular source-lane mappings. Within each core, instances sort by verified scheduler slots. Only consecutive compatible instances form homogeneous runs; class-lane ordering must not reorder a core's work. `insertLocalFragment` replaces a complete singleton insertion with a leading unit-dimension reshape view only when: - source and destination have static shapes; - destination rank is source rank + 1, with leading dimension 1 and remaining dimensions exactly equal to the source shape; - element type and encoding match; - offset/size/stride lists all match destination rank; and - every offset is statically zero, every size is the full corresponding destination dimension, and every stride is statically one. This includes scalar sources. Other insertions retain `tensor.insert_slice` semantics rather than being converted into views. Receive forwarding belongs to [`PimBufferizationPass.cpp`](../../src/PIM/Dialect/Pim/Passes/Transforms/Bufferization/PimBufferizationPass.cpp), after copy lowering and contiguity normalization. It requires a private allocation and single-use receive/copy chain in the same block, zero source and receive offsets, and equal sizes. The receive can sink to the copy target past memory-effect-free operations and `memref.load` (including lane lookup loads). Writes, synchronization, and other effectful operations are barriers. Forwarding does not establish synchronization policy. ## Transfer accounting and transpose costs [`ComputeGraph.cpp`](../../src/PIM/Dialect/Spatial/Passes/Transforms/MergeComputeNodes/Scheduling/ComputeGraph.cpp) uses the same cached publication map for dependency extraction and projected transfer volume. Physical slots map back to graph lanes; graph-range overlap is not a byte estimate. Blueprint edges belong to this shared graph for both scheduling strategies. Exact projections charge selected physical fragments individually, including each fragment's byte, endpoint-memory, and packet rounding. Conservative batch dependencies charge a producer chunk as one aggregate: scale element count by chunk lanes, multiply by effective bitwidth, then round to bytes once before memory-access/flit rounding. Dividing an already rounded whole-result byte count would lose sub-byte payloads. These two granularities are deliberate. Spatial can analyze sub-byte storage; PIM communication lowering requires byte-sized elements. Blueprint assembly charges per fragment. A full fragment receive directly into assembly storage omits duplicate endpoint-memory edge cost; later copy elimination can leave this estimate conservative. Execution transpose costs share `getTransposeRun` with lowering: round memory reads/writes per run and add vector execution when source stride is not one. The [Pimsim vector unit](../../backend-simulators/pim/pimsim-nn/src/core/component/VectorUnit.cpp) charges vector payload bytes, not the full strided address footprint. The compiler model follows that accounting; instruction setup/issue and contention remain approximations, not extra inferred memory traffic. ## Priorities, placement, and recovery limits [`PipelineScheduling.cpp`](../../src/PIM/Dialect/Spatial/Passes/Transforms/MergeComputeNodes/Scheduling/PipelineScheduling.cpp) caches a per-template adjustment between physical execution cost and nominal volume priority, scaling it by chunk lanes. Partitioning and packing use the nominal priorities; transposes count as bulk moves there. PEFT, final schedule evaluation, and reported per-core demand use physical estimates. These quantities must not be substituted for one another when interpreting results. Placement compares maximum per-core serial compute plus incident same-stage transfer demand, then completion/span and compute load. Each participating core is charged incident transfer demand as a placement proxy; those charges are not summed twice into elapsed time. Within 1% demand difference, span breaks the tie. Ordered packing plus dependency-tail priority biases cuts away from long dependent groups; it is not a throughput prediction. Measured simulator periods remain authoritative, including when a proxy improves. Core budgets come from stage allocation and bounded capacity recovery. Failed packing budgets are not lower bounds. [PEFT reservations](../../src/PIM/Dialect/Spatial/Passes/Transforms/MergeComputeNodes/Scheduling/PeftScheduler.cpp) prefer weight reuse when distinct weights exceed half the fleet's one-copy resident capacity; otherwise they favor communication/load. The reuse-first constructor supplies feasibility recovery without replacing a successful reservation, and can concentrate work. No exact combinatorial solver proves infeasibility after heuristic failure. [`ResidentWeightPacking.hpp`](../../src/PIM/Dialect/Spatial/Passes/Transforms/MergeComputeNodes/Scheduling/ResidentWeightPacking.hpp) adds at most 17 constructions after initial failure: one reverse traversal, then up to 16 single-choice replays sharing 65,536 candidate checks. Scratch buffers are reused; an extra `O(T)` failed-assignment snapshot is created only after both initial orders fail. Weight indexing still has `O(R W)` worst-case equality work; partition DP and latency insertion-gap searches remain superlinear as listed in the scheduling overview. Cached PEFT center distances avoid a topology scan per task/candidate; projection and priority analyses share/cache work rather than revisiting every template body per chunk. Ready-transfer signature searches remain another potential compile-time hotspot.