Summary
Compute blocks of the form
for i16 k, i16 l in [0:K, 0:K] {
z[k] = z[k] + A_flat[k*K + l] * x[l]
}
(as written in samples/spatial/blas/gemv.sptl, phase 4) currently lower to scalar
nested for (@range(...)) loops. The inner product therefore runs one element per
instruction, and its cost grows as K² while a hand-written CSL kernel using
@fmacs over strided DSDs stays nearly flat. We measured the crossover directly:
at K=16 the generated gemv becomes ~2× slower than a hand-written reference
(gemv-collectives_2d from Cerebras' sdk-examples), and ~4.6× slower at K=32.
We wrote a small, conservative lowering that turns this exact pattern into the
hand-written idiom and would like to contribute it, if there is interest.
Why a compiler change (and not a source-level rewrite)
We first tried to express the vectorizable form at the language level and found
both routes closed:
await map i16 k in [0:K:1] { z[k] = z[k] + A_flat[(k*K) + l] * x[l] } is
rejected by _dsd_from_array ("Expected one index variable in array access") —
the second loop variable l cannot appear in the access.
- A slice assignment
z[0:K:1] = z[0:K:1] + A_flat[l:K*K:K] * x[l] is rejected
by the surface grammar (UnexpectedCharacters: No terminal matches ':'),
although ArraySlice supports strides at the IR level.
So users cannot opt into the fast form from .sptl today.
Proposed lowering
In emit_for, detect the shape
for (k, l) in [0:Kk, 0:Kl]: Z[k] = Z[k] + A[k*Ck + l*Cl + C0] * X[f(l)]
and emit the same idiom hand-written kernels use (cf. gemv-collectives_2d):
const __vecmac_dst = @get_dsd(mem1d_dsd, .{ .tensor_access = |i|{Kk} -> Z[i] });
const __vecmac_src = @get_dsd(mem1d_dsd, .{ .tensor_access = |i|{Kk} -> A[i * Ck] });
for (@range(i16, 0, Kl, 1)) |l| {
const __vecmac_a = @increment_dsd_offset(__vecmac_src, l * Cl, f32);
@fmacs(__vecmac_dst, __vecmac_dst, __vecmac_a, X[f(l)]);
}
Safety/conservatism:
- fires only on the exact affine MAC shape above, f32 only, ranges starting at 0
with step 1; anything else falls back to the existing scalar loops;
- aliasing guard: not applied when
Z is also read as A or X (the DSD op
reorders reads relative to the sequential scalar loop);
- respects
--disable-dsd, plus a new --disable-mac-vectorization flag wired
the same way as the existing disable flags;
@increment_dsd_offset and @fmacs are both available in the SDK line this
repo targets: @fmacs is already emitted by dsd_ops.py, and
@increment_dsd_offset is used by Cerebras' own csl-examples at tag
v1.4.0 (benchmarks/cholesky/pe.csl, lines 113–119).
Measurements
Setup: WSE-2 simfabric (cycle counts are deterministic; every run verified
numerically against a NumPy reference), 4×4 PE grid, gemv.sptl with
PX=PY=4, fp32. Baseline is the unmodified scalar lowering; "hand-written" is
Cerebras' gemv-collectives_2d benchmark instrumented with the same
<time> timestamps the SPADA runtime uses. Kernel-window cycles
(max(stop) − min(start) across PEs):
| K |
scalar lowering |
vectorized |
hand-written |
speedup (vec vs scalar) |
| 4 |
490 |
579 |
3,127 |
0.85× |
| 8 |
1,521 |
809 |
3,378 |
1.88× |
| 16 |
8,357 |
1,851 |
4,090 |
4.51× |
| 32 |
28,955 |
3,744 |
6,274 |
7.73× |
The scalar lowering crosses above the hand-written kernel at K=16 and is 4.6×
slower by K=32; the vectorized lowering stays below it at every size we
measured. At K=4 the DSD setup constant makes vectorization slightly slower
(0.85×) — if that matters, a size threshold could gate the transform, but we
left the behavior unconditional and are flagging it here instead.
Caveats we want to be upfront about: measurements were taken on the SDK 2.10
simulator (with a small local compatibility layer for 2.10's CSL dialect; the
patch itself is written against current main and does not include that
layer). On unmodified main we verified that the transform fires and the
emitted code retains the SDK 1.4 dialect, and that the full
tests/spatial_ir + tests/stencil_ir suites pass (323 passed / 12 skipped),
plus 6 new unit tests covering the positive case, both aliasing cases, a
non-affine index, a mismatched accumulator, and the disable flag. We have not
run an end-to-end SDK 1.4 simulation.
Offer
The change is ~180 lines of compiler code plus ~110 lines of tests
(syntax/csl/statements.py matcher + emitter, flag plumbing in
lowering/spatial_ir_to_csl.py and cli/compiler.py,
tests/spatial_ir/test_mac_vectorization.py). Happy to open a PR if this is
welcome, and to adjust scope (e.g., f16 via @fmach, a size threshold, or
folding the flag into an existing one) to your preference.
Summary
Compute blocks of the form
(as written in
samples/spatial/blas/gemv.sptl, phase 4) currently lower to scalarnested
for (@range(...))loops. The inner product therefore runs one element perinstruction, and its cost grows as K² while a hand-written CSL kernel using
@fmacsover strided DSDs stays nearly flat. We measured the crossover directly:at K=16 the generated gemv becomes ~2× slower than a hand-written reference
(
gemv-collectives_2dfrom Cerebras' sdk-examples), and ~4.6× slower at K=32.We wrote a small, conservative lowering that turns this exact pattern into the
hand-written idiom and would like to contribute it, if there is interest.
Why a compiler change (and not a source-level rewrite)
We first tried to express the vectorizable form at the language level and found
both routes closed:
await map i16 k in [0:K:1] { z[k] = z[k] + A_flat[(k*K) + l] * x[l] }isrejected by
_dsd_from_array("Expected one index variable in array access") —the second loop variable
lcannot appear in the access.z[0:K:1] = z[0:K:1] + A_flat[l:K*K:K] * x[l]is rejectedby the surface grammar (
UnexpectedCharacters: No terminal matches ':'),although
ArraySlicesupports strides at the IR level.So users cannot opt into the fast form from
.sptltoday.Proposed lowering
In
emit_for, detect the shapeand emit the same idiom hand-written kernels use (cf.
gemv-collectives_2d):Safety/conservatism:
with step 1; anything else falls back to the existing scalar loops;
Zis also read asAorX(the DSD opreorders reads relative to the sequential scalar loop);
--disable-dsd, plus a new--disable-mac-vectorizationflag wiredthe same way as the existing disable flags;
@increment_dsd_offsetand@fmacsare both available in the SDK line thisrepo targets:
@fmacsis already emitted bydsd_ops.py, and@increment_dsd_offsetis used by Cerebras' owncsl-examplesat tagv1.4.0(benchmarks/cholesky/pe.csl, lines 113–119).Measurements
Setup: WSE-2
simfabric(cycle counts are deterministic; every run verifiednumerically against a NumPy reference), 4×4 PE grid,
gemv.sptlwithPX=PY=4, fp32. Baseline is the unmodified scalar lowering; "hand-written" isCerebras'
gemv-collectives_2dbenchmark instrumented with the same<time>timestamps the SPADA runtime uses. Kernel-window cycles(
max(stop) − min(start)across PEs):The scalar lowering crosses above the hand-written kernel at K=16 and is 4.6×
slower by K=32; the vectorized lowering stays below it at every size we
measured. At K=4 the DSD setup constant makes vectorization slightly slower
(0.85×) — if that matters, a size threshold could gate the transform, but we
left the behavior unconditional and are flagging it here instead.
Caveats we want to be upfront about: measurements were taken on the SDK 2.10
simulator (with a small local compatibility layer for 2.10's CSL dialect; the
patch itself is written against current
mainand does not include thatlayer). On unmodified
mainwe verified that the transform fires and theemitted code retains the SDK 1.4 dialect, and that the full
tests/spatial_ir+tests/stencil_irsuites pass (323 passed / 12 skipped),plus 6 new unit tests covering the positive case, both aliasing cases, a
non-affine index, a mismatched accumulator, and the disable flag. We have not
run an end-to-end SDK 1.4 simulation.
Offer
The change is ~180 lines of compiler code plus ~110 lines of tests
(
syntax/csl/statements.pymatcher + emitter, flag plumbing inlowering/spatial_ir_to_csl.pyandcli/compiler.py,tests/spatial_ir/test_mac_vectorization.py). Happy to open a PR if this iswelcome, and to adjust scope (e.g., f16 via
@fmach, a size threshold, orfolding the flag into an existing one) to your preference.