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
202 lines
7.3 KiB
Python
202 lines
7.3 KiB
Python
"""Tests for perfxpert.tools.arch."""
|
|
|
|
import pytest
|
|
|
|
from perfxpert.tools import arch
|
|
from perfxpert.tools._class import ToolClass
|
|
|
|
|
|
_STATIC_SPEC_FIELDS = (
|
|
"name",
|
|
"codename",
|
|
"peak_fp64_tflops",
|
|
"peak_fp32_tflops",
|
|
"peak_fp16_tflops",
|
|
"peak_bf16_tflops",
|
|
"peak_fp8_tflops",
|
|
"peak_int8_tops",
|
|
"memory_bandwidth_tbs",
|
|
"cu_count",
|
|
"lds_kb",
|
|
"lds_per_cu_kb",
|
|
"wave_size",
|
|
"max_vgprs_per_thread",
|
|
"vgprs_per_simd",
|
|
"simds_per_cu",
|
|
"max_waves_per_simd",
|
|
)
|
|
|
|
_OCCUPANCY_FIELD_MAP = {
|
|
"max_waves_per_simd": "max_waves_per_simd",
|
|
"vgprs_per_simd": "vgprs_per_simd",
|
|
"lds_per_cu_kb": "lds_per_cu_kb",
|
|
"wavefront_size": "wave_size",
|
|
"simds_per_cu": "simds_per_cu",
|
|
}
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _disable_runtime_discovery(monkeypatch):
|
|
monkeypatch.setattr(arch, "_runtime_specs_for_gfx", lambda gfx_id: {})
|
|
|
|
|
|
def _expected_ridge_point(specs, dtype="fp32"):
|
|
peak_key = arch._RIDGE_PEAK_KEYS.get(dtype, "peak_fp32_tflops")
|
|
peak_tflops = float(specs.get(peak_key) or specs.get("peak_fp32_tflops") or 0.0)
|
|
bandwidth_tbs = float(specs.get("memory_bandwidth_tbs") or 0.0)
|
|
if bandwidth_tbs <= 0:
|
|
return float(specs.get("ridge_point") or 0.0)
|
|
return round(peak_tflops / bandwidth_tbs, 1)
|
|
|
|
|
|
@pytest.mark.parametrize("gfx_id, static_specs", sorted(arch._gpu_specs().items()))
|
|
def test_lookup_peaks_returns_static_specs_for_known_archs(gfx_id, static_specs):
|
|
peaks = arch.lookup_peaks(gfx_id)
|
|
|
|
assert peaks["runtime_discovered"] is False
|
|
for field in _STATIC_SPEC_FIELDS:
|
|
assert peaks[field] == static_specs[field]
|
|
assert peaks["spec_sources"][field] == "gpu_specs.yaml"
|
|
|
|
assert peaks["ridge_point"] == pytest.approx(_expected_ridge_point(static_specs))
|
|
for dtype in arch._RIDGE_PEAK_KEYS:
|
|
assert peaks["ridge_points"][dtype] == pytest.approx(_expected_ridge_point(static_specs, dtype=dtype))
|
|
|
|
|
|
def test_lookup_peaks_exposes_runtime_caps_for_occupancy_users():
|
|
occupancy_table = arch.occupancy_specs_table()
|
|
for gfx_id, static_specs in arch._gpu_specs().items():
|
|
peaks = arch.lookup_peaks(gfx_id)
|
|
|
|
for occupancy_field, spec_field in _OCCUPANCY_FIELD_MAP.items():
|
|
assert occupancy_table[gfx_id][occupancy_field] == int(static_specs[spec_field])
|
|
assert peaks[spec_field] == static_specs[spec_field]
|
|
|
|
|
|
def test_lookup_peaks_covers_all_known_archs():
|
|
required_fields = {"name", "peak_fp64_tflops"}
|
|
for gfx_id in arch._gpu_specs():
|
|
result = arch.lookup_peaks(gfx_id)
|
|
assert required_fields.issubset(result)
|
|
|
|
|
|
def test_lookup_peaks_unknown_arch_raises():
|
|
unknown_gfx = "__unknown_arch_for_test__"
|
|
with pytest.raises(KeyError) as exc:
|
|
arch.lookup_peaks(unknown_gfx)
|
|
assert unknown_gfx in str(exc.value)
|
|
assert "known" in str(exc.value).lower() or "available" in str(exc.value).lower()
|
|
|
|
|
|
@pytest.mark.parametrize("gfx_id", sorted(arch._gpu_specs().keys()))
|
|
def test_lookup_peaks_keeps_static_specs_for_known_archs_by_default(monkeypatch, gfx_id):
|
|
static_specs = arch._gpu_specs()[gfx_id]
|
|
runtime = {
|
|
"gfx_id": gfx_id,
|
|
"name": f"Runtime {gfx_id}",
|
|
"cu_count": int(static_specs["cu_count"]) + 1,
|
|
"memory_bandwidth_tbs": float(static_specs["memory_bandwidth_tbs"]) + 0.125,
|
|
"spec_sources": {
|
|
"name": "rocminfo",
|
|
"cu_count": "rocminfo",
|
|
"memory_bandwidth_tbs": "amd-smi",
|
|
},
|
|
}
|
|
monkeypatch.setattr(
|
|
arch,
|
|
"_runtime_specs_for_gfx",
|
|
lambda requested: runtime if requested == gfx_id else {},
|
|
)
|
|
|
|
peaks = arch.lookup_peaks(gfx_id)
|
|
|
|
assert peaks["runtime_discovered"] is False
|
|
assert peaks["name"] == static_specs["name"]
|
|
assert peaks["cu_count"] == static_specs["cu_count"]
|
|
assert peaks["memory_bandwidth_tbs"] == static_specs["memory_bandwidth_tbs"]
|
|
|
|
|
|
@pytest.mark.parametrize("gfx_id", sorted(arch._gpu_specs().keys()))
|
|
def test_lookup_peaks_prefers_runtime_specs_for_local_init_when_requested(monkeypatch, gfx_id):
|
|
static_specs = arch._gpu_specs()[gfx_id]
|
|
runtime_memory_bandwidth = float(static_specs["memory_bandwidth_tbs"]) + 0.125
|
|
runtime_cu_count = int(static_specs["cu_count"]) + 1
|
|
runtime_clock_mhz = 2100
|
|
runtime_peak_fp32_tflops = float(static_specs["peak_fp32_tflops"]) + 999.0
|
|
runtime = {
|
|
"gfx_id": gfx_id,
|
|
"name": f"Runtime {gfx_id}",
|
|
"cu_count": runtime_cu_count,
|
|
"max_sclk_mhz": runtime_clock_mhz,
|
|
"peak_fp32_tflops": runtime_peak_fp32_tflops,
|
|
"memory_bandwidth_tbs": runtime_memory_bandwidth,
|
|
"spec_sources": {
|
|
"name": "rocminfo",
|
|
"cu_count": "rocminfo",
|
|
"max_sclk_mhz": "amd-smi",
|
|
"peak_fp32_tflops": "derived-from-runtime-topology",
|
|
"memory_bandwidth_tbs": "amd-smi",
|
|
},
|
|
}
|
|
monkeypatch.setattr(
|
|
arch,
|
|
"_runtime_specs_for_gfx",
|
|
lambda requested: runtime if requested == gfx_id else {},
|
|
)
|
|
|
|
peaks = arch.lookup_peaks(gfx_id, prefer_runtime=True)
|
|
|
|
assert peaks["runtime_discovered"] is True
|
|
assert peaks["name"] == f"Runtime {gfx_id}"
|
|
assert peaks["cu_count"] == runtime_cu_count
|
|
assert peaks["max_sclk_mhz"] == runtime_clock_mhz
|
|
assert peaks["memory_bandwidth_tbs"] == pytest.approx(runtime_memory_bandwidth)
|
|
assert peaks["peak_fp32_tflops"] == static_specs["peak_fp32_tflops"]
|
|
assert peaks["peak_fp64_tflops"] == static_specs["peak_fp64_tflops"]
|
|
assert peaks["static_fallback_keys"]
|
|
assert peaks["spec_sources"]["peak_fp32_tflops"] == "gpu_specs.yaml"
|
|
assert peaks["spec_sources"]["peak_fp64_tflops"] == "gpu_specs.yaml"
|
|
assert peaks["spec_sources"]["memory_bandwidth_tbs"] == "amd-smi"
|
|
|
|
|
|
def test_lookup_peaks_supports_runtime_only_local_gpu(monkeypatch):
|
|
runtime_gfx_id = "gfxlocal"
|
|
runtime_name = "Runtime GPU"
|
|
runtime_peak_fp32_tflops = 1.25
|
|
runtime_peak_fp64_tflops = 0.25
|
|
runtime = {
|
|
"gfx_id": runtime_gfx_id,
|
|
"name": runtime_name,
|
|
"peak_fp32_tflops": runtime_peak_fp32_tflops,
|
|
"peak_fp64_tflops": runtime_peak_fp64_tflops,
|
|
"memory_bandwidth_tbs": 0.0,
|
|
"spec_sources": {"gfx_id": "rocminfo", "peak_fp32_tflops": "derived-from-runtime-topology"},
|
|
}
|
|
monkeypatch.setattr(arch, "_runtime_specs_for_gfx", lambda gfx_id: runtime if gfx_id == runtime_gfx_id else {})
|
|
|
|
peaks = arch.lookup_peaks(runtime_gfx_id)
|
|
|
|
assert peaks["runtime_discovered"] is True
|
|
assert peaks["name"] == runtime_name
|
|
assert peaks["peak_fp32_tflops"] == pytest.approx(runtime_peak_fp32_tflops)
|
|
assert peaks["peak_fp64_tflops"] == pytest.approx(runtime_peak_fp64_tflops)
|
|
assert peaks["ridge_point"] == 0.0
|
|
|
|
|
|
def test_lookup_peaks_is_read_only_class():
|
|
"""MCP exposure policy — lookup tools are READ_ONLY."""
|
|
assert arch.lookup_peaks.__tool_class__ == ToolClass.READ_ONLY
|
|
|
|
|
|
def test_lookup_peaks_is_fast_after_runtime_specs_are_cached(monkeypatch):
|
|
"""Lookup remains cheap once runtime discovery has populated its cache."""
|
|
import time
|
|
|
|
monkeypatch.setattr(arch, "_runtime_specs_for_gfx", lambda gfx_id: {})
|
|
gfx_id = next(iter(arch._gpu_specs()))
|
|
start = time.time()
|
|
arch.lookup_peaks(gfx_id)
|
|
duration_ms = (time.time() - start) * 1000
|
|
# Must be fast (< 50ms even on cold YAML load)
|
|
assert duration_ms < 50, f"too slow: {duration_ms}ms"
|