diff --git a/benchmarks/rocm/bench_block_sparse_attention.py b/benchmarks/rocm/bench_block_sparse_attention.py index fd9ecb25833..9ebbc68924d 100644 --- a/benchmarks/rocm/bench_block_sparse_attention.py +++ b/benchmarks/rocm/bench_block_sparse_attention.py @@ -236,6 +236,10 @@ def _sweep(kinds, dry_run_iters: int, repeat_iters: int, nb: int, seed: int) -> "num_kv_heads": num_kv, "density": density, "num_blocks": nb, + # Layout and inputs are both seed-derived, so two + # seeds at one density time differently; without + # this the rows are indistinguishable. + "seed": seed, } try: if kind == "variable": @@ -402,6 +406,10 @@ def main() -> None: "block size would shrink the sparse problem below the dense baseline." ) + # --seed reached only the block mask; q/k/v came from the global RNG, + # so the seed recorded per row did not pin --accuracy's inputs. + torch.manual_seed(args.seed) + for key, value in _provenance().items(): print(f"# {key}: {value}") diff --git a/benchmarks/rocm/testlist_rocm.txt b/benchmarks/rocm/testlist_rocm.txt index 4c3621bb29a..4f4101b6f3e 100644 --- a/benchmarks/rocm/testlist_rocm.txt +++ b/benchmarks/rocm/testlist_rocm.txt @@ -78,12 +78,16 @@ --routine BatchPrefillWithRaggedKVCacheWrapper --backends fa2 auto --batch_size 16 --s_qo 1024 --s_kv 1024 --num_qo_heads 64 --num_kv_heads 8 --head_dim_qk 128 --head_dim_vo 128 --causal --q_dtype bfloat16 --kv_dtype bfloat16 --refcheck --generate_repro_command --case_tag "Llama-3.1-70B" --routine BatchPrefillWithRaggedKVCacheWrapper --backends fa2 auto --batch_size 4 --s_qo 4096 --s_kv 4096 --num_qo_heads 64 --num_kv_heads 8 --head_dim_qk 128 --head_dim_vo 128 --causal --q_dtype bfloat16 --kv_dtype bfloat16 --refcheck --generate_repro_command --case_tag "Llama-3.1-70B" -## Norm -- bf16 and fp16 +## Norm -- bf16 # The backend column reads "cuda": these routines take no backend argument, so # each op runs at its own default, which for norm and rope is native. For the # native-vs-AITER comparison run rocm/bench_norm.py with no flag; --aa there is # native against itself, i.e. the noise floor a ratio has to clear. # +# fp16 is deliberately absent: vec_size is gcd(16/sizeof(T), d) and both +# 2-byte types give the same one, so fp16 would re-run this path for +# numerical noise alone. +# # hidden_size 111 is in the sweep on purpose: the native kernel's vec_size is # gcd(16/sizeof(T), d), so an ill-aligned d goes fully scalar. A powers-of-two # sweep never sees it. diff --git a/benchmarks/routines/rocm/support.py b/benchmarks/routines/rocm/support.py index 4aef6ef5108..1e3d6905125 100644 --- a/benchmarks/routines/rocm/support.py +++ b/benchmarks/routines/rocm/support.py @@ -31,8 +31,8 @@ # norm "rmsnorm": ("rmsnorm", _NATIVE), "fused_add_rmsnorm": ("fused_add_rmsnorm", _NATIVE), - "gemma_rmsnorm": ("layernorm", _NATIVE), - "gemma_fused_add_rmsnorm": ("layernorm", _NATIVE), + "gemma_rmsnorm": ("gemma_rmsnorm", _NATIVE), + "gemma_fused_add_rmsnorm": ("gemma_rmsnorm", _NATIVE), # rope. apply_rope_with_cos_sin_cache is absent: the routine builds # cos_sin_cache in --input_dtype, but the op requires float32 and # --input_dtype offers none, so it fails on CUDA too. diff --git a/tests/rocm/test_arch_caps.py b/tests/rocm/test_arch_caps.py index eacdfedccc2..fed1d36ff9b 100644 --- a/tests/rocm/test_arch_caps.py +++ b/tests/rocm/test_arch_caps.py @@ -293,6 +293,46 @@ def test_every_op_the_library_asks_for_is_declared(self): f"{sorted(used - declared)}" ) + def test_benchmark_registry_ops_are_declared(self): + """The benchmark registry names capability ops; check the targets exist. + + Parsed rather than imported: this lane has no torch, and support.py + reaches flashinfer. It is also why the regex scan above cannot cover it + -- support.py passes `op` as a variable, so the strings live in a dict + literal and never appear at a call site. + + `layernorm` sat here until arch_caps stopped declaring it; git merged + both changes cleanly and two Gemma routines silently stopped producing + rows. tests/rocm/test_benchmark_harness.py catches it too, but runs on + no CI lane. + """ + import ast + + root = pathlib.Path(__file__).resolve().parents[2] + src = root / "benchmarks" / "routines" / "rocm" / "support.py" + tree = ast.parse(src.read_text()) + referenced = set() + for node in ast.walk(tree): + if not isinstance(node, ast.Assign): + continue + if not any( + isinstance(t, ast.Name) and t.id == "_ROCM_ROUTINE_TO_CAP_OP" + for t in node.targets + ): + continue + for value in node.value.values: + op = value.elts[0] + assert isinstance(op, ast.Constant), f"non-literal cap op: {op!r}" + referenced.add(op.value) + + assert referenced, "no capability ops parsed; the registry moved or renamed" + assert "rmsnorm" in referenced, "parse found no known op; the shape changed" + declared = {c.op for c in arch_caps.CAPABILITIES if c.backend == "hip"} + assert referenced <= declared, ( + "benchmark registry names capability ops with no hip row: " + f"{sorted(referenced - declared)}" + ) + def test_known_bad_rows_explain_themselves(self): """A gate with no detail is unactionable for whoever hits it.""" for cap in arch_caps.CAPABILITIES: