CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
263 lines
10 KiB
Python
263 lines
10 KiB
Python
#!/usr/bin/env python3
|
|
# Copyright (c) Advanced Micro Devices, Inc.
|
|
# SPDX-License-Identifier: MIT
|
|
|
|
"""Pre-commit hook: verify gfx*_sets.yaml files.
|
|
|
|
Checks:
|
|
1. Every metric ID maps to the correct metric name in the analysis config.
|
|
2. Each set's counters fit within a single profiling pass (per-block limits
|
|
from perfmon_config in mi_gpu_spec.yaml).
|
|
|
|
Metric ID format X.Y.Z:
|
|
X = panel_config_id // 100
|
|
Y = metric_table_id % 100 (metric_table_id = X*100 + Y)
|
|
Z = 0-indexed position of the metric within that table's ordered metric dict
|
|
"""
|
|
|
|
import sys
|
|
from collections import defaultdict
|
|
from pathlib import Path
|
|
|
|
import yaml
|
|
|
|
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
|
SETS_DIR = PROJECT_ROOT / "src" / "rocprof_compute_soc" / "profile_configs" / "sets"
|
|
ANALYSIS_DIR = PROJECT_ROOT / "src" / "rocprof_compute_soc" / "analysis_configs"
|
|
GPU_SPEC_PATH = PROJECT_ROOT / "src" / "utils" / "mi_gpu_spec.yaml"
|
|
|
|
# Make src/ importable so we can reuse the canonical counter definitions.
|
|
sys.path.insert(0, str(PROJECT_ROOT / "src"))
|
|
from utils.utils_common import canonical_config_arch # noqa: E402
|
|
from utils.utils_counter_defs import ( # noqa: E402
|
|
counter_to_block,
|
|
extract_counters_and_variables,
|
|
)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def resolve_metric_id(metric_id: str) -> tuple[int | None, int | None]:
|
|
"""Parse 'X.Y.Z' into (metric_table_id, metric_index)."""
|
|
tokens = metric_id.split(".")
|
|
if len(tokens) < 3:
|
|
return None, None
|
|
try:
|
|
x, y, z = int(tokens[0]), int(tokens[1]), int(tokens[2])
|
|
except ValueError:
|
|
return None, None
|
|
return x * 100 + y, z
|
|
|
|
|
|
def load_analysis_configs(arch: str) -> dict[int, dict[str, dict]]:
|
|
"""Return {metric_table_id: {metric_name: formula_dict}} for *arch*.
|
|
|
|
Metric name ordering is preserved (dict insertion order), so
|
|
``list(result[table_id])`` gives the ordered name list needed for
|
|
index-based lookups.
|
|
"""
|
|
arch_dir = ANALYSIS_DIR / (canonical_config_arch(arch) or arch)
|
|
if not arch_dir.is_dir():
|
|
return {}
|
|
result: dict[int, dict[str, dict]] = {}
|
|
for config_path in sorted(arch_dir.glob("*.yaml")):
|
|
data = yaml.safe_load(config_path.read_text())
|
|
panel = (data or {}).get("Panel Config", {})
|
|
for source in panel.get("data source", []):
|
|
mt = source.get("metric_table", {})
|
|
table_id = mt.get("id")
|
|
if table_id is None:
|
|
continue
|
|
metrics = mt.get("metric", {})
|
|
if metrics:
|
|
result[table_id] = dict(metrics)
|
|
return result
|
|
|
|
|
|
def load_perfmon_configs() -> dict[str, dict[str, int]]:
|
|
"""Return {gpu_arch: {block: max_counters}} from mi_gpu_spec.yaml."""
|
|
data = yaml.safe_load(GPU_SPEC_PATH.read_text())
|
|
result: dict[str, dict[str, int]] = {}
|
|
for series in data.get("mi_gpu_spec", []):
|
|
for arch_entry in series.get("gpu_archs", []):
|
|
arch = arch_entry.get("gpu_arch")
|
|
perfmon = arch_entry.get("perfmon_config", {})
|
|
if arch and perfmon:
|
|
# Filter out non-block entries like TCC_channels
|
|
result[arch] = {
|
|
k: v
|
|
for k, v in perfmon.items()
|
|
if isinstance(v, int) and "_" not in k
|
|
}
|
|
return result
|
|
|
|
|
|
def load_gpu_series_mapping() -> dict[str, str]:
|
|
"""Return {gpu_arch: gpu_series} mapping from mi_gpu_spec.yaml."""
|
|
data = yaml.safe_load(GPU_SPEC_PATH.read_text())
|
|
result: dict[str, str] = {}
|
|
for series in data.get("mi_gpu_spec", []):
|
|
gpu_series = series.get("gpu_series")
|
|
if not gpu_series:
|
|
continue
|
|
for arch_entry in series.get("gpu_archs", []):
|
|
arch = arch_entry.get("gpu_arch")
|
|
if arch:
|
|
# Store uppercase to match mi_gpu_specs.get_gpu_series() behavior
|
|
result[arch] = gpu_series.upper()
|
|
return result
|
|
|
|
|
|
def _flatten_formula_values(d: dict) -> str:
|
|
"""Recursively collect all string values from a metric formula dict."""
|
|
parts: list[str] = []
|
|
for v in d.values():
|
|
if isinstance(v, str):
|
|
parts.append(v)
|
|
elif isinstance(v, dict):
|
|
parts.append(_flatten_formula_values(v))
|
|
return "\n".join(parts)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Validation (single pass over all sets files)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def validate() -> list[str]:
|
|
"""Run all checks, return error messages."""
|
|
errors: list[str] = []
|
|
perfmon_configs = load_perfmon_configs()
|
|
gpu_series_map = load_gpu_series_mapping()
|
|
shared_arch_expansions = {"gfx115x": ["gfx1150", "gfx1151", "gfx1152", "gfx1153"]}
|
|
|
|
for sets_path in sorted(SETS_DIR.glob("gfx*_sets.yaml")):
|
|
arch = sets_path.stem.replace("_sets", "")
|
|
sets_data = yaml.safe_load(sets_path.read_text())
|
|
analysis = load_analysis_configs(arch)
|
|
target_arches = shared_arch_expansions.get(arch, [arch])
|
|
|
|
for s in sets_data.get("sets", []):
|
|
set_option = s.get("set_option", "<unknown>")
|
|
formula_texts: list[str] = []
|
|
|
|
for entry in s.get("metric", []):
|
|
for metric_id_raw, expected_name in entry.items():
|
|
metric_id = str(metric_id_raw)
|
|
expected_name = str(expected_name)
|
|
table_id, idx = resolve_metric_id(metric_id)
|
|
|
|
# --- Check 1: metric ID maps to correct name ---
|
|
if table_id is None or idx is None:
|
|
errors.append(
|
|
f"[{arch}] set '{set_option}': metric ID "
|
|
f"'{metric_id}' is not fully qualified (need X.Y.Z)"
|
|
)
|
|
continue
|
|
|
|
if table_id not in analysis:
|
|
errors.append(
|
|
f"[{arch}] set '{set_option}': metric_table "
|
|
f"{table_id} not found in analysis configs "
|
|
f"(metric ID {metric_id})"
|
|
)
|
|
continue
|
|
|
|
table_metrics = analysis[table_id]
|
|
metric_names = list(table_metrics)
|
|
if idx >= len(metric_names):
|
|
errors.append(
|
|
f"[{arch}] set '{set_option}': index {idx} out of "
|
|
f"range for table {table_id} which has "
|
|
f"{len(metric_names)} metrics (metric ID {metric_id}). "
|
|
f"Metrics: {metric_names}"
|
|
)
|
|
continue
|
|
|
|
actual = metric_names[idx]
|
|
# Allow the sets file to use a qualified name that
|
|
# includes context from the panel/table title
|
|
# (e.g. "vL1D Cache Utilization" matches "Utilization"
|
|
# when the table is under the vL1D Cache panel).
|
|
if actual != expected_name and not expected_name.endswith(
|
|
" " + actual
|
|
):
|
|
errors.append(
|
|
f"[{arch}] set '{set_option}': metric ID "
|
|
f"{metric_id} is labeled '{expected_name}' but "
|
|
f"analysis config (table {table_id}) has "
|
|
f"'{actual}' at index {idx}. "
|
|
f"Full table: {metric_names}"
|
|
)
|
|
|
|
# Collect formula text for single-pass check
|
|
formula = table_metrics.get(actual)
|
|
if formula is not None:
|
|
formula_texts.append(
|
|
_flatten_formula_values(formula)
|
|
if isinstance(formula, dict)
|
|
else str(formula)
|
|
)
|
|
|
|
if not formula_texts:
|
|
continue
|
|
|
|
for target_arch in target_arches:
|
|
gpu_series = gpu_series_map.get(target_arch)
|
|
if not gpu_series:
|
|
errors.append(
|
|
f"[{target_arch}] arch missing from mi_gpu_spec.yaml; "
|
|
f"add it under mi_gpu_spec.<series>.gpu_archs"
|
|
)
|
|
continue
|
|
|
|
limits = perfmon_configs.get(target_arch)
|
|
if limits is None:
|
|
continue
|
|
|
|
counters, _ = extract_counters_and_variables(
|
|
"\n".join(formula_texts), gpu_series
|
|
)
|
|
# *_ACCUM is the per-bucket alias for SQ_ACCUM_PREV_HIRES, which is
|
|
# injected automatically by the profiler for level counters
|
|
counters = {c for c in counters if not c.endswith("_ACCUM")}
|
|
block_counters: dict[str, set[str]] = defaultdict(set)
|
|
for c in counters:
|
|
block_counters[counter_to_block(c)].add(c)
|
|
|
|
for block, block_ctrs in sorted(block_counters.items()):
|
|
if block not in limits:
|
|
continue
|
|
if len(block_ctrs) > limits[block]:
|
|
errors.append(
|
|
f"[{target_arch}] set '{set_option}': block {block} needs "
|
|
f"{len(block_ctrs)} counters but limit is "
|
|
f"{limits[block]}. "
|
|
f"Counters: {sorted(block_ctrs)}"
|
|
)
|
|
|
|
return errors
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Main
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def main() -> int:
|
|
errors = validate()
|
|
if errors:
|
|
print("Sets metric ID validation failed:\n")
|
|
for e in errors:
|
|
print(f" ERROR: {e}")
|
|
print(f"\n{len(errors)} error(s) found.")
|
|
return 1
|
|
print("Sets metric ID validation passed.")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|