Skip to main content

GemmUnroll

Struct GemmUnroll 

Source
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: usize

Outer tile loops over the output — rows and columns of tiles.

§jj: usize§kk: usize

The reduction over K-chunks.

§i: usize

Inner element loops within a tile — rows and columns.

§j: usize

Implementations§

Source§

impl GemmUnroll

Source

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.

Source

pub const MAX_TOTAL_UNROLL: usize = 4

Source

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.

Source

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_j 66,587, loop_i 66,713, loop_kk 67,700 — so total parallel work is what the objective should maximise;
  • among equal products the column loop wins, because j iterations are independent dot products over the SAME a_sram, while i reloads that buffer per iteration and kk adds 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.

Source

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.

Source

pub fn choose(ts: usize, its: usize, ip: usize) -> GemmUnroll

The compiler’s choice for a tiling when the tile counts are not known to the caller.

Trait Implementations§

Source§

impl Clone for GemmUnroll

Source§

fn clone(&self) -> GemmUnroll

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for GemmUnroll

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for GemmUnroll

Source§

fn default() -> Self

Serial everywhere. The innermost dot is always par ip, which is a separate argument.

Source§

impl PartialEq for GemmUnroll

Source§

fn eq(&self, other: &GemmUnroll) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl Copy for GemmUnroll

Source§

impl Eq for GemmUnroll

Source§

impl StructuralPartialEq for GemmUnroll

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
§

impl<T> Downcast for T
where T: Any,

§

fn into_any(self: Box<T>) -> Box<dyn Any>

Convert 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>

Convert 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)

Convert &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)

Convert &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
where T: Any + Send + Sync,

§

fn into_any_arc(self: Arc<T>) -> Arc<dyn Any + Send + Sync>

Convert Arc<Trait> (where Trait: Downcast) to Arc<Any>. Arc<Any> can then be further downcast into Arc<ConcreteType> where ConcreteType implements Trait.
§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,