Operators¶
The operator library. Each operator is constructed by keyword
(GEMV(M=..., K=...)); outside a graph, op(*tensors) compiles once and
runs on the NPU, and inside a graph's body() calling it records a step.
iron.operators.CATALOG holds every operator by the name a profile gives it.
The IRON operator library.
AXPY
¶
AXPY(*, num_aie_columns: int = None, num_channels: int = None, tile_size: int = None, size: int, scalar_factor: float = 3.0)
Bases: BinaryElementwise
AIE-accelerated aX + Y operator.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
num_aie_columns
|
int
|
Tunable: the call's, else the profile's, else |
None
|
num_channels
|
int
|
Tunable: the call's, else the profile's, else |
None
|
tile_size
|
int
|
Tunable: the call's, else the profile's, else |
None
|
size
|
int
|
|
required |
scalar_factor
|
float
|
|
3.0
|
Attributes:
| Name | Type | Description |
|---|---|---|
count |
Per call. |
|
valid |
Per call. |
|
a |
|
|
b |
|
|
y |
|
Source code in iron/operators/axpy.py
Clamp
¶
Clamp(*, num_aie_columns: int = None, num_channels: int = None, tile_size: int = None, size: int, low: float, high: float)
Bases: UnaryElementwise
x clamped to [low, high], elementwise, the bounds rounded to bf16.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
num_aie_columns
|
int
|
Tunable: the call's, else the profile's, else |
None
|
num_channels
|
int
|
Tunable: the call's, else the profile's, else |
None
|
tile_size
|
int
|
Tunable: the call's, else the profile's, else |
None
|
size
|
int
|
|
required |
low
|
float
|
|
required |
high
|
float
|
|
required |
Attributes:
| Name | Type | Description |
|---|---|---|
count |
Per call. |
|
valid |
Per call. |
|
x |
|
|
y |
|
|
low_bits |
Per call. |
|
high_bits |
Per call. |
Source code in iron/operators/clamp.py
Copy
¶
Copy(*, input_buffer_size: int, src: TensorAccessPattern | tuple[TensorAccessPattern, ...] = lambda op: TensorAccessPattern.full((op.input_buffer_size,)), output_buffer_size: int = lambda op: sum(prod(p.sizes) for p in ((op.dst,) if op.src_repeat else op.src if isinstance(op.src, tuple) else (op.src,))), dst: TensorAccessPattern | tuple[TensorAccessPattern, ...] = lambda op: TensorAccessPattern.full((op.output_buffer_size // op.src.sizes[-1], op.src.sizes[-1]) if isinstance(op.src, TensorAccessPattern) else (op.output_buffer_size // op.src[0].sizes[-1], op.src[0].sizes[-1])), src_bound: int | None = None, dst_bound: int | None = None, src_repeat: bool = False, tile_size: int = None, num_channels: int = None, dtype: Any = bfloat16)
Bases: Operator
A copy between two views, gathered by src and scattered by dst.
Through num_channels memtile pass-throughs, split on the innermost
run every pattern holds whole. A tile_size short of a descriptor
never completes it, and the drain hangs (ERT_CMD_STATE_TIMEOUT).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
input_buffer_size
|
int
|
|
required |
src
|
TensorAccessPattern | tuple[TensorAccessPattern, ...]
|
|
lambda op: TensorAccessPattern.full((op.input_buffer_size,))
|
output_buffer_size
|
int
|
A repeated source's walk is its capacity: the destination's rows are the copy's. |
lambda op: sum(prod(p.sizes) for p in ((op.dst,) if op.src_repeat else op.src if isinstance(op.src, tuple) else (op.src,)))
|
dst
|
TensorAccessPattern | tuple[TensorAccessPattern, ...]
|
Rows as wide as the source's innermost axis: the channels split both alike. |
lambda op: TensorAccessPattern.full((op.output_buffer_size // op.src.sizes[-1], op.src.sizes[-1]) if isinstance(op.src, TensorAccessPattern) else (op.output_buffer_size // op.src[0].sizes[-1], op.src[0].sizes[-1]))
|
src_bound
|
int | None
|
The axis of each pattern a graph bounds per call. |
None
|
dst_bound
|
int | None
|
|
None
|
src_repeat
|
bool
|
The source's first axis runs a per-call count of times (Handle.at's repeat). |
False
|
tile_size
|
int
|
Tunable: the call's, else the profile's, else |
None
|
num_channels
|
int
|
Tunable: the call's, else the profile's, else |
None
|
dtype
|
Any
|
|
bfloat16
|
Attributes:
| Name | Type | Description |
|---|---|---|
x |
|
|
y |
|
|
in_offset |
Per call. In elements. |
|
out_offset |
Per call. |
|
src_valid |
Per call. |
|
dst_valid |
Per call. |
|
src_repeats0 |
Per call. Each walk's runs past the first, as a task queue's repeat count counts them. |
|
src_repeats1 |
Per call. |
|
src_repeats2 |
Per call. |
|
src_repeats3 |
Per call. |
|
src_repeats4 |
Per call. |
|
src_repeats5 |
Per call. |
|
src_repeats6 |
Per call. |
|
src_repeats7 |
Per call. |
Source code in iron/operators/copy.py
walks
¶
walks() -> list[tuple[TensorAccessPattern, ...]]
Return the patterns each side walks in turn, [src, dst].
reference
¶
reference(x, y=None, *, in_offset=0, out_offset=0, src_valid=None, dst_valid=None, src_repeats0=None, src_repeats1=None, src_repeats2=None, src_repeats3=None, src_repeats4=None, src_repeats5=None, src_repeats6=None, src_repeats7=None)
Gather by src, scatter by dst, into y in place if given.
Source code in iron/operators/copy.py
Gather
¶
Gather(*, rows: int, table_rows: int, row: int, dtype: Any = bfloat16, words: int = lambda op: 20 * op.chain + 6 * op.rows + 3 * len(op.batches) + (3 if op.rows % op.chain else 0), feeds: int = None)
Bases: Operator
Copy(table[ids]) with ids per call: the rows a call names.
Moved through feeds pairs of shims; a row past a bound moves
pad_bytes instead, a descriptor's minimum 16-byte unit.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rows
|
int
|
|
required |
table_rows
|
int
|
|
required |
row
|
int
|
|
required |
dtype
|
Any
|
|
bfloat16
|
words
|
int
|
Derived where not given; a given value must equal it. |
lambda op: 20 * op.chain + 6 * op.rows + 3 * len(op.batches) + (3 if op.rows % op.chain else 0)
|
feeds
|
int
|
Tunable: the call's, else the profile's, else |
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
table |
|
|
control |
|
|
y |
|
|
valid_rows |
Per call. |
|
drained0 |
Per call. Per feed, the 16-byte units it drains under a bound. |
|
drained1 |
Per call. |
|
drained2 |
Per call. |
|
drained3 |
Per call. |
Source code in iron/operators/copy.py
template
cached
property
¶
Return the control words with every row's address zero.
And where each row's address-low word is (its length one before, its high word two after).
drained
¶
Return the 16-byte units feed feed drains.
With valid_rows rows valid.
Source code in iron/operators/copy.py
word_source
¶
word_source() -> GatherWords
Return the step writing row addresses into the template.
For ids the device made.
Source code in iron/operators/copy.py
control_words
¶
Compute the control words gathering ids from a table at address.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ids
|
Sequence[int] | ndarray
|
Each row to gather, by table row index. |
required |
address
|
int
|
The table's device address. |
required |
valid
|
int | None
|
Rows past it move |
None
|
Raises:
| Type | Description |
|---|---|
IndexError
|
An id is past the table, either way, or |
Source code in iron/operators/copy.py
GatherWords
¶
GatherWords(*, rows: int, table_rows: int, row_bytes: int, words: int, pairs: int = lambda op: 2 * op.rows, bounded: bool = False, record: int = lambda op: op.pairs + (op.rows if op.bounded else 0))
Bases: Operator
A Gather's control words for ids the device made, in a graph.
Each id, clipped to the table, becomes its row's address words, split at
low_bits. Where bounded, it also writes each row's length, capped
past valid_rows.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rows
|
int
|
|
required |
table_rows
|
int
|
|
required |
row_bytes
|
int
|
|
required |
words
|
int
|
|
required |
pairs
|
int
|
Derived where not given; a given value must equal it. |
lambda op: 2 * op.rows
|
bounded
|
bool
|
|
False
|
record
|
int
|
Derived where not given; a given value must equal it. The words a call writes: the address pairs, then if bounded the lengths. |
lambda op: op.pairs + (op.rows if op.bounded else 0)
|
Attributes:
| Name | Type | Description |
|---|---|---|
ids |
|
|
control |
|
|
base_lo |
Per call. |
|
base_hi |
Per call. |
|
valid_rows |
Per call. |
|
valid |
Per call. |
Source code in iron/operators/copy.py
address_words
¶
base_lo and base_hi for a table at address.
taps
¶
taps() -> list[TensorAccessPattern]
Return where the rows' address words go in control, in row order.
Then if bounded their lengths, the word before each address.
Source code in iron/operators/copy.py
DepthwiseConv1d
¶
Bases: Operator
A causal depthwise convolution over time, five taps, channels last.
One core per column takes a run of rows, its input fifo the sliding window; every run but the first is filled from four rows before it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rows
|
int
|
|
required |
channels
|
int
|
|
required |
num_aie_columns
|
int
|
Tunable: the call's, else the profile's, else |
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
x |
|
|
weight |
|
|
y |
|
|
valid |
Per call. Every row is still computed under a bound: a row reads only those before it. |
|
steps |
Per call. |
Source code in iron/operators/depthwise_conv1d.py
sequence
¶
Write the weights to every core, then each run with the four rows before it.
Source code in iron/operators/depthwise_conv1d.py
ElementwiseAdd
¶
ElementwiseAdd(*, num_aie_columns: int = None, num_channels: int = None, tile_size: int = None, size: int)
Bases: BinaryElementwise
AIE-accelerated element-wise addition.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
num_aie_columns
|
int
|
Tunable: the call's, else the profile's, else |
None
|
num_channels
|
int
|
Tunable: the call's, else the profile's, else |
None
|
tile_size
|
int
|
Tunable: the call's, else the profile's, else |
None
|
size
|
int
|
|
required |
Attributes:
| Name | Type | Description |
|---|---|---|
count |
Per call. |
|
valid |
Per call. |
|
a |
|
|
b |
|
|
y |
|
Source code in iron/operators/elementwise_add.py
RowwiseAdd
¶
Bases: RowBroadcast
Each row plus one row, x + row: a projection's bias.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
num_aie_columns
|
int
|
Tunable: the call's, else the profile's, else |
None
|
num_channels
|
int
|
Tunable: the call's, else the profile's, else |
None
|
tile_size
|
int
|
|
required |
rows
|
int
|
|
required |
Attributes:
| Name | Type | Description |
|---|---|---|
count |
Per call. |
|
valid |
Per call. |
|
x |
|
|
y |
|
|
row |
|
Source code in iron/operators/elementwise_add.py
ElementwiseMul
¶
ElementwiseMul(*, num_aie_columns: int = None, num_channels: int = None, tile_size: int = None, size: int)
Bases: BinaryElementwise
AIE-accelerated element-wise multiplication.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
num_aie_columns
|
int
|
Tunable: the call's, else the profile's, else |
None
|
num_channels
|
int
|
Tunable: the call's, else the profile's, else |
None
|
tile_size
|
int
|
Tunable: the call's, else the profile's, else |
None
|
size
|
int
|
|
required |
Attributes:
| Name | Type | Description |
|---|---|---|
count |
Per call. |
|
valid |
Per call. |
|
a |
|
|
b |
|
|
y |
|
Source code in iron/operators/elementwise_mul.py
RowwiseMul
¶
Bases: RowBroadcast
Each row times one row, x * row.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
num_aie_columns
|
int
|
Tunable: the call's, else the profile's, else |
None
|
num_channels
|
int
|
Tunable: the call's, else the profile's, else |
None
|
tile_size
|
int
|
|
required |
rows
|
int
|
|
required |
Attributes:
| Name | Type | Description |
|---|---|---|
count |
Per call. |
|
valid |
Per call. |
|
x |
|
|
y |
|
|
row |
|
Source code in iron/operators/elementwise_mul.py
Emit
¶
Bases: Operator
Evaluate an emit program: the next call's scratchpad image and state.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
slots
|
int
|
|
required |
carried
|
int
|
|
required |
rows
|
int
|
Derived where not given; a given value must equal it. |
lambda op: op.slots + op.carried
|
Attributes:
| Name | Type | Description |
|---|---|---|
program |
|
|
planes |
|
|
image |
|
|
state |
|
Source code in iron/operators/emit.py
GELU
¶
GELU(*, num_aie_columns: int = None, num_channels: int = None, tile_size: int = None, size: int, approximate: str = 'tanh')
Bases: UnaryElementwise
AIE-accelerated GELU.
By its tanh approximation, or exact (approximate="none",
x * Phi(x)), as torch's approximate.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
num_aie_columns
|
int
|
Tunable: the call's, else the profile's, else |
None
|
num_channels
|
int
|
Tunable: the call's, else the profile's, else |
None
|
tile_size
|
int
|
Tunable: the call's, else the profile's, else |
None
|
size
|
int
|
|
required |
approximate
|
str
|
|
'tanh'
|
Attributes:
| Name | Type | Description |
|---|---|---|
count |
Per call. |
|
valid |
Per call. |
|
x |
|
|
y |
|
Source code in iron/operators/gelu.py
GEMM
¶
GEMM(*, M: int, K: int, N: int, tile_m: int = None, tile_k: int = None, tile_n: int = None, num_aie_columns: int = None, placement: GEMMPlacement = None, b_col_maj: bool = False, c_col_maj: bool = False, precision: Precision, input_dtype: Any = bfloat16, output_dtype: Any = bfloat16)
Bases: Operator
C = A @ B, tiled m x k x n.
A broadcasts across columns and splits across cores' rows, B splits
across columns and broadcasts across rows, C joins across rows and
splits across columns. Used wherever flm.ProjectionGEMM doesn't
apply (a weight operand, MEDIUM/HIGH, two or more rows).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
M
|
int
|
|
required |
K
|
int
|
|
required |
N
|
int
|
|
required |
tile_m
|
int
|
Tunable: the call's, else the profile's, else |
None
|
tile_k
|
int
|
Tunable: the call's, else the profile's, else |
None
|
tile_n
|
int
|
Tunable: the call's, else the profile's, else |
None
|
num_aie_columns
|
int
|
Tunable: the call's, else the profile's, else |
None
|
placement
|
GEMMPlacement
|
Tunable: the call's, else the profile's, else |
None
|
b_col_maj
|
bool
|
|
False
|
c_col_maj
|
bool
|
|
False
|
precision
|
Precision
|
|
required |
input_dtype
|
Any
|
|
bfloat16
|
output_dtype
|
Any
|
|
bfloat16
|
Attributes:
| Name | Type | Description |
|---|---|---|
A |
|
|
B |
|
|
C |
|
|
valid |
Per call. A bound limits the compute, not the DMAs: A's pattern fills all four BD dims. |
|
valid_k |
Per call. The last K tile zeroes A's columns and B's rows past the bound. |
|
k_div_k |
Per call. Reduction steps per output tile, and output tiles per core. |
|
n_tiles |
Per call. |
|
row_blocks_valid |
Per call. Under a bound: the row blocks it covers, and the column tiles of each. |
|
col_tiles |
Per call. |
|
k_valid |
Per call. |
Source code in iron/operators/gemm.py
wider_accumulator
property
¶
wider_accumulator: bool
Check whether build() and GEMM.held() build HIGHEST's f32 buffer.
For a bf16 C's partial sums, beside C's own.
mac_block
¶
Return what tile_m, tile_k and tile_n must each be a multiple of.
mm.cc's r x s x t blocks, two in m and n (four in m on aie2).
dev=None assumes aie2p's geometry.
Source code in iron/operators/gemm.py
held
¶
Core memory a core of dev holds at tile_m.
Source code in iron/operators/gemm.py
matmul
¶
Return the m x k x n kernel each core runs.
Source code in iron/operators/gemm.py
matmul_calls
¶
Return the fewest matmul kernel calls the busiest core makes in a step.
At per-call values: each output tile under the bound takes one
call per K tile under it.
Source code in iron/operators/gemm.py
reference
¶
Compute A @ B over the first valid_k of K, a bounded call's.
Source code in iron/operators/gemm.py
tolerance
¶
tolerance() -> Tolerance
Return C's roundings, in units of 2^-8 of what each rounds.
2 of |A| @ |B| for C's own, 2 per K tile's partial sum but the
last when C accumulates in bf16, and 2 more for bfp16 inputs;
measured at no more than 0.83 of this over K = 256-8192 on npu2.
Source code in iron/operators/gemm.py
GEMV
¶
GEMV(*, M: int, K: int, num_batches: int | None = None, repeat: int = 1, num_matrices: int | None = lambda op: None if op.num_batches is None else op.num_batches // op.repeat, num_aie_columns: int = None, num_channels: int = None, tile_size_input: int = None, tile_size_output: int = None, kernel_vector_size: int = None)
Bases: Operator
C = A @ B as row-blocks of A per lane, tile_size_input rows per mv.cc call.
B rides each lane's A stream ahead of its rows so every input channel of a column can carry A.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
M
|
int
|
|
required |
K
|
int
|
|
required |
num_batches
|
int | None
|
None: a single batch, with no batch dimension. |
None
|
repeat
|
int
|
Batches per matrix of A (grouped-query scores): no repeated copy. |
1
|
num_matrices
|
int | None
|
Derived where not given; a given value must equal it. |
lambda op: None if op.num_batches is None else op.num_batches // op.repeat
|
num_aie_columns
|
int
|
Tunable: the call's, else the profile's, else |
None
|
num_channels
|
int
|
Tunable: the call's, else the profile's, else |
None
|
tile_size_input
|
int
|
Tunable: the call's, else the profile's, else |
None
|
tile_size_output
|
int
|
Tunable: the call's, else the profile's, else |
None
|
kernel_vector_size
|
int
|
Tunable: the call's, else the profile's, else |
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
A |
|
|
B |
|
|
C |
|
|
valid |
Per call. |
|
tiles |
Per call. |
|
batches |
Per call. |
Source code in iron/operators/gemv.py
sequence
¶
Sequence per batch and lane: B's head tile, then A's rows.
C drains are issued first: an undrained C would stop A.
Source code in iron/operators/gemv.py
328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 | |
reference
¶
C = A @ B, one product per batch.
Source code in iron/operators/gemv.py
LayerNorm
¶
LayerNorm(*, num_aie_columns: int = None, num_channels: int = None, tile_size: int, rows: int, weighted: bool = False, epsilon: float = 1e-05)
Bases: WeightedRowwise
AIE-accelerated Layer Normalization of each row (beta 0), optionally times a learned weight row (gamma).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
num_aie_columns
|
int
|
Tunable: the call's, else the profile's, else |
None
|
num_channels
|
int
|
Tunable: the call's, else the profile's, else |
None
|
tile_size
|
int
|
|
required |
rows
|
int
|
|
required |
weighted
|
bool
|
|
False
|
epsilon
|
float
|
The epsilon under the root: torch's 1e-5 by default; a model states its own. |
1e-05
|
Attributes:
| Name | Type | Description |
|---|---|---|
count |
Per call. |
|
valid |
Per call. |
|
x |
|
|
y |
|
|
weight |
|
Source code in iron/operators/layer_norm.py
Limbs
¶
Limbs(*, num_aie_columns: int = None, num_channels: int = None, tile_size: int = lambda op: 6 * op.line, rows: int, line: int)
Bases: Rowwise
Each row of float32 as six planes of its bf16 limbs, for a float32 product by GEMM.
[hi, mid, hi, lo, mid, hi].
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
num_aie_columns
|
int
|
Tunable: the call's, else the profile's, else |
None
|
num_channels
|
int
|
Tunable: the call's, else the profile's, else |
None
|
tile_size
|
int
|
Derived where not given; a given value must equal it. |
lambda op: 6 * op.line
|
rows
|
int
|
|
required |
line
|
int
|
|
required |
Attributes:
| Name | Type | Description |
|---|---|---|
count |
Per call. |
|
valid |
Per call. |
|
x |
|
|
y |
|
Source code in iron/operators/limbs.py
Log
¶
Log(*, num_aie_columns: int = None, num_channels: int = None, tile_size: int = None, size: int, offset: float = 0.0)
Bases: UnaryElementwise
log(x + offset) of float32, rounded once to bf16.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
num_aie_columns
|
int
|
Tunable: the call's, else the profile's, else |
None
|
num_channels
|
int
|
Tunable: the call's, else the profile's, else |
None
|
tile_size
|
int
|
Tunable: the call's, else the profile's, else |
None
|
size
|
int
|
|
required |
offset
|
float
|
|
0.0
|
Attributes:
| Name | Type | Description |
|---|---|---|
count |
Per call. |
|
valid |
Per call. |
|
x |
|
|
y |
|
Source code in iron/operators/log.py
Magnitude
¶
Magnitude(*, num_aie_columns: int = None, num_channels: int = None, tile_size: int = lambda op: op.width // 2, rows: int, width: int)
Bases: Rowwise
The magnitude of each row of complex float32 held as [re | im].
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
num_aie_columns
|
int
|
Tunable: the call's, else the profile's, else |
None
|
num_channels
|
int
|
Tunable: the call's, else the profile's, else |
None
|
tile_size
|
int
|
Derived where not given; a given value must equal it. |
lambda op: op.width // 2
|
rows
|
int
|
|
required |
width
|
int
|
|
required |
Attributes:
| Name | Type | Description |
|---|---|---|
count |
Per call. |
|
valid |
Per call. |
|
x |
|
|
y |
|
Source code in iron/operators/magnitude.py
Merge
¶
Merge(*, rows: int, placeholders: tuple[tuple[int, int], ...], block: int = 64, offset: bool = False, kinds: int = lambda op: len(op.placeholders))
Bases: Operator
The row each position of ids takes from the text's embeddings and the towers' soft tokens.
For each placeholder (token, at), the j-th token takes row
at + j; any other id its own position.
Given before, the count of each placeholder before these ids (a
prompt chunk after others), the j-th takes row at + before + j.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rows
|
int
|
|
required |
placeholders
|
tuple[tuple[int, int], ...]
|
Each placeholder's (token id, first row of its soft tokens). |
required |
block
|
int
|
|
64
|
offset
|
bool
|
|
False
|
kinds
|
int
|
Derived where not given; a given value must equal it. |
lambda op: len(op.placeholders)
|
Attributes:
| Name | Type | Description |
|---|---|---|
ids |
|
|
before |
|
|
merge |
|
|
count |
Per call. |
Source code in iron/operators/merge.py
MHA
¶
MHA(*, num_heads: int, num_KV_heads: int = lambda op: op.num_heads, seq_len: int = lambda op: op.seq_pad, seq_pad: int = lambda op: op.seq_padding(op.seq_len), kv_len: int = lambda op: op.seq_pad, heads_interleaved: bool = False, kv_interleaved: bool = False, d: int = 64, causal: bool = True, window: int | None = None, scale: float = lambda op: float(1 / np.sqrt(op.d)), B_q: int = None, num_pipelines: int = None, pv_cores: int = None)
Bases: Operator
Fused attention over (B_q, d) Q, (d, B_kv) K and (B_kv, d) V blocks.
More than six pipelines split Q and O over two shims.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
num_heads
|
int
|
|
required |
num_KV_heads
|
int
|
|
lambda op: op.num_heads
|
seq_len
|
int
|
|
lambda op: op.seq_pad
|
seq_pad
|
int
|
Derived where not given; a given value must equal it. |
lambda op: op.seq_padding(op.seq_len)
|
kv_len
|
int
|
A cache longer than the queries, whose last rows the queries are. |
lambda op: op.seq_pad
|
heads_interleaved
|
bool
|
|
False
|
kv_interleaved
|
bool
|
|
False
|
d
|
int
|
|
64
|
causal
|
bool
|
|
True
|
window
|
int | None
|
Keys more than this many positions from a query are masked. |
None
|
scale
|
float
|
|
lambda op: float(1 / np.sqrt(op.d))
|
B_q
|
int
|
Tunable: the call's, else the profile's, else |
None
|
num_pipelines
|
int
|
Tunable: the call's, else the profile's, else |
None
|
pv_cores
|
int
|
Tunable: the call's, else the profile's, else |
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
Q |
|
|
K |
|
|
V |
|
|
O |
|
|
valid |
Per call. A bound on the queries limits the compute; Q and O stream every row. |
|
kv_valid |
Per call. A bound on the keys limits K's and V's blocks: the work follows the context. |
|
heads |
Per call. One query packed sits past every key block: no causal skip, no diagonal. |
|
q_blocks_per_pipeline |
Per call. |
|
q_blocks_valid |
Per call. |
|
kv_blocks |
Per call. |
|
s_q |
Per call. |
|
s_kv |
Per call. |
|
q_start |
Per call. |
|
q_offset |
Per call. |
Source code in iron/operators/mha.py
kv_tokens
property
¶
kv_tokens: int
The keys attended over: the call's bound, else kv_len less Q's padding.
first_query
property
¶
first_query: int
The position of Q's first row, which mha.cc masks by; 0 unmasked.
first_query_block
property
¶
first_query_block: int
The Q block first_query is in, q_offset rows into it.
seq_padding
¶
key_block
¶
B_kv for B_q: itself, which mha.cc's causal skip compares by.
Packed, the widest divisor of kv_len whose cores fit dev
(any, where None), since mha.cc's vectorized softmax reads 64-key rows.
Source code in iron/operators/mha.py
core_bytes
¶
Return the larger of a pipeline's QK^T and P*V cores' L1 bytes.
Kernels' data and stack included, over the device's default stack.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dev
|
Device
|
The device the cores' memory is measured against. |
required |
B_q
|
int
|
The query block size. |
required |
B_kv
|
int
|
The key/value block size. |
required |
pv_cores
|
int
|
The P*V cores the head is split over. |
required |
Source code in iron/operators/mha.py
mha_kernel
¶
mha.cc at a B_q x width x B_kv block.
Source code in iron/operators/mha.py
ops
¶
ops() -> int
Q K^T and its product with V per head, over the keys each query keeps.
Source code in iron/operators/mha.py
reference
¶
Attention per head, the queries the keys' last rows.
Query rows from s_q on are zeros and keys from s_kv on are
masked.
Source code in iron/operators/mha.py
sequence
¶
One descriptor set per KV group.
K and V re-read per (head, block) from the iteration slot, not six descriptors per block.
Source code in iron/operators/mha.py
1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 | |
ReLU
¶
Bases: UnaryElementwise
AIE-accelerated ReLU activation function.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
num_aie_columns
|
int
|
Tunable: the call's, else the profile's, else |
None
|
num_channels
|
int
|
Tunable: the call's, else the profile's, else |
None
|
tile_size
|
int
|
Tunable: the call's, else the profile's, else |
None
|
size
|
int
|
|
required |
Attributes:
| Name | Type | Description |
|---|---|---|
count |
Per call. |
|
valid |
Per call. |
|
x |
|
|
y |
|
Source code in iron/operators/relu.py
PatchPositions
¶
PatchPositions(*, rows: int, out_height: int, out_width: int, pool: int = 3, positions: int = 10240, cores: int = 16, block: int = 256)
Bases: Operator
Where each of rows patch rows of the vision tower comes from.
For an image resized to out_rows by out_columns pixels.
The patches are in pooling-window order; order is each one's row of
Resample's raster. Rows past the image hold positions,
2 * positions, past each table, which a gather bounded to the image's
patches never reads, and the raster's zero patch.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rows
|
int
|
|
required |
out_height
|
int
|
|
required |
out_width
|
int
|
|
required |
pool
|
int
|
|
3
|
positions
|
int
|
The rows of each axis's position-embedding table. |
10240
|
cores
|
int
|
Resample's cores, which its raster's columns are padded past. |
16
|
block
|
int
|
|
256
|
Attributes:
| Name | Type | Description |
|---|---|---|
out_rows |
Per call. |
|
out_columns |
Per call. |
|
xy |
|
|
position_ids |
|
|
order |
|
|
count |
Per call. |
|
dst_rows |
Per call. |
|
dst_columns |
Per call. |
Source code in iron/operators/resample/op.py
sequence
¶
Write xy and order as the core computes them.
Writes position_ids with a block's x into the first half and its
y into the second.
Source code in iron/operators/resample/op.py
Resample
¶
Resample(*, height: int, width: int, out_height: int, out_width: int, chunk: int = 4096, words: int = 356, patch_columns: int = lambda op: max(8, op.default_patch_columns()), image_chunks: int = lambda op: op.height * -(-3 * op.width // op.chunk), width_chunks: int = lambda op: 16 * -(-op.out_width // (16 * SIDE)), height_chunks: int = lambda op: 16 * -(-op.out_height // (16 * SIDE)), patch_rows: int = lambda op: op.padded_rows(), num_aie_columns: int = None, num_channels: int = None)
Bases: Operator
An image resized as torchvision's antialiased bicubic resize does it on the CPU.
Written as the vision tower's 16x16 patches of u8 / 255 in bf16
(vision.resize).
Every core receives the image in chunk-byte chunks; core k owns
patch columns k, k + cores, ..., resamples each row across them, then
each band of 16 rows down them. Patch (py, px) is row py * pad + px
of patches, pad the patch columns padded past the cores with zeros:
row pad - 1 is the zero patch padding gathers. The resized sizes are
whole patches; a call's sizes that are not write zeros.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
height
|
int
|
|
required |
width
|
int
|
|
required |
out_height
|
int
|
|
required |
out_width
|
int
|
|
required |
chunk
|
int
|
Every chunk is a handshake with every core: few and large ones. |
4096
|
words
|
int
|
A 39-tap window: a scale of up to 9.5. |
356
|
patch_columns
|
int
|
A floor of 8, so two resizes of one image that need no more share an array. |
lambda op: max(8, op.default_patch_columns())
|
image_chunks
|
int
|
|
lambda op: op.height * -(-3 * op.width // op.chunk)
|
width_chunks
|
int
|
Multiples of 16, as ResampleTaps's are. |
lambda op: 16 * -(-op.out_width // (16 * SIDE))
|
height_chunks
|
int
|
|
lambda op: 16 * -(-op.out_height // (16 * SIDE))
|
patch_rows
|
int
|
|
lambda op: op.padded_rows()
|
num_aie_columns
|
int
|
Tunable: the call's, else the profile's, else |
None
|
num_channels
|
int
|
Tunable: the call's, else the profile's, else |
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
rows |
Per call. |
|
columns |
Per call. |
|
out_rows |
Per call. |
|
out_columns |
Per call. |
|
image |
|
|
taps_w |
|
|
taps_h |
|
|
patches |
|
|
src_rows |
Per call. |
|
src_columns |
Per call. |
|
dst_rows |
Per call. |
|
dst_columns |
Per call. |
|
image_count |
Per call. |
|
width_count |
Per call. |
|
height_count |
Per call. |
|
patch_count |
Per call. |
Source code in iron/operators/resample/op.py
default_patch_columns
¶
default_patch_columns() -> int
Return patch columns a core holds: out_width's split over the cores.
And a zero one past them.
Source code in iron/operators/resample/op.py
padded_rows
¶
padded_rows() -> int
Every band's patch columns, padded past the cores.
Source code in iron/operators/resample/op.py
held
¶
held(dev) -> int
Core memory a core of dev holds at patch_columns.
Source code in iron/operators/resample/op.py
reference
¶
reference(image, taps_w, taps_h, *, rows=None, columns=None, out_rows=None, out_columns=None, patches=None)
Return the kernel's patches (vision.resize_ref).
Given patches, written over its leading rows, the ones a call
writes, the rest kept.
Source code in iron/operators/resample/op.py
sequence
¶
Run the image, then both tables down one stream, and each core's patches.
Source code in iron/operators/resample/op.py
ResampleTaps
¶
ResampleTaps(*, in_size: int, out_size: int, words: int = 256, slots: int | None = None, chunks: int = lambda op: 16 * -(-op.out_size // (16 * max(1, op.per_chunk(op.in_size, op.out_size)))), num_aie_columns: int = None, num_channels: int = None)
Bases: Operator
The int16 filter table and precision torch resamples one axis with.
in_samples samples to out_samples.
Each core takes chunks k, k + cores, ... and computes their weights in
float64 twice, as vision.resample_peak and vision.resample_quantize
do: once for the largest weight, which a chain through the cores
reduces, and once to quantize at the precision it sets.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
in_size
|
int
|
|
required |
out_size
|
int
|
|
required |
words
|
int
|
|
256
|
slots
|
int | None
|
The slots of a chunk, or None for as many as the window lets fit. |
None
|
chunks
|
int
|
A multiple of 16, so every core count up to it divides. |
lambda op: 16 * -(-op.out_size // (16 * max(1, op.per_chunk(op.in_size, op.out_size))))
|
num_aie_columns
|
int
|
Tunable: the call's, else the profile's, else |
None
|
num_channels
|
int
|
Tunable: the call's, else the profile's, else |
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
table |
|
|
in_samples |
Per call. |
|
out_samples |
Per call. |
|
count |
Per call. |
|
in_length |
Per call. |
|
out_length |
Per call. |
Source code in iron/operators/resample/op.py
per_chunk
¶
Return the outputs a chunk holds of n_in samples to n_out (per_chunk).
filled
¶
Return the chunks a table of n_in samples to n_out is written in.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n_in
|
int
|
The input samples on the axis. |
required |
n_out
|
int
|
The output samples on the axis. |
required |
Returns:
| Type | Description |
|---|---|
int
|
Those its outputs fill, in a multiple of 16 as |
int
|
chunk for sizes the table cannot hold, each header saying so. The |
int
|
cores write whole rounds, so up to a round past them. |
Source code in iron/operators/resample/op.py
sequence
¶
Chunk c from core c % cores, the order the cores compute them in.
Source code in iron/operators/resample/op.py
RMSNorm
¶
RMSNorm(*, num_aie_columns: int = None, num_channels: int = None, tile_size: int, rows: int, weighted: bool = False, epsilon: float = 1e-05)
Bases: WeightedRowwise
AIE-accelerated RMS Normalization of each row, optionally times a learned weight row.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
num_aie_columns
|
int
|
Tunable: the call's, else the profile's, else |
None
|
num_channels
|
int
|
Tunable: the call's, else the profile's, else |
None
|
tile_size
|
int
|
|
required |
rows
|
int
|
|
required |
weighted
|
bool
|
|
False
|
epsilon
|
float
|
The epsilon under the root: 1e-5 by default; a model states its own. |
1e-05
|
Attributes:
| Name | Type | Description |
|---|---|---|
count |
Per call. |
|
valid |
Per call. |
|
x |
|
|
y |
|
|
weight |
|
Source code in iron/operators/rms_norm.py
RoPE
¶
RoPE(*, rows: int, cols: int, angle_rows: int = lambda op: op.rows, num_aie_columns: int = None, two_halves: bool = True)
Bases: Operator
AIE-accelerated RoPE: one core per column, each rotating rows of cols.
Each angle row serves rows / angle_rows consecutive rows.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rows
|
int
|
|
required |
cols
|
int
|
|
required |
angle_rows
|
int
|
|
lambda op: op.rows
|
num_aie_columns
|
int
|
Tunable: the call's, else the profile's, else |
None
|
two_halves
|
bool
|
|
True
|
Attributes:
| Name | Type | Description |
|---|---|---|
x |
|
|
angles |
|
|
y |
|
|
valid |
Per call. Either row count may be bounded per call: x[:n] and angles[:m]. |
|
valid_angles |
Per call. |
|
lut_rows |
Per call. |
|
rows_per_lut |
Per call. |
Source code in iron/operators/rope.py
extent_unit
¶
Return a position's rows at a time, so each core gets its angle row.
One core takes the rows a call bounds one at a time.
Source code in iron/operators/rope.py
reference
¶
datamovement.rope_ref on the packed angles buffer.
Each angle row serving rows / angles.shape[0] consecutive rows
of x.
Source code in iron/operators/rope.py
Sample
¶
Sample(*, vocab: int, k_max: int = 64, drawable: int = lambda op: op.vocab, steps: int = 1, cores: int = None, chunk: int = None)
Bases: Operator
Draw the next token from a row of logits (exact temperature/top-k).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
vocab
|
int
|
|
required |
k_max
|
int
|
|
64
|
drawable
|
int
|
The logits that are tokens'; the rest pad an odd vocabulary to whole words, and the last select core writes them -inf. |
lambda op: op.vocab
|
steps
|
int
|
Positions the draws and the record hold; the sequence picks one. |
1
|
cores
|
int
|
Tunable: the call's, else the profile's, else |
None
|
chunk
|
int
|
Tunable: the call's, else the profile's, else |
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
logits |
|
|
draws |
|
|
tokens |
|
|
token |
|
|
row |
Per call. Element offsets of this position's draw row and record. |
|
at |
Per call. |
Source code in iron/operators/sample.py
runs
property
¶
Return each select core's (logits, chunk, drawable).
The last reads what is left, the padding with it.
sequence
¶
One group: the draw and the logits in, then the record and the token.
Source code in iron/operators/sample.py
reference
¶
(tokens, token): the draw at draws row row // 4, recorded at at.
Source code in iron/operators/sample.py
SelectiveScan
¶
SelectiveScan(*, rows: int, heads: int, head_dim: int, state_size: int, groups: int = 1, columns: int = None, inner: int = lambda op: op.heads * op.head_dim, width: int = lambda op: op.inner + 2 * op.groups * op.state_size, block: int = None, slice_state: int = lambda op: SLICE * op.state_size)
Bases: Operator
Each row t of xbc updates each head's state (head_dim, state_size) and reads it out.
The row splits into x (heads, head_dim) and groups rows
each of B and C (state_size,):
dt = softplus(dt[t] + dt_bias)
state = exp(dt * A) * state + dt * outer(x, B)
y[t] = state @ C + D * x
head h reading group h // (heads // groups)'s B and C
and its (dt_bias, A, D) from head. state is the state
before the first row; final the state after the last valid one.
Float32 throughout, y rounded once.
Each of columns cores takes whole heads, SLICE rows of a head's
state at a time, and gathers a block of rows' x, B, C and
dt for the slice from whole rows of xbc, which one memtile
joins to each row's dt and every core reads; another joins each
slice's state to its head's head, so a core reads two streams.
The kernel keeps the state from block to block. Scalar.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rows
|
int
|
|
required |
heads
|
int
|
|
required |
head_dim
|
int
|
|
required |
state_size
|
int
|
|
required |
groups
|
int
|
|
1
|
columns
|
int
|
Tunable: the call's, else the profile's, else |
None
|
inner
|
int
|
Derived where not given; a given value must equal it. |
lambda op: op.heads * op.head_dim
|
width
|
int
|
Derived where not given; a given value must equal it. |
lambda op: op.inner + 2 * op.groups * op.state_size
|
block
|
int
|
Tunable: the call's, else the profile's, else |
None
|
slice_state
|
int
|
Derived where not given; a given value must equal it. |
lambda op: SLICE * op.state_size
|
Attributes:
| Name | Type | Description |
|---|---|---|
xbc |
|
|
dt |
|
|
head |
|
|
state |
|
|
y |
|
|
final |
|
|
valid |
Per call. |
|
count |
Per call. |
Source code in iron/operators/selective_scan.py
slice_bytes
¶
Bytes of a slice's (dt_bias, A, D) then state in, and its final out.
held
¶
Return the bytes a core of dev uses at rows rows per call.
That is the input row, the block buffer, y, the kernel's data and stack (its own copy of the state included), and the device's default stack, which covers the core's own frame. One slice's state and final state are not counted.
Source code in iron/operators/selective_scan.py
sequence
¶
Every row of xbc and dt once a slice, for every core.
Per core, its heads' parameters and states, and its slices of y
and final.
Source code in iron/operators/selective_scan.py
ops
¶
ops() -> int
Count per row, per head, the state update and the C readout.
The update is two multiplies and an add over its
head_dim * state_size elements; the readout a
state_size-deep dot product per head_dim row.
Source code in iron/operators/selective_scan.py
Sigmoid
¶
Bases: UnaryElementwise
AIE-accelerated Sigmoid activation function.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
num_aie_columns
|
int
|
Tunable: the call's, else the profile's, else |
None
|
num_channels
|
int
|
Tunable: the call's, else the profile's, else |
None
|
tile_size
|
int
|
Tunable: the call's, else the profile's, else |
None
|
size
|
int
|
|
required |
Attributes:
| Name | Type | Description |
|---|---|---|
count |
Per call. |
|
valid |
Per call. |
|
x |
|
|
y |
|
Source code in iron/operators/sigmoid.py
SiLU
¶
Bases: UnaryElementwise
AIE-accelerated SiLU activation function.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
num_aie_columns
|
int
|
Tunable: the call's, else the profile's, else |
None
|
tile_size
|
int
|
Tunable: the call's, else the profile's, else |
None
|
size
|
int
|
|
required |
Attributes:
| Name | Type | Description |
|---|---|---|
count |
Per call. |
|
valid |
Per call. |
|
x |
|
|
y |
|
Source code in iron/operators/silu.py
Softmax
¶
Softmax(*, rows: int, cols: int, num_aie_columns: int = None, num_channels: int = None, block: int = None, streamed: bool = None, input_dtype: Any = ml_dtypes.bfloat16, soft_cap: float | None = None, has_addend: bool = False, window: int | None = None, period: int | None = None)
Bases: Operator
Softmax over each row, one core per (column, channel), each taking a contiguous run of rows.
streamed, a core reads a row in block-sized chunks three times
(maximum, sums, output); a per-call bound zeroes elements past it and
only its blocks move.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rows
|
int
|
|
required |
cols
|
int
|
|
required |
num_aie_columns
|
int
|
Tunable: the call's, else the profile's, else |
None
|
num_channels
|
int
|
Tunable: the call's, else the profile's, else |
None
|
block
|
int
|
Tunable: the call's, else the profile's, else |
None
|
streamed
|
bool
|
Tunable: the call's, else the profile's, else |
None
|
input_dtype
|
Any
|
|
ml_dtypes.bfloat16
|
soft_cap
|
float | None
|
Float input only, mlir-aie's softmax_f32_bf16: a pre-cap addend and cap * tanh(x / cap). |
None
|
has_addend
|
bool
|
|
False
|
window
|
int | None
|
Float input only, softmax_rows_banded_f32_bf16: a band computed from these two in the core (window, and the row's own query position, wrapped every period rows), not a mask operand -- a core has two input DMA channels, and x and addend already take both. |
None
|
period
|
int | None
|
|
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
length |
Per call. cols, or fewer per call |
|
x |
|
|
addend |
|
|
y |
|
|
valid_cols |
Per call. |
|
blocks |
Per call. A row held whole is one block, which the bound does not change. |
Source code in iron/operators/softmax.py
sequence
¶
Each lane's rows as (block, row, element), thrice if streamed.
Source code in iron/operators/softmax.py
reference
¶
Return the softmax kernel's contract reference, a row at a time.
rest is addend where has_addend, positional; the
band, where given, comes from the factory's own banded contract,
row_offset each row's index.
Source code in iron/operators/softmax.py
Tanh
¶
Bases: UnaryElementwise
AIE-accelerated Tanh activation function.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
num_aie_columns
|
int
|
Tunable: the call's, else the profile's, else |
None
|
num_channels
|
int
|
Tunable: the call's, else the profile's, else |
None
|
tile_size
|
int
|
Tunable: the call's, else the profile's, else |
None
|
size
|
int
|
|
required |
Attributes:
| Name | Type | Description |
|---|---|---|
count |
Per call. |
|
valid |
Per call. |
|
x |
|
|
y |
|
Source code in iron/operators/tanh.py
FastFlowLM ports¶
Catalogued as "flm.<name>". flm.ProjectionGEMM is not
iron.operators.GEMM.
Ports of FastFlowLM's Gemma 4 NPU overlays to IRON.
https://github.com/ROCm/FastFlowLM, MIT.
DequantBFP
¶
DequantBFP(*, K: int, N: int, run_out_features: int | None = None, run_period_out_features: int | None = None, quantized_bytes: int = lambda op: op.quantized_size(), packed_blocks: int = lambda op: op.K * op.N // bfp.BLOCK, cols: int = None)
Bases: Operator
q4nx weights to bfp16, packed the way flm.ProjectionGEMM reads B.
A 4-row grid, as wide as the device, for one q4nx tiling. Nothing depends on K or N, since every core loops forever over identical per-block work, so one xclbin serves every shape; K, N and the interleave move only the instruction stream's offsets. See README.md for the layout and constraints.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
K
|
int
|
|
required |
N
|
int
|
|
required |
run_out_features
|
int | None
|
A projection interleaved with another in the same buffer: this one occupies |
None
|
run_period_out_features
|
int | None
|
|
None
|
quantized_bytes
|
int
|
Derived where not given; a given value must equal it. The q4nx input is bytes, as a block interleaves three tables at 5 bits a weight; the output is bfp16ebs8 blocks, as the drains index them. |
lambda op: op.quantized_size()
|
packed_blocks
|
int
|
Derived where not given; a given value must equal it. |
lambda op: op.K * op.N // bfp.BLOCK
|
cols
|
int
|
Tunable: the call's, else the profile's, else |
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
qw |
|
|
out |
|
Source code in iron/operators/flm/dequant/op.py
config_name
property
¶
config_name: str
Stem of the artifacts that do not depend on the shape: the xclbin's.
name
property
¶
name: str
The configuration's stem, then the shape.
Two instruction streams never share a name.
column_blocks
property
¶
Where each of the weight's column blocks sits in the blob, in column blocks.
run_out_features of every run_period_out_features.
xclbin_operator
¶
Return this configuration at its reference shape.
Its xclbin serves every shape sharing the configuration, and only the instruction stream is per shape, as flm.ProjectionGEMM's.
Source code in iron/operators/flm/dequant/op.py
grid
¶
Return the weight's q4nx blocks in qw, as q4nx.unpack's grid.
A column block is its k-tiles in order, each k-tile its two k-halves, each of those its two n-halves (README.md layers 6-9).
Source code in iron/operators/flm/dequant/op.py
reference
¶
Return the bytes the operator must produce from the blob qw.
A flat uint8 array, bit-exact against the device, since the cores round to bf16 toward negative infinity (README.md has the layout and rounding).
Source code in iron/operators/flm/dequant/op.py
ProjectionGEMM
¶
ProjectionGEMM(*, M: int, K: int, N: int, epilogue: Epilogue | str = Epilogue.NONE, packed_blocks: int = lambda op: op.K * op.N // bfp.BLOCK, tile_n: int = None, k_tile: int = None, ct_k: int = None, tile_ma: int = None, m_chunk: int = None, epilogue_modes: tuple = tuple(Epilogue), rounding: Rounding | str = Rounding.CONV_EVEN, gelu: Gelu | str = Gelu.FP32, b_col_maj: bool = False, precision: Precision, bfp16_b: bool = False)
Bases: Operator
AIE-accelerated bf16 GEMM on a 4-row grid, as wide as the device.
With a fused epilogue. M, K, N and the activation are values written to
the cores, so every shape on one config_name shares an xclbin. Used
wherever iron.operators.GEMM doesn't apply (a weight operand,
MEDIUM/HIGH, two or more rows).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
M
|
int
|
|
required |
K
|
int
|
|
required |
N
|
int
|
|
required |
epilogue
|
Epilogue | str
|
The activation, selected at run time from the compiled-in modes; its gelu is x * sigmoid(1.702x), the port's own arithmetic. |
Epilogue.NONE
|
packed_blocks
|
int
|
Derived where not given; a given value must equal it. B's packed block count on AIE2P: blocks, not bytes, since B's declaration counts bfp16ebs8 blocks and bfp.itemsize turns that back into the byte count pack_b returns. |
lambda op: op.K * op.N // bfp.BLOCK
|
tile_n
|
int
|
Tunable: the call's, else the profile's, else |
None
|
k_tile
|
int
|
Tunable: the call's, else the profile's, else |
None
|
ct_k
|
int
|
Tunable: the call's, else the profile's, else |
None
|
tile_ma
|
int
|
Tunable: the call's, else the profile's, else |
None
|
m_chunk
|
int
|
Tunable: the call's, else the profile's, else |
None
|
epilogue_modes
|
tuple
|
The activations the epilogue can select between at run time. Each one compiled in costs program memory, so a deployment that dispatches two should compile two. |
tuple(Epilogue)
|
rounding
|
Rounding | str
|
|
Rounding.CONV_EVEN
|
gelu
|
Gelu | str
|
|
Gelu.FP32
|
b_col_maj
|
bool
|
B as stored (N, K), a checkpoint's layout, read as is: the memtile lays out the k panels the kernel reads as B^T blocks, so no packed copy. |
False
|
precision
|
Precision
|
MEDIUM multiplies on AIE2P's bfp16 macs, A and B rounded to bfp16ebs8 (the port's arithmetic); above it, or on AIE2, on bf16 macs. |
required |
bfp16_b
|
bool
|
B in DDR as pack_b's bfp16ebs8 blocks, which AIE2P's bfp16 macs multiply unconverted: a packed B at precision='medium' there must say so, since B's host shape depends on it and no device is known yet. |
False
|
Attributes:
| Name | Type | Description |
|---|---|---|
A |
|
|
B |
|
|
C |
|
|
valid |
Per call. M, or fewer rows per call ( |
|
n_val |
Per call. The parameter words every core reads once a call. The unit counts are read only at m_chunk > 1: each word costs every dispatch (README.md, "A word is not free"). The clamp bounds are read always, the identity: the kernel's clamp is not compiled out. |
|
m_row_blocks |
Per call. |
|
k_iters |
Per call. |
|
mode |
Per call. |
|
clamp_min |
Per call. |
|
clamp_max |
Per call. |
|
n_units |
Per call. |
|
units_valid |
Per call. Under a bound, the units it reaches: the cores' trip count and C's length, in row blocks. |
|
units_repeat |
Per call. A's repeat count on a full ELF: the units less one. |
|
b_passes_repeat |
Per call. Under a bound, the passes B's memtile ring takes on a column-block, less one: a streamed ring's per call, as DDR reads B once per unit reached. |
|
b_resident |
Per call. Whether the memtile replays B to every unit, as it does where B is resident, so that the cores drain the replays past a bound. |
|
a_half |
Per call. Under a bound, where the second half of the core rows starts in A and in C: past the first half's share of the units it reaches. |
|
c_half |
Per call. |
Source code in iron/operators/flm/gemm/op.py
compiled_modes
property
¶
compiled_modes: tuple[Epilogue, ...]
Return the activations compiled into the epilogue.
None and epilogue_modes.
epilogue_mask
property
¶
epilogue_mask: int
Return the bitmask of the modes compiled into the epilogue.
Mode 0 is always present; the kernel falls back to it.
config_name
property
¶
config_name: str
Stem of the artifacts that do not depend on the shape: the xclbin's.
name
property
¶
name: str
Return the configuration's stem, then every runtime parameter.
Two instruction streams never share a name.
frame
¶
frame(dev, tile_n, k_tile, ct_k, band_m, bfp16_b, emulate) -> int
Return what a core of dev holds beside its buffers at these tiles.
Its kernel's data and deepest call's stack, over the default stack. Kept per kernel, as every resolve asks it of up to six.
Source code in iron/operators/flm/gemm/op.py
kernel
¶
Return the fused tile kernel each core runs at these tiles.
Source code in iron/operators/flm/gemm/op.py
xclbin_operator
¶
Return this configuration at its reference shape and activation.
The xclbin every shape sharing it runs, each with its own instruction stream.
Source code in iron/operators/flm/gemm/op.py
pack_b
¶
Reorder a row-major (K, N) weight matrix into consumption order.
(iron.operators.flm.packing): flat uint8 bfp16ebs8 blocks on
NPU2, flat bf16 on NPU1, making both B hops linear descriptors.
Raises:
| Type | Description |
|---|---|
ValueError
|
Under |
Source code in iron/operators/flm/gemm/op.py
reference
¶
C = epilogue(A @ B), in the kernel's order.
The product accumulates in float32 and rounds to the output dtype
before the epilogue, since mm_fused_epilogue_chunk converts the
accumulator to bf16 first. Not bit-exact -- the activation is a LUT
on aie2, native on aie2p, worth up to ~0.02 absolute, absorbed by
the tolerance. B is (N, K) under b_col_maj.
Source code in iron/operators/flm/gemm/op.py
tolerance
¶
tolerance() -> Tolerance | None
Return C's roundings, in units of 2^-8 of |A| @ |B|.
Under 2 for C's conversion to bf16, plus K * 2^-16 for the f32 accumulator; bfp16 macs add 2 more units (nearest) or 8 (truncating). An activated GEMM has no tolerance, since the approximation isn't bounded here.
Source code in iron/operators/flm/gemm/op.py
DecodeLayer
¶
DecodeLayer(*, geometry: FlmGemma4DecodeGeometry, layer_type: LayerType | str, x_len: int = lambda op: arg_sizes(op.geometry)['x'], proj_len: int = lambda op: arg_sizes(op.geometry)['proj'], rms_len: int = lambda op: arg_sizes(op.geometry)['rms'], rope_rms_len: int = lambda op: arg_sizes(op.geometry)['rope_rms'], kv_len: int = lambda op: arg_sizes(op.geometry)['kv'])
Bases: Operator
One Gemma 4 decode layer for one token, as FastFlowLM's engine drives it.
See README.md for the parameters, the buffers and the dispatch parameters.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
geometry
|
FlmGemma4DecodeGeometry
|
|
required |
layer_type
|
LayerType | str
|
|
required |
x_len
|
int
|
Derived where not given; a given value must equal it. |
lambda op: arg_sizes(op.geometry)['x']
|
proj_len
|
int
|
Derived where not given; a given value must equal it. |
lambda op: arg_sizes(op.geometry)['proj']
|
rms_len
|
int
|
Derived where not given; a given value must equal it. |
lambda op: arg_sizes(op.geometry)['rms']
|
rope_rms_len
|
int
|
Derived where not given; a given value must equal it. |
lambda op: arg_sizes(op.geometry)['rope_rms']
|
kv_len
|
int
|
Derived where not given; a given value must equal it. |
lambda op: arg_sizes(op.geometry)['kv']
|
Attributes:
| Name | Type | Description |
|---|---|---|
x |
|
|
proj |
|
|
rms |
|
|
rope_rms |
|
|
kv |
|
|
context_len |
Per call. |
|
max_l |
Per call. |
Source code in iron/operators/flm/layer/op.py
reference
¶
X and kv as the dispatch leaves them; see reference.py.
Source code in iron/operators/flm/layer/op.py
LMHead
¶
LMHead(*, dim: int, vocab: int, softcap: float, w_words: int = lambda op: packed_bytes(op.vocab * op.dim) // 4, x_len: int = lambda op: 2 * op.dim, cols: int = None, rows: int = None)
Bases: Operator
Softcapped logits from a q4nx vocabulary, with the RMS norm folded in.
x holds the token then its RMS weight, w the q4nx
vocabulary, y the logits. See README.md.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dim
|
int
|
|
required |
vocab
|
int
|
|
required |
softcap
|
float
|
|
required |
w_words
|
int
|
Derived where not given; a given value must equal it. |
lambda op: packed_bytes(op.vocab * op.dim) // 4
|
x_len
|
int
|
Derived where not given; a given value must equal it. |
lambda op: 2 * op.dim
|
cols
|
int
|
Tunable: the call's, else the profile's, else |
None
|
rows
|
int
|
Tunable: the call's, else the profile's, else |
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
y |
|
|
w |
|
|
x |
|
Source code in iron/operators/flm/lm_head/op.py
build
¶
build() -> Built
Each core RMS-normalizes the token once.
Each round, it accumulates one weight block per K_TILE in-features into M_TILE out-features and applies the tanh softcap to the sums.
Source code in iron/operators/flm/lm_head/op.py
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 | |
projection_error
¶
projection_error(w, x) -> float
Return the bound on the projection's error.
A share of the largest logit before the softcap.
Source code in iron/operators/flm/lm_head/op.py
reference
¶
Return the softcapped logits of X for W, computed in float64 and rounded to Y's bf16.
Source code in iron/operators/flm/lm_head/op.py
PrefillAttention
¶
PrefillAttention(*, max_context: int, num_heads: int, num_kv_heads: int, head_dim: int = lambda op: Geometry.of(op.kernel()).dh, qo_len: int = lambda op: op.max_context * op.num_heads * op.head_dim, kv_len: int = lambda op: 2 * op.max_context * op.num_kv_heads * op.head_dim)
Bases: _PrefillAttentionBase
Causal prefill attention with a head dim of 512, from a KV cache.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
max_context
|
int
|
|
required |
num_heads
|
int
|
|
required |
num_kv_heads
|
int
|
|
required |
head_dim
|
int
|
Derived where not given; a given value must equal it. |
lambda op: Geometry.of(op.kernel()).dh
|
qo_len
|
int
|
Derived where not given; a given value must equal it. |
lambda op: op.max_context * op.num_heads * op.head_dim
|
kv_len
|
int
|
Derived where not given; a given value must equal it. |
lambda op: 2 * op.max_context * op.num_kv_heads * op.head_dim
|
Attributes:
| Name | Type | Description |
|---|---|---|
o |
|
|
q |
|
|
kv |
|
|
L_begin |
Per call. |
|
L_end |
Per call. |
|
max_l |
Per call. |
Source code in iron/operators/flm/prefill_attn/op.py
PrefillSlidingAttention
¶
PrefillSlidingAttention(*, max_context: int, num_heads: int, num_kv_heads: int, head_dim: int = lambda op: Geometry.of(op.kernel()).dh, qo_len: int = lambda op: op.max_context * op.num_heads * op.head_dim, kv_len: int = lambda op: 2 * op.max_context * op.num_kv_heads * op.head_dim, window: int = 512)
Bases: _PrefillAttentionBase
Sliding-window causal prefill attention with a head dim of 256, from a KV cache.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
max_context
|
int
|
|
required |
num_heads
|
int
|
|
required |
num_kv_heads
|
int
|
|
required |
head_dim
|
int
|
Derived where not given; a given value must equal it. |
lambda op: Geometry.of(op.kernel()).dh
|
qo_len
|
int
|
Derived where not given; a given value must equal it. |
lambda op: op.max_context * op.num_heads * op.head_dim
|
kv_len
|
int
|
Derived where not given; a given value must equal it. |
lambda op: 2 * op.max_context * op.num_kv_heads * op.head_dim
|
window
|
int
|
|
512
|
Attributes:
| Name | Type | Description |
|---|---|---|
o |
|
|
q |
|
|
kv |
|
|
L_begin |
Per call. |
|
L_end |
Per call. |
|
max_l |
Per call. |