SpaDA emits raw `[1]f32` refs where CSL requires DSD/DSR operands for async fabric operations
@tbennun is already working on this.
Since Sep 15, 2026.
- Dominant language
- Python
- Stars
- 10
- Forks
- 2
- Avg merge
- 1h 43m
- Merged PRs (30d)
- 1
Description
TL;DR
sptlc lowers any per-PE single-element buffer (e.g. a 1-wide halo cell f32[1]) into a bare [1]f32 identifier passed straight into @fmovs on a fabout_dsd / fabin_dsd. cslc rejects that: async fabric ops require DSD/DSR operands. Wrapping the buffer in a mem1d_dsd at emission time fixes it and produces bitwise-correct output. Repro below.
Environment
| Component | Value |
|---|---|
| SpaDA (this checkout) | 88e79fb3d123d7278f70625fdfdc39fc5a34f4d2 , 2026-04-20, main HEAD |
| Cerebras SDK | Cerebras-SDK-1.4.0 |
| SDK container image | sdk-cbcore-202505010205-2-ef181f81.sif (SHA256 9df94b2b…) |
cslc binary |
SHA256 e49dca08… |
cs_python binary |
SHA256 62ae6c68… |
| Python (SpaDA runtime) | ≥ 3.9 required (SpaDA source uses PEP 585 generic subscripting; 3.8 fails at import) |
| Python (in SDK container) | 3.8.16 (only affects cs_python) |
| CSL target arch | --arch=wse2 (SpaDA default per spada/syntax/csl/constants.py:ARCH). Bug also reproduces on --arch=wse3 with the identical cslc message. |
| OS (host tested) | Rocky Linux 8.10, kernel 4.18.0-553.53.1.el8_10.x86_64 |
How the CSL was generated
The reproducer bundle ships pre-generated CSL, but the CSL is produced by SpaDA's sptlc CLI (spada/cli/compiler.py, entrypoint sptlc from setup.py):
# Build spada and compile to CSL
pip install -e .
# Lower one .spada file to a folder of .csl + metadata.json (does not invoke cslc)
sptlc kernel.spada out/ --generate-only
sptlc internally runs the CSL lowering pipeline (spada/lowering/spatial_ir_to_csl.py:lower_spatial_ir_to_csl) with these default settings — none disabled in this repro:
disable_benchmarking = Falsedisable_asynchronous = Falsedisable_dsd = Falsedisable_map = Falsedisable_task_fusion = Falsedisable_copy_elision = Falsedisable_task_recycling = False
Without --generate-only it additionally invokes:
For the reproducer below, the equivalent explicit cslc invocation is:
cslc --arch=wse2 ./layout.csl \
--fabric-dims=5,2 --fabric-offsets=4,1 \
-o out --memcpy --channels=1
Input — minimal .spada
halo_p2.spada (1D 3-point stencil, per-PE tile width 2, halo width 1, over 2 PEs — the smallest kernel that declares a single-element f32[1] buffer and uses it in a send/receive):
kernel @stencil<>(stream<f32, 2>[2, 1] readonly in0_in,
stream<f32, 2>[2, 1] writeonly out0_out) {
place i16 x, i16 y in [0:2, 0] {
f32[2] in0
f32[1] in0_halo_lo
f32[1] in0_halo_hi
f32[2] out0
f32[1] in0_edge_hi
f32[1] in0_edge_lo
}
phase {
compute i16 x, i16 y in [0:2, 0] {
await receive(in0, in0_in[x, y])
}
}
phase {
dataflow i16 x, i16 y in [0:2, 0] {
stream<f32> in0_east = relative_stream(1, 0) { hops = [(1, 0)], channel = 0 }
stream<f32> in0_west = relative_stream(-1, 0) { hops = [(-1, 0)], channel = 1 }
}
compute i16 x, i16 y in [0, 0] {
in0_edge_hi[0] = in0[1]
in0_halo_lo[0] = 0.0
completion in0_send_east = send(in0_edge_hi, in0_east)
completion in0_recv_hi = receive(in0_halo_hi, in0_west)
await in0_send_east
await in0_recv_hi
}
compute i16 x, i16 y in [1, 0] {
in0_edge_lo[0] = in0[0]
in0_halo_hi[0] = 0.0
completion in0_send_west = send(in0_edge_lo, in0_west)
completion in0_recv_lo = receive(in0_halo_lo, in0_east)
await in0_send_west
await in0_recv_lo
}
}
phase {
compute i16 x, i16 y in [0:2, 0] {
out0[0] = ((in0_halo_lo[0] + in0[0]) + in0[1])
out0[1] = ((in0[0] + in0[1]) + in0_halo_hi[0])
await send(out0, out0_out[x, y])
}
}
}
Output SpaDA emits
sptlc halo_p2.spada out/ --generate-only produces :
var in0_halo_lo: [1]f32;
var in0_halo_hi: [1]f32;
var in0_edge_hi: [1]f32; // single-element buffers — no MemoryDSD registered
var in0_in: [2]f32;
var out0_out: [2]f32;
// Only the fabric DSDs are emitted. NO mem1d_dsd for in0_edge_hi / in0_halo_hi.
const in0_east__1_out_dsd = @get_dsd(fabout_dsd, .{ .extent = 1,
.fabric_color = in0_east__1_color_out, .output_queue = @get_output_queue(2) });
const in0_west__1_in_dsd = @get_dsd(fabin_dsd, .{ .extent = 1,
.fabric_color = in0_west__1_color_in, .input_queue = @get_input_queue(0) });
task task_slot_0() void {
in0_edge_hi[0] = in0_in[1];
in0_halo_lo[0] = 0.0;
@fmovs(in0_east__1_out_dsd, in0_edge_hi, .{ .async = true, .activate = task_1_id });
@fmovs(in0_halo_hi, in0_west__1_in_dsd, .{ .async = true, .unblock = task_1_id });
@activate(task_2_id);
}
The two async @fmovs at lines 34–35 pass in0_edge_hi and in0_halo_hi — raw [1]f32 identifiers — as the memory operand.
What cslc says
$ cslc --arch=wse2 ./layout.csl --fabric-dims=5,2 --fabric-offsets=4,1 -o out --memcpy --channels=1
./code_0_0.csl:34:5: error: only DSD/DSR operands are allowed for async operations
@fmovs(in0_east__1_out_dsd, in0_edge_hi, .{ .async = true, .activate = task_1_id });
^
./layout.csl:12:13: error: semantic error in module imported here
@set_tile_code(pe_x, pe_y, "code_0_0.csl", .{ .memcpy_params = memcpy.get_params(pe_x), });
^
The same defect surfaces with a different cslc message for a synchronous @fmovs — where pad_zero: [1]f32 is passed to @fmovs(pad_zero, 0.0);:
./code_1_0.csl:46:5: error: operand types do not match expectations
@fmovs(pad_zero, 0.0);
^
./code_1_0.csl:46:5: note: got type(s): [1]f32, comptime_float
./code_1_0.csl:46:5: note: expected type(s): DSD/DSR, DSD/DSR
./code_1_0.csl:46:5: note: expected type(s): DSD/DSR, f32
./code_1_0.csl:46:5: note: expected type(s): *f32, DSD/DSR
Same root cause: SpaDA emits the bare identifier where cslc requires a DSD.
What CSL actually needs (Ideal version made to pass the CSLC compiler stage successfully)
Adding a mem1d_dsd wrapper per single-element buffer and rewriting the @fmovs operands to use the wrapper name compiles cleanly:
const in0_edge_hi_dsd = @get_dsd(mem1d_dsd, .{ .tensor_access = |__index|{1} -> in0_edge_hi[__index] });
const in0_halo_hi_dsd = @get_dsd(mem1d_dsd, .{ .tensor_access = |__index|{1} -> in0_halo_hi[__index] });
@fmovs(in0_east__1_out_dsd, in0_edge_hi_dsd, .{ .async = true, .activate = task_1_id });
@fmovs(in0_halo_hi_dsd, in0_west__1_in_dsd, .{ .async = true, .unblock = task_1_id });
Result on the same cslc command line above:
[INFO] === Beginning compilation ===
[INFO] Compilation successful
And the compiled ELF passes the host-reference check bitwise:
$ cs_python check.py --kind stencil --elf-dir out --meta metadata.json
kernel=stencil kind=stencil grid=2x1 per_pe=2 N=4
launch...
d2h...
got [0:6] = [ 1. 4. 9. 13. 15. 9.]
expect[0:6] = [ 1. 4. 9. 13. 15. 9.]
BITWISE PASS: 4/4
Is this a SPADA bug or an unsupported feature?
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.