pub struct GemmUnroll {
pub ii: usize,
pub jj: usize,
pub kk: usize,
pub i: usize,
pub j: usize,
}Expand description
Emit a Spatial REGRESSION-HARNESS GEMM app for C[m×n] = A[m×k] · B[k×n] — the structure of Spatial’s own
regression/GEMM.scala (an output tile its × ts accumulated over k in ts-chunks by a MemReduce, an
ip-lane Reduce per dot product) with OUR shape and tiles, so a LOGOS matmul runs through the public
chain as the toolchain’s authors would write it. Host data are small residues so the 32-bit sums stay
exact and the harness’s PASS line means equality. Typed refusal when the tiles do not divide the shape
(the template streams whole tiles).
How wide each loop of the GEMM nest runs. Spatial’s own regression variants set exactly these, and a
factor left at 1 is a loop we run serially where the hand-written app runs it wide.
Fields§
§ii: usizeOuter tile loops over the output — rows and columns of tiles.
jj: usize§kk: usizeThe reduction over K-chunks.
i: usizeInner element loops within a tile — rows and columns.
j: usizeImplementations§
Source§impl GemmUnroll
impl GemmUnroll
Sourcepub const MAX_PER_LOOP: usize = 3
pub const MAX_PER_LOOP: usize = 3
The widest any SINGLE loop may be unrolled, and the largest TOTAL unroll across all of them.
Both are measured frontiers, not resource derivations, and the distinction between them matters. A 16-wide column loop at ts=32 does not merely stop paying: PIR emitted over eighteen thousand Tungsten contexts and made no progress in forty minutes on a program that otherwise compiles in two. But that is a PER-LOOP effect — widening one loop 16 ways instantiates sixteen reduces fed by one operand buffer — and it does not say a product of 16 spread across four loops is unplaceable. Spatial’s own variants confirm the split: they never widen a single loop past 3, and they freely combine three loops at 2 for a product of 8.
So the search is bounded per loop by what stalled, and in total by what has been demonstrated to COMPLETE on the chain — which is 4, measured from both sides:
- their
GEMM_10(loop_i=2 loop_j=2, product 4) simulates in minutes at 9,065 cycles; - a product of 8 does not. Our own emission at kk=2 i=2 j=2 made no progress in sixteen minutes,
the same signature as the 16-wide single loop, and their
GEMM_13(loop_ii=2 loop_jj=2, also product 4 but on the OUTER tile loops) stalled at twenty-five minutes — which is why the outer loops are ranked last: they replicate the whole tile pipeline rather than one reduce.
Raise it when the parity corpus shows a wider configuration completing and winning. Both numbers are evidence, not intuition, and neither is tuned to a shape.
pub const MAX_TOTAL_UNROLL: usize = 4
Sourcepub const PLACEABLE_UNROLL: usize = Self::MAX_TOTAL_UNROLL
pub const PLACEABLE_UNROLL: usize = Self::MAX_TOTAL_UNROLL
Kept as the single knob the older call sites pass; the total is what bounds a whole configuration.
Sourcepub fn search(
ts: usize,
its: usize,
k_chunks: usize,
budget: usize,
) -> GemmUnroll
pub fn search( ts: usize, its: usize, k_chunks: usize, budget: usize, ) -> GemmUnroll
Search the unroll space for the configuration that does the most parallel work per cycle within what the toolchain can place.
The space is small — each factor divides its own loop extent — so this enumerates rather than guesses. Two facts order the search, both measured on the vendor’s own simulator at 128^3, ts 64, its 64, ip 16, where the serial program costs 132,068 cycles:
- widening ANY single loop by 2 roughly halves the cycles —
loop_j66,587,loop_i66,713,loop_kk67,700 — so total parallel work is what the objective should maximise; - among equal products the column loop wins, because
jiterations are independent dot products over the SAMEa_sram, whileireloads that buffer per iteration andkkadds a partial-sum buffer per parallel chunk. The measured ordering j < i < kk is exactly that reasoning.
So: maximise the product subject to the placement frontier, break ties by that ordering. No shape is named anywhere — the same rule runs for every tiling, and a shape it has never seen gets the same treatment as one in the corpus.
Sourcepub fn search_nest(
ts: usize,
its: usize,
k_chunks: usize,
m_tiles: usize,
n_tiles: usize,
budget: usize,
) -> GemmUnroll
pub fn search_nest( ts: usize, its: usize, k_chunks: usize, m_tiles: usize, n_tiles: usize, budget: usize, ) -> GemmUnroll
The full five-loop search. m_tiles and n_tiles are how many output tiles the shape has in each
direction, which is what the outer ii / jj loops range over.
Trait Implementations§
Source§impl Clone for GemmUnroll
impl Clone for GemmUnroll
Source§fn clone(&self) -> GemmUnroll
fn clone(&self) -> GemmUnroll
1.0.0 · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Debug for GemmUnroll
impl Debug for GemmUnroll
Source§impl Default for GemmUnroll
impl Default for GemmUnroll
Source§impl PartialEq for GemmUnroll
impl PartialEq for GemmUnroll
impl Copy for GemmUnroll
impl Eq for GemmUnroll
impl StructuralPartialEq for GemmUnroll
Auto Trait Implementations§
impl Freeze for GemmUnroll
impl RefUnwindSafe for GemmUnroll
impl Send for GemmUnroll
impl Sync for GemmUnroll
impl Unpin for GemmUnroll
impl UnsafeUnpin for GemmUnroll
impl UnwindSafe for GemmUnroll
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can
then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be
further downcast into Rc<ConcreteType> where ConcreteType implements Trait.§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.§impl<T> DowncastSync for T
impl<T> DowncastSync for T
§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key and return true if they are equal.