This guide explains how to extend accelerator-microbenchmarks with new
operations or composite benchmarks.
Every benchmark must inherit from BaseBenchmark and reside in
src/accelerator_microbenchmarks/benchmarks/.
from ..core import BaseBenchmark, registry
import jax.numpy as jnp
import jax
@registry.register("my_new_op")
class MyNewOpBenchmark(BaseBenchmark):
def setup(self, **params):
"""Called once. Use for JIT compilation or constant allocation."""
@jax.jit
def my_op(x):
return x * 2
self._jit_fn = my_op
def generate_inputs(self, **params):
"""Generate test data. Returns a tuple of arguments for run_op."""
size = params.get("size", 1024)
x = jnp.ones((size,))
return (x,)
def run_op(self, x):
"""The core loop operation. Must use jax.block_until_ready internally handled by Base."""
return self._jit_fn(x)To enable theoretical performance analysis, implement these two methods:
def get_total_bytes(self, **params) -> float:
"""Calculate bytes moved to/from HBM."""
size = params.get("size", 1024)
return size * 4 * 2 # 1 read + 1 write of float32
def get_arithmetic_intensity(self, **params) -> float:
"""Flops per Byte moved."""
size = params.get("size", 1024)
flops = size # 1 multiply per element
return flops / self.get_total_bytes(**params)The new benchmark implementations must be in
src/accelerator_microbenchmarks/benchmarks/ to auto import your new module.
The benchmark_loader.py discovers new benchmarks and loads them inside the
main function by calling:
def load_all_benchmarks():
# ... existing imports- Mesh Awareness: Use
self.meshfor any sharding logic to ensure TPU multi-core compatibility. - Micro-benchmarks: Keep kernels focused. Avoid complex state management
inside
run_op. - Dtype Flexibility: Always allow
dtypeorin_dtype/out_dtypeto be passed viaparams. - Trace-Ready: Ensure
run_opis a pure JAX function to supportuse_trace_roofline: true.
Test your new benchmark using the CLI:
tpums benchmark run my_new_op --size 2048