Skip to content

Generalize MAC vectorization: either loop order, f16/f32/mixed dtypes, fold into --disable-dsd - #75

Closed
ycmath wants to merge 6 commits into
spcl:mainfrom
ycmath:nd-mac-vectorization
Closed

ycmath wants to merge 6 commits into
spcl:mainfrom
ycmath:nd-mac-vectorization

Conversation

@ycmath

@ycmath ycmath commented Sep 1, 2026

Copy link
Copy Markdown

Follow-up to #69 — first of the two stages proposed there. This covers asks (2), (3), and (5); arbitrary nesting depth (1) and generalizing the affine approach to any DSD operation (4) are left for a second PR, pending the increment_dsd relative-vs-absolute offset question in the issue thread.

Loop order (2). Loop-variable roles are now detected from the destination index's affine decomposition — the variable appearing as the bare destination index is the row variable, the other is the reduction variable — so both for (k, l) and for (l, k) declaration orders vectorize. The positional hardcoding is gone.

Dtypes (3). The f32 gate is replaced by a dispatch that mirrors FMADSDOp._as_csl exactly (sharing dsd_ops._get_base_dtype): @fmach, @fmachs, @fmacs for the same dtype combinations FMADSDOp models. Integer dtypes are intentionally out of scope — DSD_ASSIGNMENT_MAPPING carries no integer MAC builtin. Per the SDK language reference, @increment_dsd_offset's elem_type accepts any ABI-compatible numeric type including f16, so the offset-stepping side generalizes with it.

Flag (5). --disable-mac-vectorization is removed; the pass is now gated solely by --disable-dsd, as suggested. This changes nothing for existing --disable-dsd users (the pass was already AND-gated behind it). The negative test that isolated MAC vectorization from other DSD emission was rewritten against --disable-dsd.

Tests. test_mac_vectorization.py grows 6 → 20: positives across order × dtype, negatives for unsupported dtypes, and the existing aliasing / non-affine / accumulator-mismatch negatives replicated across both loop orders. Full suite: 395 passed / 12 skipped (baseline at eaf8d1c: 375 / 12 — zero regressions). f32 gemv codegen emits a byte-identical vectorized MAC block relative to the pre-change output.

🤖 Generated with Claude Code

wcy added 6 commits September 2, 2026 01:43
Ports p4_mac_vectorization.patch (issue spcl#69) onto eaf8d1c, which added
the Appliance Mode compiler (spcl#70) and refactored CLI options into a
shared `codegen_options` decorator used by both `compile_spatial_ir`
and the new `compile_spatial_ir_appliance`. The patch's CLI hunk no
longer applied cleanly (2/3 hunks rejected) because compiler.py's
option list and function signature moved; the lowering/statements.py
hunks applied without conflict.

Resolved by hand-adding --disable-mac-vectorization to the shared
codegen_options decorator and threading disable_mac_vectorization
through generate_program() and both CLI entry points
(compile_spatial_ir, compile_spatial_ir_appliance), matching how
--disable-dsd is already threaded through both.

Baseline (upstream HEAD, no P4): 375 passed, 12 skipped.
This commit: 381 passed, 12 skipped (0 regressions, +6 P4 tests).
Detect the row variable (k, appears alone with coefficient 1 in the
destination index) and the reduction variable (l, everything else)
from the destination index's affine decomposition instead of assuming
statement.variables[0]/[1] position. Accepts both
`for (k, l) in [...]` and `for (l, k) in [...]` declarations of the
same MAC loop; behavior for the existing (k, l) shape is unchanged
(same equality check on the affine decomposition, now checked against
either candidate name instead of a hardcoded position).

Verified manually with an (l, k)-order kernel: emits identical
@fmacs/@increment_dsd_offset output to the (k, l) case (ad hoc script,
not committed). Full suite: 381 passed, 12 skipped (unchanged from
previous commit).
Replace the ad hoc base_f32() gate with a dispatch mirroring
FMADSDOp._as_csl (dsd_ops.py) exactly, reusing dsd_ops._get_base_dtype
for dtype resolution instead of a hand-rolled base_type/element_type
lookup:

  - f16 matrix/vector operands, f16 accumulator -> @fmach
  - f32 matrix/vector operands, f32 accumulator -> @fmacs (unchanged)
  - f32 matrix/vector operands, f16 accumulator -> @fmachs (mixed)

The @increment_dsd_offset elem_type argument (previously hardcoded
`f32`) and the emitted op name are now both derived from the resolved
matrix/vector dtype and dst dtype respectively, via dtype_as_csl().

f16 multiplicands with an f32 accumulator, and all integer dtypes, are
intentionally NOT vectorized: FMADSDOp itself has no dispatch branch
for f16-mul/f32-accumulate, and DSD_ASSIGNMENT_MAPPING has no integer
MAC builtin at all (only @fmach/@fmachs/@fmacs map to FMADSDOp) --
these shapes fall through to the scalar loop, matching upstream's own
declared support surface rather than inventing new CSL builtins.

SDK verification for @increment_dsd_offset's elem_type parameter:
docker exec into cs_sdk_run was unavailable (daemon was down; once
started, the container/image itself is not present in this
environment -- it belongs to a separate measurement rig). Verified
instead against the official CSL language reference
(sdk.cerebras.ai/csl/language/dsds, current docs matching SDK 2.10.x):
"elem_type ... must be an ABI-compatible numeric type (u16, i16, u32,
i32, f16, or f32)" -- f16 is explicitly listed as valid.

Manually verified all 5 dtype combinations against the real lowering
pipeline (ad hoc script, not committed): all-f16 -> @fmach, all-f32 ->
@fmacs, f32-mul/f16-acc -> @fmachs, f16-mul/f32-acc -> correctly NOT
vectorized, all-i16 -> correctly NOT vectorized.

Full suite: 381 passed, 12 skipped (unchanged).
Per issue spcl#69 ask 5 (maintainer: "this generalizes DSD detection to ND
loops" -- no separate flag needed), remove --disable-mac-vectorization
entirely:

  - spada/cli/compiler.py: drop the click option, the
    generate_program()/compile_spatial_ir() parameter, and the
    pass-through call argument.
  - spada/cli/appliance_compiler.py: same, for
    compile_spatial_ir_appliance() (this entry point postdates P4 --
    it never had the flag to begin with, so this is purely keeping it
    in sync with compiler.py's shared codegen_options).
  - spada/lowering/spatial_ir_to_csl.py: drop
    disable_mac_vectorization from lower_spatial_ir_to_csl() and
    generate_rectangle(), and the `cslstmt.DISABLE_MAC_VECTORIZATION =
    True` assignment.
  - spada/syntax/csl/statements.py: drop the DISABLE_MAC_VECTORIZATION
    module global; emit_for's gate is now `if not
    dsd_ops.DISABLE_DSD:` only, with no second condition. Also
    rewrote the pass's header comment to reflect the current (order-
    and dtype-generalized, still 2-level-only) scope and record the
    @increment_dsd_offset f16 verification.
  - tests/spatial_ir/test_mac_vectorization.py: rewrote
    test_scalar_fallback_when_disabled to pass disable_dsd=True (was
    disable_mac_vectorization=True) and reset dsd_ops.DISABLE_DSD (was
    cslstmt.DISABLE_MAC_VECTORIZATION) in its cleanup -- both are
    sticky module-level globals, so the reset target had to change
    along with the flag.

Semantic note: this was already true in practice before this commit --
emit_for's old gate was `if not dsd_ops.DISABLE_DSD and not
DISABLE_MAC_VECTORIZATION`, so --disable-dsd already disabled MAC
vectorization too. What's removed is the ability to disable *only*
MAC vectorization while leaving other DSD ops enabled; no prior
behavior for --disable-dsd users changes.

Verified both CLI entry points still parse correctly
(`python -m spada.cli.compiler --help` /
`python -m spada.cli.appliance_compiler --help`) with no
--disable-mac-vectorization option and no crash. Full suite: 381
passed, 12 skipped (unchanged).
Extends tests/spatial_ir/test_mac_vectorization.py from 6 to 20 tests
to cover the (2) order and (3) dtype generalizations:

  - test_mac_loop_vectorized_for_order_and_dtype: positive matrix,
    {kl, lk} x {(f32,f32)->@fmacs, (f16,f16)->@fmach,
    (f32 mul,f16 acc)->@fmachs} = 6 cases.
  - test_no_vectorization_for_unsupported_dtype_combo: {kl, lk} x
    {(f16 mul, f32 acc) -- no FMADSDOp branch, (i16, i16) -- no
    integer @FMac* builtin} = 4 cases.
  - The four pre-existing negative tests (accumulator aliases matrix,
    accumulator aliases vector, non-affine index, accumulator differs
    from destination) are now parametrized over {kl, lk} = 8 cases,
    up from 4, to confirm the aliasing/affine/accumulator guards still
    apply correctly regardless of which position declares the row vs.
    reduction variable.

_kernel() gained mat_ty/out_ty parameters (defaulting to the original
all-f32 behavior) so stream types can vary with the tested dtype
combination -- receive/send would otherwise reject a dtype mismatch
before MAC vectorization is ever reached.

Armed per art.14 (verified by temporarily breaking the guard under
test and confirming the corresponding new cases go red, then
reverting):
  - Aliasing guard disabled -> all 4
    test_no_vectorization_when_accumulator_aliases_{matrix,vector}[kl|lk]
    cases fail as expected (vectorization no longer suppressed).
  - Dtype gate bypassed (falling back to @fmacs unconditionally) -> all
    4 test_no_vectorization_for_unsupported_dtype_combo[...] cases fail
    as expected.
Both reverted; suite is green again with the real guards in place.

Full suite: 395 passed, 12 skipped (baseline 375 + 20 in this file,
0 regressions).
Leftover line wrap from adding then removing disable_mac_vectorization
across the earlier P4-rebase/flag-removal commits; no functional
change. Rejoins compile_spatial_ir's signature to match upstream's
original formatting so the final diff against eaf8d1c is minimal.
@ycmath

ycmath commented Sep 13, 2026

Copy link
Copy Markdown
Author

Closing as a duplicate: #72 (opened earlier from a parallel workflow on my side) covers the same asks (2), (3), and (5). Sorry for the noise — #72 is the canonical one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant