Skip to content

Commit 7e71179

Browse files
authored
Merge bc0e57e into 3ad2eb2
2 parents 3ad2eb2 + bc0e57e commit 7e71179

2 files changed

Lines changed: 115 additions & 16 deletions

File tree

iron/operators/repeat/design.py

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,32 @@
1212

1313

1414
def repeat(dev, dtype, rows, cols, repeat, transfer_size=None):
15+
elem_bytes = np.dtype(dtype).itemsize
1516
dtype = np.dtype[dtype]
1617

17-
# Try to work around hardware size limitations by breaking transfers into smaller chunks
18-
cols_split = 1
19-
if cols > 1023:
20-
for divisor in range(2, cols + 1):
21-
if cols % divisor == 0 and cols // divisor <= 1023:
22-
cols_split = divisor
23-
break
24-
else:
25-
raise ValueError(
26-
f"Cannot split cols={cols} into chunks <= 1023; hardware limits cols to not exceed 1023"
27-
)
28-
assert cols_split <= 1023, "cols is too large, can't split into smaller transfers"
18+
# Split cols into cols_split chunks of cols // cols_split. This is required to
19+
# satisfy hardware constraints on BD dimensions. We must choose a split that
20+
# does not exceed the hardware register sizes:
21+
# - the chunk length is the innermost dim: <= 1023 (10-bit wrap) AND a whole number
22+
# of 32-bit words, since the BD's innermost size is denominated in words
23+
# - the chunk count is the next dim out: <= 1023, the same wrap field
24+
# An odd cols has only odd divisors, so no split of it is ever word-aligned at bf16;
25+
# that is reported here rather than left to the BD verifier.
26+
granule = max(1, 4 // elem_bytes) # elements per 32-bit word
27+
cols_split = None
28+
for divisor in range(1, cols + 1):
29+
if cols % divisor:
30+
continue
31+
chunk = cols // divisor
32+
if chunk <= 1023 and divisor <= 1023 and chunk % granule == 0:
33+
cols_split = divisor
34+
break
35+
if cols_split is None:
36+
raise ValueError(
37+
f"Cannot split cols={cols} at {elem_bytes} bytes/element: need a divisor d "
38+
f"with cols//d <= 1023, d <= 1023, and cols//d a multiple of {granule} "
39+
f"({granule} elements = one 32-bit word). No divisor of {cols} satisfies all three."
40+
)
2941

3042
if transfer_size is None:
3143
transfer_size = cols
@@ -46,15 +58,20 @@ def repeat(dev, dtype, rows, cols, repeat, transfer_size=None):
4658
input_tap = TensorAccessPattern(
4759
tensor_dims=(rows, cols),
4860
offset=0,
49-
sizes=[repeat, rows, cols // cols_split, cols_split],
50-
strides=[0, cols, cols_split, 1],
61+
# The chunk LENGTH is innermost so the contiguous run is the innermost dim; the
62+
# chunk COUNT sits outside it. Swapping these two produces the same address
63+
# sequence, but putting the count innermost makes the unsplit case (cols_split
64+
# == 1) a 1-element innermost dim, which is not a whole 32-bit word for any
65+
# sub-word dtype and is rejected by the BD verifier.
66+
sizes=[repeat, rows, cols_split, cols // cols_split],
67+
strides=[0, cols, cols // cols_split, 1],
5168
)
5269

5370
output_tap = TensorAccessPattern(
5471
tensor_dims=(rows * repeat, cols),
5572
offset=0,
56-
sizes=[repeat, rows, cols // cols_split, cols_split],
57-
strides=[cols, cols * repeat, cols_split, 1],
73+
sizes=[repeat, rows, cols_split, cols // cols_split],
74+
strides=[cols, cols * repeat, cols // cols_split, 1],
5875
)
5976

6077
# Use smaller FIFOs for the transfer amount

iron/operators/repeat/test.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
#!/usr/bin/env python3
2+
# SPDX-FileCopyrightText: Copyright (C) 2026 Advanced Micro Devices, Inc. All rights reserved.
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
import pytest
6+
7+
from iron.operators.repeat.op import Repeat
8+
from iron.operators.repeat.reference import generate_golden_reference
9+
from iron.common.test_utils import run_test
10+
11+
12+
def get_params():
13+
# rows, cols, repeat, transfer_size.
14+
#
15+
# design.py splits cols into chunks <= 1023 by picking the smallest divisor that
16+
# gets under the hardware limit, so cols on either side of 1023 take different
17+
# paths and both need covering. The llama arm is the shape the only caller in the
18+
# tree actually dispatches: n_kv_groups=8 groups expanded to n_heads=32 over a
19+
# max_seq_len=2048 context of head_dim=64, i.e. repeat=4 with cols=2048*64.
20+
return [
21+
pytest.param(8, 64, 4, None),
22+
pytest.param(8, 512, 4, 64),
23+
pytest.param(4, 1024, 2, None),
24+
pytest.param(4, 2048, 2, None, marks=[pytest.mark.extensive]),
25+
pytest.param(8, 2048 * 64, 4, 64, marks=[pytest.mark.extensive]),
26+
]
27+
28+
29+
@pytest.mark.metrics(
30+
Latency=r"Latency \(us\): (?P<value>[\d\.]+)",
31+
Bandwidth=r"Effective Bandwidth: (?P<value>[\d\.e\+-]+) GB/s",
32+
)
33+
@pytest.mark.parametrize("rows,cols,repeat,transfer_size", get_params())
34+
def test_repeat(rows, cols, repeat, transfer_size, aie_context):
35+
"""Repeat moves data and computes nothing, so the gate is exact equality.
36+
37+
A tolerance gate would accept a permutation that reads the wrong group -- which
38+
is the whole failure mode here, since the only caller uses this to expand KV
39+
groups to attention heads and a misrouted group is numerically plausible.
40+
"""
41+
golden_ref = generate_golden_reference(rows=rows, cols=cols, repeat=repeat)
42+
43+
operator = Repeat(
44+
rows=rows,
45+
cols=cols,
46+
repeat=repeat,
47+
transfer_size=transfer_size,
48+
context=aie_context,
49+
)
50+
51+
errors, latency_us, bandwidth_gbps = run_test(
52+
operator,
53+
{"input": golden_ref["input"]},
54+
{"output": golden_ref["output"]},
55+
rel_tol=0.0,
56+
abs_tol=0.0,
57+
)
58+
59+
print(f"\nLatency (us): {latency_us:.1f}")
60+
print(f"Effective Bandwidth: {bandwidth_gbps:.6e} GB/s\n")
61+
62+
assert not errors, f"Test failed with errors: {errors}"
63+
64+
65+
@pytest.mark.parametrize(
66+
"cols,why",
67+
[
68+
(513, "odd: every divisor is odd, so no chunk is a whole 32-bit word"),
69+
(1031, "prime > 1023: the only divisors are 1 and cols, neither legal"),
70+
(2062, "2 x 1031: the only word-aligned chunk leaves a 1031-wide chunk count"),
71+
],
72+
)
73+
def test_cols_without_a_legal_split_is_rejected(cols, why, aie_context):
74+
"""A split has to satisfy the innermost dim AND the dim holding the chunk count.
75+
76+
Both land on a 10-bit wrap field, and the innermost is denominated in 32-bit words,
77+
so bounding the chunk length alone lets through taps the BD verifier then rejects
78+
with a much less legible error.
79+
"""
80+
operator = Repeat(rows=8, cols=cols, repeat=4, context=aie_context)
81+
with pytest.raises(ValueError, match="Cannot split cols"):
82+
operator.compile()

0 commit comments

Comments
 (0)