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
167 lines
7.3 KiB
Python
167 lines
7.3 KiB
Python
# Copyright Advanced Micro Devices, Inc., or its affiliates.
|
|
# SPDX-License-Identifier: MIT
|
|
"""
|
|
CUOccupancy measurement utilities: occupancy formula and custom-kernel ASM parsing.
|
|
|
|
Production code — must not import hip-python or perform GPU runtime queries.
|
|
HIP-based occupancy measurement lives in the test layer:
|
|
Tensile/Tests/unit/occupancy_hip_testutil.py
|
|
"""
|
|
|
|
from math import ceil
|
|
from typing import Optional, Tuple
|
|
|
|
|
|
# ── Occupancy computation formula ─────────────────────────────────────────────
|
|
|
|
def compute_occupancy_from_resources(
|
|
vgpr_count: int,
|
|
lds_bytes: int,
|
|
sgpr_count: int,
|
|
num_threads: int,
|
|
physical_vgpr: int,
|
|
physical_sgpr: int,
|
|
device_lds: int,
|
|
max_waves_per_simd: int,
|
|
) -> int:
|
|
"""Compute CUOccupancy from kernel resource counts (no GPU needed).
|
|
|
|
Mirrors getVgprOccupancy in KernelWriterAssembly.py exactly:
|
|
the workgroup multiplier scales the aligned VGPR footprint (not just the
|
|
wave cap), so >256-thread kernels correctly yield lower VGPR occupancy.
|
|
|
|
For ArchAccUnifiedRegs ISAs the unified physical_vgpr pool (512 on gfx950)
|
|
is divided by vgpr_count (=.amdhsa_next_free_vgpr).
|
|
|
|
Args:
|
|
vgpr_count: Unified VGPR count (from .amdhsa_next_free_vgpr or scan).
|
|
lds_bytes: Static LDS from group_segment_fixed_size.
|
|
sgpr_count: SGPR count.
|
|
num_threads: Block size (NumThreads or .max_flat_workgroup_size).
|
|
physical_vgpr: Hardware VGPR pool per SIMD (512 ArchAccUnifiedRegs; 256 otherwise).
|
|
physical_sgpr: Hardware SGPR pool per SIMD (800 for gfx9).
|
|
device_lds: Total LDS per CU (163840 B for gfx950).
|
|
max_waves_per_simd: Hardware wave cap per SIMD (8 ArchAccUnifiedRegs; 10 otherwise).
|
|
|
|
Returns:
|
|
Max active workgroups per CU (Tensile's CUOccupancy).
|
|
"""
|
|
# Workgroup multiplier: ceil(max(numThreads, 256) / 256) — matches getVgprOccupancy.
|
|
# For >256-thread blocks each SIMD slot holds multiplier waves, so the aligned
|
|
# VGPR footprint per slot scales up by the same factor (Fix #1).
|
|
multiplier = max(int(ceil(num_threads / 256.0)), 1)
|
|
max_occ = max_waves_per_simd // multiplier
|
|
vgpr_align = 4 # AMDGPU VGPR allocation granularity (dwords)
|
|
vgprs_aligned = int(ceil(max(vgpr_count, 1) / vgpr_align)) * vgpr_align * multiplier
|
|
vgpr_occ = physical_vgpr // vgprs_aligned
|
|
sgpr_occ = physical_sgpr // max(sgpr_count, 1) if sgpr_count > 0 else max_waves_per_simd
|
|
lds_occ = device_lds // max(((lds_bytes + 255) // 256) * 256, 256) if lds_bytes > 0 else max_waves_per_simd
|
|
|
|
return max(1, min(vgpr_occ, sgpr_occ, lds_occ, max_occ))
|
|
|
|
|
|
# ── Build-time helpers ────────────────────────────────────────────────────────
|
|
|
|
def _arch_caps_for_kernel(kernel) -> Optional[Tuple[int, int, int, int]]:
|
|
"""Return (physical_vgpr, physical_sgpr, device_lds, max_waves_per_simd) for a kernel.
|
|
|
|
Derived from the kernel's ISA tuple; mirrors rocisa/hardware_caps.hpp constants.
|
|
KernelWriterAssembly passes live rocisa caps directly to
|
|
compute_occupancy_from_asm_source() at runtime; this table is a fallback for
|
|
standalone/test use where rocisa is not yet initialized for the target ISA.
|
|
Must be kept in sync when new ISAs are added to rocisa/hardware_caps.hpp.
|
|
|
|
Returns None for ISAs not explicitly listed (Fix #4) — callers must treat
|
|
None as "cannot compute → skip CUOccupancy override" to avoid emitting
|
|
incorrect caps for unsupported architectures.
|
|
"""
|
|
isa = tuple(kernel.get("ISA", (9, 0, 8)))
|
|
|
|
# ArchAccUnifiedRegs: gfx90a (9,0,10), gfx942 (9,4,2), gfx950 (9,5,0)
|
|
# physical_vgpr = MaxVgpr(256) * 2 = 512 (matches getSourceFileString cap passthrough)
|
|
if isa in {(9, 0, 10), (9, 4, 2), (9, 5, 0)}:
|
|
physical_vgpr = 512
|
|
max_waves_per_simd = 8
|
|
# DeviceLDS per hardware_caps.hpp: gfx950 = 160 KB; gfx90a/gfx942 = 64 KB
|
|
device_lds = 163840 if isa == (9, 5, 0) else 65536
|
|
elif isa[0] == 9:
|
|
# Other gfx9 (e.g. gfx908/gfx906): non-unified, 64 KB LDS, 10 waves
|
|
physical_vgpr = 256
|
|
max_waves_per_simd = 10
|
|
device_lds = 65536
|
|
elif isa == (12, 5, 0):
|
|
# gfx1250: MaxVgpr=1024, ArchAccUnifiedRegs=False, DeviceLDS=320 KB
|
|
# (hardware_caps.hpp: MaxVgpr=1024, deviceLDS=327680, MaxWavesPerSimd=10)
|
|
physical_vgpr = 1024
|
|
max_waves_per_simd = 10
|
|
device_lds = 327680 # 320 * 1024
|
|
else:
|
|
# Unknown or unsupported ISA: return None rather than silently emitting
|
|
# garbage caps. The occupancy formula is gfx9/wave64-specific; callers
|
|
# should skip the CUOccupancy override for unrecognised architectures.
|
|
return None
|
|
|
|
physical_sgpr = 800 # gfx9 family and gfx1250 (hardware_caps.hpp PhysicalMaxSgpr)
|
|
|
|
return physical_vgpr, physical_sgpr, device_lds, max_waves_per_simd
|
|
|
|
|
|
def compute_occupancy_from_asm_source(
|
|
kernel,
|
|
asm_source: str,
|
|
arch_caps: Optional[Tuple[int, int, int, int]] = None,
|
|
) -> Optional[int]:
|
|
"""Parse .amdhsa_* directives from a hand-written custom kernel .s and return CUOccupancy.
|
|
|
|
Used by KernelWriterAssembly.getSourceFileString for custom kernels that bypass the
|
|
normal checkResources path. For ArchAccUnifiedRegs ISAs .amdhsa_next_free_vgpr is
|
|
the combined unified VGPR total (regularVgprs + accVgprs).
|
|
|
|
The regex tolerates inline comments after values (e.g. ``.amdhsa_next_free_vgpr 256 // vgprs``).
|
|
|
|
Args:
|
|
kernel: Kernel dict with ``"ISA"`` and ``"NumThreads"``.
|
|
asm_source: Full text of the .s file.
|
|
arch_caps: Optional ``(physical_vgpr, physical_sgpr, device_lds, max_waves_per_simd)``
|
|
from rocisa; when None, falls back to :func:`_arch_caps_for_kernel`.
|
|
|
|
Returns:
|
|
Computed CUOccupancy (>= 1) or None if any required directive is missing.
|
|
"""
|
|
import re as _re
|
|
|
|
def _parse(directive: str, text: str) -> Optional[int]:
|
|
m = _re.search(r'\.' + directive + r'\s+(\d+)', text)
|
|
return int(m.group(1)) if m else None
|
|
|
|
vgpr_total = _parse('amdhsa_next_free_vgpr', asm_source)
|
|
sgpr_total = _parse('amdhsa_next_free_sgpr', asm_source)
|
|
lds_size = _parse('amdhsa_group_segment_fixed_size', asm_source)
|
|
|
|
if vgpr_total is None or sgpr_total is None or lds_size is None:
|
|
return None
|
|
|
|
num_threads = kernel.get('NumThreads', 256)
|
|
if arch_caps is not None:
|
|
phy_vgpr, phy_sgpr, device_lds, max_waves = arch_caps
|
|
else:
|
|
_caps = _arch_caps_for_kernel(kernel)
|
|
if _caps is None:
|
|
# Unsupported ISA: skip rather than emit wrong occupancy.
|
|
return None
|
|
phy_vgpr, phy_sgpr, device_lds, max_waves = _caps
|
|
|
|
try:
|
|
return compute_occupancy_from_resources(
|
|
vgpr_count=vgpr_total,
|
|
lds_bytes=lds_size,
|
|
sgpr_count=sgpr_total,
|
|
num_threads=num_threads,
|
|
physical_vgpr=phy_vgpr,
|
|
physical_sgpr=phy_sgpr,
|
|
device_lds=device_lds,
|
|
max_waves_per_simd=max_waves,
|
|
)
|
|
except Exception:
|
|
return None
|