-
Notifications
You must be signed in to change notification settings - Fork 47
Add iris.bench benchmarking framework #484
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+972
−0
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
c034db2
Add iris.bench benchmarking framework
mawad-amd 46198d8
Support num_ranks as a sweep axis, add --skip, rename shmem to ctx
mawad-amd 24b8059
Apply Ruff auto-fixes
github-actions[bot] 158cec2
Add sample all-gather benchmark using iris.bench
mawad-amd f6251d8
Document execution model and API semantics in docstrings
mawad-amd d5898d1
Use auto-discovered free port instead of hardcoded 29500
mawad-amd fe873a4
Use file:// rendezvous instead of TCP port discovery
mawad-amd 55bb345
Switch from mp.spawn to elastic_launch (programmatic torchrun)
mawad-amd 353b067
Move _dtype_str next to _DTYPE_MAP
mawad-amd 7a609c4
Remove dash-line section dividers
mawad-amd eace2d9
Remove extra whitespace between section comments and code
mawad-amd File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| #!/usr/bin/env python3 | ||
| # SPDX-License-Identifier: MIT | ||
| # Copyright (c) 2026 Advanced Micro Devices, Inc. All rights reserved. | ||
|
|
||
| """Sample benchmark using iris.bench — all-gather collective.""" | ||
|
|
||
| import torch | ||
| import iris.bench as bench | ||
| from iris.ccl import Config | ||
|
|
||
|
|
||
| @bench.register | ||
| @bench.axis("num_ranks", [2, 4, 8]) | ||
| @bench.axis("M", [1024, 4096, 16384]) | ||
| @bench.axis("N", [1024, 4096]) | ||
| @bench.axis("dtype", [torch.float16, torch.bfloat16]) | ||
| def all_gather(state, ctx): | ||
| M, N, dtype = state["M"], state["N"], state["dtype"] | ||
| world_size = ctx.get_num_ranks() | ||
|
|
||
| inp = ctx.zeros((M, N), dtype=dtype) | ||
| out = ctx.zeros((world_size * M, N), dtype=dtype) | ||
| inp.fill_(float(ctx.get_rank() + 1)) | ||
|
|
||
| total_bytes = (world_size - 1) * M * N * inp.element_size() | ||
| state.set_bytes(total_bytes) | ||
|
|
||
| config = Config(use_gluon=False) | ||
| state.exec(lambda: ctx.ccl.all_gather(out, inp, config=config)) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| bench.main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,136 @@ | ||
| # SPDX-License-Identifier: MIT | ||
| # Copyright (c) 2026 Advanced Micro Devices, Inc. All rights reserved. | ||
|
|
||
| """ | ||
| iris.bench — GPU Benchmarking Framework | ||
|
|
||
| A declarative benchmarking framework for iris that eliminates boilerplate. | ||
| Write ~25 lines instead of ~350 to benchmark a GPU kernel. | ||
|
|
||
| Execution Model | ||
| --------------- | ||
|
|
||
| Every benchmark function has the signature ``fn(state, ctx)`` where *state* | ||
| is a :class:`State` object and *ctx* is an :class:`~iris.Iris` context. The | ||
| framework calls each function once per parameter combination. Inside the | ||
| function you do three things: | ||
|
|
||
| 1. **Setup** — allocate tensors, build configs, fill data. This code runs | ||
| **once** per parameter combination and is **not timed**. | ||
|
|
||
| 2. **Declare metrics** — call ``state.set_bytes(n)`` and/or | ||
| ``state.set_flops(n)`` so the framework can compute bandwidth / TFLOPS. | ||
|
|
||
| 3. **Register the kernel** — call ``state.exec(fn)`` with the callable to | ||
| time. ``exec`` does **not** run the callable; it stores it. After your | ||
| function returns, the framework passes it to ``iris.do_bench()`` which | ||
| handles warmup, cache clearing, barrier synchronization, and CUDA-event | ||
| timing. | ||
|
|
||
| The callable registered via ``state.exec()`` is invoked | ||
| ``1 + n_warmup + n_repeat`` times total. Only the last ``n_repeat`` | ||
| invocations are timed. | ||
|
|
||
| Per-Iteration Reset (``preamble_fn``) | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
|
||
| If you need to reset state before **each** invocation (zero output buffers, | ||
| reset locks, reinitialize a workspace), pass a ``preamble_fn``:: | ||
|
|
||
| state.exec( | ||
| lambda: ctx.ccl.all_gather(out, inp, config=config), | ||
| preamble_fn=lambda: out.zero_(), | ||
| ) | ||
|
|
||
| ``preamble_fn`` runs before every invocation (warmup and timed) but is | ||
| **not timed** — it executes before the CUDA start event is recorded. It can | ||
| be as heavyweight as needed without affecting measured results. | ||
|
|
||
| The ``num_ranks`` Axis | ||
| ~~~~~~~~~~~~~~~~~~~~~~ | ||
|
|
||
| ``num_ranks`` is a special axis. It controls how many GPU processes are | ||
| spawned. The framework collects all unique ``num_ranks`` values across | ||
| registered benchmarks, then does a separate ``mp.spawn()`` for each value. | ||
| Other axes are iterated inside the worker processes. | ||
|
|
||
| If no ``num_ranks`` axis is declared, the benchmark runs with 8 ranks. | ||
|
|
||
| Axes & Parameter Sweeps | ||
| ~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
|
||
| Stack multiple ``@bench.axis`` decorators to define a sweep. The framework | ||
| generates the Cartesian product of all axes. The outermost ``@axis`` | ||
| decorator is the slowest-varying axis in the output table. | ||
|
|
||
| CLI Overrides | ||
| ~~~~~~~~~~~~~ | ||
|
|
||
| Any axis can be overridden or filtered from the command line: | ||
|
|
||
| - ``--axis_M=1024,2048`` — replace the M axis with these values. | ||
| - ``--axis_M=pow2:8:12`` — replace with ``[256, 512, 1024, 2048, 4096]``. | ||
| - ``--axis_dtype=fp16`` — run only float16. | ||
| - ``--skip_num_ranks=1,2`` — exclude 1- and 2-rank runs. | ||
| - ``--benchmark_filter=all_gather`` — regex filter on benchmark name. | ||
|
|
||
| Example | ||
| ------- | ||
|
|
||
| :: | ||
|
|
||
| import torch | ||
| import iris.bench as bench | ||
| from iris.ccl import Config | ||
|
|
||
| @bench.register | ||
| @bench.axis("num_ranks", [2, 4, 8]) | ||
| @bench.axis("M", bench.power_of_two(8, 13)) | ||
| @bench.axis("N", [256, 512, 1024]) | ||
| @bench.axis("dtype", [torch.float16, torch.float32]) | ||
| def all_gather(state, ctx): | ||
| M, N, dtype = state["M"], state["N"], state["dtype"] | ||
| world_size = ctx.get_num_ranks() | ||
|
|
||
| inp = ctx.zeros((M, N), dtype=dtype) | ||
| out = ctx.zeros((world_size * M, N), dtype=dtype) | ||
| inp.fill_(float(ctx.get_rank() + 1)) | ||
|
|
||
| state.set_bytes((world_size - 1) * M * N * inp.element_size()) | ||
|
|
||
| config = Config(use_gluon=False) | ||
| state.exec(lambda: ctx.ccl.all_gather(out, inp, config=config)) | ||
|
|
||
| if __name__ == "__main__": | ||
| bench.main() | ||
|
|
||
| Run:: | ||
|
|
||
| python bench_all_gather.py | ||
| python bench_all_gather.py --skip_num_ranks=2 | ||
| python bench_all_gather.py --axis_M=1024 --benchmark_format=json | ||
| """ | ||
|
|
||
| from ._core import ( | ||
| AxisDef, | ||
| BenchmarkDef, | ||
| Result, | ||
| State, | ||
| axis, | ||
| linear_range, | ||
| power_of_two, | ||
| register, | ||
| ) | ||
| from ._runner import main | ||
|
|
||
| __all__ = [ | ||
| "AxisDef", | ||
| "BenchmarkDef", | ||
| "Result", | ||
| "State", | ||
| "axis", | ||
| "linear_range", | ||
| "main", | ||
| "power_of_two", | ||
| "register", | ||
| ] | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.