Tuning¶
Timing each operator's settings on the NPU and writing the fastest as a profile. Nothing in the graph core imports it.
measure
¶
Timing designs on the NPU.
A design's step is the excess of repeats runs of it over one run, per
extra step, so the dispatch cancels (measure). Runs are interleaved
round by round, timed only while no other process holds the NPU, and a
figure whose calls waited for a CPU (CPU_WAIT_US) is refused. Every
figure is kept in a Cache keyed on what it follows, so a design is
timed once per NPU and power mode.
Timing
dataclass
¶
How long to measure: rounds interleaved rounds of calls runs.
Where the runs are settings of one design, a setting whose runs take
more than cutoff times the fastest setting's once settle rounds
are in is timed no further, its figure those rounds'.
Standalone
¶
Standalone(name: str, runlist: Sequence[Operator], values: Mapping[Operator, Mapping[str, int]] | None = None, seed: int = 0, distinct: bool = True, inputs: Mapping[Operator, Mapping[str, ndarray]] | None = None, full_elf: bool = True, load: bool = True)
A runlist of operators built alone into a loaded image.
Its inputs seeded random data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
full_elf
|
bool
|
How the runlist is packaged: one full ELF, else an xclbin per design dispatched step by step. |
True
|
distinct
|
bool
|
Every step runs on buffers of its own, as a graph's do, so no step finds its inputs in a SoC cache. |
True
|
values
|
Mapping[Operator, Mapping[str, int]] | None
|
Per operator, each per-call value by name: the tuner cannot know what a value means. One derived from bound extents follows from theirs. |
None
|
inputs
|
Mapping[Operator, Mapping[str, ndarray]] | None
|
Per operator, input buffers by name, where random bytes would not be representative (a draw row's temperature and top-k). |
None
|
load
|
bool
|
Write the per-call values now; with |
True
|
Source code in iron/tune/measure.py
load
¶
Write the per-call values, which loads a full ELF that holds any.
Any other image is loaded by its first run.
address
¶
Return the device address of buffer name.
Source code in iron/tune/measure.py
inputs
¶
Return what step k was given, by buffer name.
Views of the device buffers, an in-out one as it was before any run.
Source code in iron/tune/measure.py
written
¶
Return what step k wrote in the last run, by buffer name.
Along a bounded axis only the rows under the call's bound.
Source code in iron/tune/measure.py
Measurement
dataclass
¶
Measurement(t_step_us: float, round_us: tuple[float, ...], output: str, run: str, pmode: str, calls: int, default_round_us: tuple[float, ...] = (), measured: str = (lambda: isoformat())())
One design's step, as measure timed it.
Attributes:
| Name | Type | Description |
|---|---|---|
t_step_us |
float
|
Its time per step, the median of |
round_us |
tuple[float, ...]
|
Its time per step as each round measured it. Two
measurements of one |
output |
str
|
The sha256 of what one run wrote, so two settings run on the same inputs compare without a rerun. |
run |
str
|
Which |
pmode |
str
|
The NPU's power mode. |
calls |
int
|
The runs a round timed. |
default_round_us |
tuple[float, ...]
|
The rounds of the default timed beside it in its
|
measured |
str
|
The day, as an ISO date. |
against_default
¶
difference from the default timed beside it.
Source code in iron/tune/measure.py
difference
¶
difference(other: Measurement) -> tuple[float, float | None]
How much longer a step of this one takes than one of other, and that difference's standard error.
Round by round where the two were timed in one run, else from each one's own.
Source code in iron/tune/measure.py
Cache
¶
Every design measured on one NPU platform at one power mode.
One JSON file each under root/<platform>/<power mode>/.
An entry is keyed on what its time follows: the design's build (its recipe and the sources, tools and device it is compiled with, as mlir-aie's compile cache keys it), the per-call values it was run at, the contents of the inputs it was given and its packaging. Editing how a design is generated therefore misses rather than reusing a stale time.
Source code in iron/tune/measure.py
key
staticmethod
¶
key(op: Operator, values: Mapping[str, int] | None = None, inputs: Mapping[str, ndarray] | None = None, full_elf: bool = True) -> str
Return the entry op, resolved for the current device, is kept in.
As run at values on inputs (random where not given), packaged
as one full ELF or not.
Source code in iron/tune/measure.py
get
¶
get(key: str) -> Measurement | None
Return the measurement at key; None if there is none.
Also None if it was written with other fields than Measurement has.
Source code in iron/tune/measure.py
standard_error
¶
Stats' standard error of the median of samples; None from fewer than two.
platform
¶
Return the NPU's platform report from xrt-smi.
Its Name, its Power Mode, ...
Source code in iron/tune/measure.py
judge
¶
judge(default: Operator, op: Operator, inputs: Mapping[str, ndarray], written: Mapping[str, ndarray], values: Mapping[str, int] | None = None) -> str | None
Why op's output written on inputs may not replace that of default.
The setting it would replace: it must be within the default's gate and its own. None where it may.
Only a bit-identical setting is admitted where a gate is missing or
exact, or values bound an extent short of its length.
Source code in iron/tune/measure.py
time_interleaved
¶
time_interleaved(runs: Sequence[ImageCallable], timing: Timing, groups: Sequence[Hashable | None] = (), fastest: float = inf, log: Callable[[str], None] = print) -> list[Stats]
Each loaded image's per-round medians, microseconds (samples_us).
Each call's figure the time the image itself took, which its
KernelResult carries: mlir-aie's run_interleaved, whose arguments
and errors these are.
Source code in iron/tune/measure.py
write
¶
Write text to path whole, as another process may read it.
Source code in iron/tune/measure.py
measure
¶
measure(ops: Sequence[Operator], *, full_elf: bool, timing: Timing = Timing(), repeats: int = 9, values: Mapping[str, int] | None = None, inputs: Mapping[str, ndarray] | None = None, cache: Cache | None = None, remeasure: bool = False, log: Callable[[str], None] = print) -> dict[int, Measurement]
Time each of ops, settings of one design with the default first.
As many at once as the device's contexts hold. Every batch times the default too, so each other setting pairs with it round by round.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ops
|
Sequence[Operator]
|
The settings to time, the default (index 0) first. |
required |
full_elf
|
bool
|
How each is packaged: one full ELF, else an xclbin per design dispatched step by step. |
required |
timing
|
Timing
|
How long to measure each. |
Timing()
|
repeats
|
int
|
How many back-to-back runs a step's time is the excess over. |
9
|
values
|
Mapping[str, int] | None
|
The per-call values each is run at. |
None
|
inputs
|
Mapping[str, ndarray] | None
|
What its input buffers hold, by name, where random bytes would not be representative. |
None
|
cache
|
Cache | None
|
Settings it holds are taken from it rather than run, unless
|
None
|
remeasure
|
bool
|
Run every setting even where |
False
|
log
|
Callable[[str], None]
|
Where progress and a setting that fails to build are reported. |
print
|
Returns:
| Type | Description |
|---|---|
dict[int, Measurement]
|
Each one timed, by its index in |
dict[int, Measurement]
|
that does not build is left out, and reported to |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
The default does not build. |
Source code in iron/tune/measure.py
492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 | |
autotune
¶
Choosing each operator's tunables by timing them, written as a profile.
autotune takes the calls a model runs (Timed), finds each operator
shape in them, times every setting of its tunables
(Operator.domains) the placer takes against the setting it was recorded
at, and returns a Profile naming the fastest for each shape.
Point
dataclass
¶
Timed
dataclass
¶
Timed(recording: Recording, values: Mapping[str, int] = dict(), contents: Mapping[str, ndarray] = dict(), points: Sequence[Point] = ())
One call of a recorded graph, as its operators are timed in.
The graph's per-call values, and what any buffer whose contents a
step's time follows holds, by graph buffer name. points are the
values its calls run at, where they differ from call to call: an
operator whose work they change is timed at each (op_points).
op_values
¶
Return the per-call values op is written in this call.
Or at the graph's values, by member name.
Source code in iron/tune/autotune.py
op_points
¶
Return where op is timed, as (weight, values) pairs.
Points that give op's bound extents the same values are merged:
their weights are summed, and the first one's per-call values are
kept. When every point gives them the same values, as when op
has no bound extent, that is a single pair.
Source code in iron/tune/autotune.py
op_inputs
¶
Return what op's buffers hold in this call, by op's buffer name.
Source code in iron/tune/autotune.py
Trial
dataclass
¶
Trial(tunables: dict[str, Any], delta_us: float, noise_us: float | None, refused: str | None = None)
One setting of a shape, timed against the setting it was recorded at.
Attributes:
| Name | Type | Description |
|---|---|---|
tunables |
dict[str, Any]
|
The setting. |
delta_us |
float
|
How much longer its steps take, summed over the shape's operators and weighted over their points; negative is faster. |
noise_us |
float | None
|
The standard error of |
refused |
str | None
|
Why it may not replace the recorded setting, or None. |
Shape
¶
Shape(op: Operator)
The operators of the calls one profile entry covers.
One class at one set of dimensions (Profile.dims).
Source code in iron/tune/autotune.py
named
¶
Return the dimensions its entry names.
Those off their field's default, and every one an entry
covering it names, so it outranks them.
Source code in iron/tune/autotune.py
kernel_cycles
¶
Return each kernel's cycles per call as mlir-aie's nightly last timed it.
On dev's NPU, by ExternalFunction.recipe_key; empty where they
cannot be read.
Source code in iron/tune/autotune.py
least_us
¶
Return a step time op cannot beat at per-call values.
Its busiest core's matmul calls at cycles per call, at core_mhz.
None where cycles does not hold its matmul.
Source code in iron/tune/autotune.py
settings
¶
Return op's tunables as resolved for dev, then every other combination.
Every other combination of their Operator.domains it resolves at
and whose derived transfers fit their descriptors, each design once.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
op
|
Operator
|
The operator whose tunables are searched. |
required |
dev
|
Device
|
The device each combination is resolved for. |
required |
pinned
|
frozenset[str]
|
Tunables another operator of its shape pins, held at the
value |
frozenset()
|
Source code in iron/tune/autotune.py
refusal
¶
Return why the placer refuses op alone on a device, or None if it fits.
Kept under FIT_CACHE, keyed on its recipe and its build environment
(the device and the tools), so a placer fix is seen.
Source code in iron/tune/autotune.py
line
¶
Return the settings of found a coordinate descent tries for tunable name.
From through: at each of its values, the setting that differs from
through in the fewest other tunables, the first in found on a
tie. Where tunables are coupled, a value is reached by moving the
others with it.
Source code in iron/tune/autotune.py
shapes
¶
Return the shapes of calls' operators that have a tunable to search.
In the order the calls first run them, each operator design once.
Source code in iron/tune/autotune.py
time_shape
¶
time_shape(shape: Shape, dev, *, full_elf: bool, timing: Timing = Timing(), repeats: int = 9, cache: Cache | None = None, remeasure: bool = False, exhaustive: int = EXHAUSTIVE, cycles: Mapping[str, int] | None = None, core_mhz: float | None = None, log: Callable[[str], None] = print) -> list[Trial]
Each setting of shape timed, the recorded one first.
Every setting its operators share and the placer takes where there are
at most exhaustive, else those a coordinate descent tries, each
tunable's line through the fastest setting taken so far, until a
pass over the tunables moves it no further. A setting whose
least_us at cycles and core_mhz is no faster than the
fastest taken so far is not timed.
Source code in iron/tune/autotune.py
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 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 | |
autotune
¶
autotune(calls: Sequence[Timed], dev: Device, profile: Profile | None = None, *, full_elf: bool, timing: Timing = Timing(), repeats: int = 9, cache: Cache | None = None, remeasure: bool = False, cycles: Mapping[str, int] | None = None, core_mhz: float | None = None, log: Callable[[str], None] = print) -> Profile
Return profile with an entry for each shape of calls (shapes).
Each entry names the fastest setting taken (time_shape), the
recorded one where none gains. profile's entries covering any of
those shapes are replaced; the rest are kept.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
calls
|
Sequence[Timed]
|
The calls whose graphs were recorded under |
required |
dev
|
Device
|
The device the calls' operators are timed on. |
required |
profile
|
Profile | None
|
The profile whose unreplaced entries are kept. |
None
|
full_elf
|
bool
|
How the calls' graphs are packaged. |
required |
timing
|
Timing
|
How each setting's steps are timed. |
Timing()
|
repeats
|
int
|
How many times each setting's steps are timed. |
9
|
cache
|
Cache | None
|
Where each time is kept across runs ( |
None
|
remeasure
|
bool
|
Time again what |
False
|
cycles
|
Mapping[str, int] | None
|
Kernel cycles per call by recipe key ( |
None
|
core_mhz
|
float | None
|
The cores' clock the runtime reads ( |
None
|
log
|
Callable[[str], None]
|
Where progress and the gains found are logged. |
print
|
Source code in iron/tune/autotune.py
tune
¶
Tune a model's profile on this NPU.
python -m iron.lm.tune <application> times its calls' operators and
rewrites profiles/<device>.json with the fastest settings found;
review and commit the change with git diff.
contexts
¶
At most count contexts halving down from top, multiples of unit.
Ascending, each weighted by the share of a uniform context in
(0, top] nearest it (the trapezoid rule): the weights sum to 1.
Source code in iron/lm/tune.py
arguments
¶
arguments(parser: ArgumentParser) -> None
Add the flags a decoder's calls follow from.
The checkpoint whose header gives the weights' shapes, the contexts attention over the cache is timed at, and the sampling Sample is timed at.
Source code in iron/lm/tune.py
calls
¶
calls(model: CausalLM, position: int, token: int, context: int, points: int, sampler: Sampler) -> list[Timed]
Return the calls a decoder's designs are timed in.
The decode step and the prompt chunk, each over contexts up to
context, points of them, and Sample on the draw rows
sampler gives.
Source code in iron/lm/tune.py
application_model
¶
Return name's model class, name dotted under iron.applications.
Of those its package exports, the one that names its calls
(tune_calls).
Raises:
| Type | Description |
|---|---|
ValueError
|
|
Source code in iron/lm/tune.py
main
¶
Run the command line that tunes name's profile, into its profiles directory.
The calls model plans (tune_calls, on the flags
tune_arguments adds).
Source code in iron/lm/tune.py
cli
¶
python -m iron.lm.tune <application>: tune <application>'s profile.
Tunes with main, its model found by application_model;
--help after <application> lists the rest of its flags.