Files
TheRock/rocm-systems/projects/rocprofiler-compute/tests/test_data_imputation.py
T
derek.holloway 7502bf64a4
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
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

1497 lines
50 KiB
Python

# Copyright (c) Advanced Micro Devices, Inc.
# SPDX-License-Identifier: MIT
from pathlib import Path
import pandas as pd
import pytest
import utils.utils_analysis as utils
def seed_perfmon_files(tmp_path: Path, count: int) -> None:
"""Create empty pmc_perf_*.yaml files so the imputation function sees the
expected number of counter buckets. Clears any existing perfmon files
first so the helper is safe to call multiple times in one test."""
perfmon = tmp_path / "perfmon"
perfmon.mkdir(exist_ok=True)
for stale in perfmon.glob("pmc_perf_*.yaml"):
stale.unlink()
for stale in perfmon.glob("*.txt"):
stale.unlink()
for i in range(count):
(perfmon / f"pmc_perf_{i}.yaml").touch()
def test_impute_multiplex_kernel_policy(tmp_path: Path) -> None:
"""Test imputation with kernel policy on a single kernel."""
data = {
"Dispatch_ID": [1, 2, 3],
"GPU_ID": [0, 0, 0],
"Grid_Size": [1024, 512, 1024],
"Workgroup_Size": [64, 64, 64],
"LDS_Per_Workgroup": [32, 32, 32],
"Scratch_Per_Workitem": [0, 0, 0],
"Arch_VGPR": [16, 16, 16],
"Accum_VGPR": [0, 0, 0],
"SGPR": [32, 32, 32],
"Kernel_Name": ["kernel_a", "kernel_a", "kernel_a"],
"Start_Timestamp": [1000, 1200, 1400],
"End_Timestamp": [1500, 1700, 1900],
"Kernel_ID": [1, 1, 1],
"Counter1": [100, None, None],
"Counter2": [None, 500, 300],
}
df = pd.DataFrame(data)
result = utils.impute_counters_iteration_multiplex(df, "kernel", tmp_path)
# Sort by Dispatch_ID to ensure consistent order
result = result.sort_values(by="Dispatch_ID")
assert isinstance(result, pd.DataFrame)
assert len(result) == 3 # Ensure same number of rows
# Assert Counter2 imputed for first dispatch, Counter1 imputed for second dispatch
assert result["Counter2"].iloc[0] == 500
assert result["Counter1"].iloc[1] == 100
def test_impute_multiplex_kernel_launch_params_policy(tmp_path: Path) -> None:
"""Test imputation with kernel_launch_params policy on a single kernel."""
data = {
"Dispatch_ID": [1, 2, 3],
"GPU_ID": [0, 0, 0],
"Grid_Size": [1024, 512, 1024],
"Workgroup_Size": [64, 64, 64],
"LDS_Per_Workgroup": [32, 32, 32],
"Scratch_Per_Workitem": [0, 0, 0],
"Arch_VGPR": [16, 16, 16],
"Accum_VGPR": [0, 0, 0],
"SGPR": [32, 32, 32],
"Kernel_Name": ["kernel_a", "kernel_a", "kernel_a"],
"Start_Timestamp": [1000, 1200, 1400],
"End_Timestamp": [1500, 1700, 1900],
"Kernel_ID": [1, 1, 1],
"Counter1": [100, None, None],
"Counter2": [None, 500, 300],
}
df = pd.DataFrame(data)
result = utils.impute_counters_iteration_multiplex(
df, "kernel_launch_params", tmp_path
)
# Sort by Dispatch_ID to ensure consistent order
result = result.sort_values(by="Dispatch_ID")
# Assert Counter2 imputed for first dispatch, Counter1 imputed for last dispatch
assert result["Counter2"].iloc[0] == 300
assert result["Counter1"].iloc[2] == 100
assert isinstance(result, pd.DataFrame)
assert len(result) == 3
def test_impute_multiplex_kernel_launch_params_no_imputation(tmp_path: Path) -> None:
"""Test imputation with kernel_launch_params when no imputation is possible."""
data = {
"Dispatch_ID": [1, 2, 3],
"GPU_ID": [0, 0, 0],
"Grid_Size": [1024, 1024, 1024],
"Workgroup_Size": [64, 64, 32],
"LDS_Per_Workgroup": [32, 24, 32],
"Scratch_Per_Workitem": [0, 0, 0],
"Arch_VGPR": [16, 16, 16],
"Accum_VGPR": [0, 0, 0],
"SGPR": [32, 32, 32],
"Kernel_Name": ["kernel_a", "kernel_a", "kernel_a"],
"Start_Timestamp": [1000, 1200, 1400],
"End_Timestamp": [1500, 1700, 1900],
"Kernel_ID": [1, 1, 1],
"Counter1": [100, None, 300],
"Counter2": [None, 500, None],
}
df = pd.DataFrame(data)
# Counter1 and Counter2 form 2 round-robin buckets.
num_counter_bucket = 2
seed_perfmon_files(tmp_path, count=num_counter_bucket)
result = utils.impute_counters_iteration_multiplex(
df, "kernel_launch_params", tmp_path
)
# Sort by Dispatch_ID to ensure consistent order
result = result.sort_values(by="Dispatch_ID")
assert isinstance(result, pd.DataFrame)
assert len(result) == 3 # Ensure same number of rows
# Each dispatch still has NaN in the other counter after imputation,
# so all counter columns are nullified for all dispatches.
assert pd.isna(result["Counter1"].iloc[0])
assert pd.isna(result["Counter2"].iloc[0])
assert pd.isna(result["Counter1"].iloc[1])
assert pd.isna(result["Counter2"].iloc[1])
assert pd.isna(result["Counter1"].iloc[2])
assert pd.isna(result["Counter2"].iloc[2])
def test_impute_multiplex_multi_kernel_kernel_policy(tmp_path: Path) -> None:
"""Test imputation with kernel policy on multiple kernels."""
data = {
"Dispatch_ID": [1, 2, 3],
"GPU_ID": [0, 0, 0],
"Grid_Size": [1024, 1024, 512],
"Workgroup_Size": [64, 64, 64],
"LDS_Per_Workgroup": [32, 32, 32],
"Scratch_Per_Workitem": [0, 0, 0],
"Arch_VGPR": [16, 16, 16],
"Accum_VGPR": [0, 0, 0],
"SGPR": [32, 32, 32],
"Kernel_Name": ["kernel_a", "kernel_b", "kernel_a"],
"Start_Timestamp": [1000, 1200, 1400],
"End_Timestamp": [1500, 1700, 1900],
"Kernel_ID": [1, 1, 1],
"Counter1": [100, None, None],
"Counter2": [None, 500, 300],
}
df = pd.DataFrame(data)
result = utils.impute_counters_iteration_multiplex(df, "kernel", tmp_path)
# Sort by Dispatch_ID to ensure consistent order
result = result.sort_values(by="Dispatch_ID")
# Assert Counter1 and Counter2 imputed for first and last dispatches
assert result["Counter2"].iloc[0] == 300
assert result["Counter1"].iloc[2] == 100
assert isinstance(result, pd.DataFrame)
assert len(result) == 3 # Ensure same number of rows
def test_impute_multiplex_multi_kernel_kernel_launch_params_no_imputation(
tmp_path: Path,
) -> None:
"""Test imputation with kernel_launch_params when no imputation is possible."""
data = {
"Dispatch_ID": [1, 2, 3],
"GPU_ID": [0, 0, 0],
"Grid_Size": [1024, 1024, 512],
"Workgroup_Size": [64, 64, 64],
"LDS_Per_Workgroup": [32, 32, 32],
"Scratch_Per_Workitem": [0, 0, 0],
"Arch_VGPR": [16, 16, 16],
"Accum_VGPR": [0, 0, 0],
"SGPR": [32, 32, 32],
"Kernel_Name": ["kernel_a", "kernel_b", "kernel_a"],
"Start_Timestamp": [1000, 1200, 1400],
"End_Timestamp": [1500, 1700, 1900],
"Kernel_ID": [1, 1, 1],
"Counter1": [100, None, None],
"Counter2": [None, 500, 300],
}
df = pd.DataFrame(data)
# Counter1 and Counter2 form 2 round-robin buckets.
num_counter_bucket = 2
seed_perfmon_files(tmp_path, count=num_counter_bucket)
result = utils.impute_counters_iteration_multiplex(
df, "kernel_launch_params", tmp_path
)
# Sort by Dispatch_ID to ensure consistent order
result = result.sort_values(by="Dispatch_ID")
assert isinstance(result, pd.DataFrame)
assert len(result) == 3 # Ensure same number of rows
# Each dispatch still has NaN in the other counter after imputation,
# so all counter columns are nullified for all dispatches.
assert pd.isna(result["Counter1"].iloc[0])
assert pd.isna(result["Counter2"].iloc[0])
assert pd.isna(result["Counter1"].iloc[1])
assert pd.isna(result["Counter2"].iloc[1])
assert pd.isna(result["Counter1"].iloc[2])
assert pd.isna(result["Counter2"].iloc[2])
def test_fewer_dispatches_single_kernel(tmp_path: Path) -> None:
"""
Test imputation with kernel policy on a single kernel with
fewer dispatches than buckets.
1 kernel, 3 counters, only 2 dispatches (missing C3 bucket).
C3 remains NaN because there are no previous_fill_values.
"""
data = {
"Dispatch_ID": [1, 2],
"GPU_ID": [0, 0],
"Grid_Size": [1024, 1024],
"Workgroup_Size": [64, 64],
"LDS_Per_Workgroup": [32, 32],
"Scratch_Per_Workitem": [0, 0],
"Arch_VGPR": [16, 16],
"Accum_VGPR": [0, 0],
"SGPR": [32, 32],
"Kernel_Name": ["kernel_a", "kernel_a"],
"Start_Timestamp": [1000, 1200],
"End_Timestamp": [1500, 1700],
"Kernel_ID": [1, 1],
"C1": [10, None],
"C2": [None, 20],
"C3": [None, None],
}
df = pd.DataFrame(data)
# C1, C2, C3 form 3 round-robin buckets but the kernel only had 2 dispatches.
num_counter_bucket = 3
seed_perfmon_files(tmp_path, count=num_counter_bucket)
result = utils.impute_counters_iteration_multiplex(df, "kernel", tmp_path)
result = result.sort_values(by="Dispatch_ID")
assert isinstance(result, pd.DataFrame)
assert len(result) == 2
# C3 was never collected (NaN on all rows after imputation), so all rows are
# nullified — C1 and C2 are also set to NaN to fully exclude these dispatches.
assert pd.isna(result["C1"].iloc[0])
assert pd.isna(result["C2"].iloc[0])
assert pd.isna(result["C3"].iloc[0])
assert pd.isna(result["C1"].iloc[1])
assert pd.isna(result["C2"].iloc[1])
assert pd.isna(result["C3"].iloc[1])
def test_fewer_dispatches_multiple_kernels_both_incomplete(tmp_path: Path) -> None:
"""
Test imputation with kernel policy on multiple kernels, both incomplete.
kernel_a: buckets {C1}, {C2} (missing C3)
kernel_b: buckets {C1}, {C2} (missing C3)
"""
data = {
"Dispatch_ID": [1, 2, 3, 4],
"GPU_ID": [0, 0, 0, 0],
"Grid_Size": [1024, 1024, 1024, 1024],
"Workgroup_Size": [64, 64, 64, 64],
"LDS_Per_Workgroup": [32, 32, 32, 32],
"Scratch_Per_Workitem": [0, 0, 0, 0],
"Arch_VGPR": [16, 16, 16, 16],
"Accum_VGPR": [0, 0, 0, 0],
"SGPR": [32, 32, 32, 32],
"Kernel_Name": ["kernel_a", "kernel_a", "kernel_b", "kernel_b"],
"Start_Timestamp": [1000, 1200, 1400, 1600],
"End_Timestamp": [1500, 1700, 1900, 2100],
"Kernel_ID": [1, 1, 2, 2],
"C1": [10, None, 40, None],
"C2": [None, 20, None, 60],
"C3": [None, None, None, None],
}
df = pd.DataFrame(data)
# C1, C2, C3 form 3 round-robin buckets but each kernel has only 2 dispatches.
num_counter_bucket = 3
seed_perfmon_files(tmp_path, count=num_counter_bucket)
result = utils.impute_counters_iteration_multiplex(df, "kernel", tmp_path)
result = result.sort_values(by="Dispatch_ID")
assert isinstance(result, pd.DataFrame)
assert len(result) == 4
# kernel_a (dispatches 1-2): C3 never collected → all rows nullified
assert pd.isna(result["C1"].iloc[0])
assert pd.isna(result["C2"].iloc[0])
assert pd.isna(result["C3"].iloc[0])
assert pd.isna(result["C1"].iloc[1])
assert pd.isna(result["C2"].iloc[1])
assert pd.isna(result["C3"].iloc[1])
# kernel_b (dispatches 3-4): C3 never collected → all rows nullified
assert pd.isna(result["C1"].iloc[2])
assert pd.isna(result["C2"].iloc[2])
assert pd.isna(result["C3"].iloc[2])
assert pd.isna(result["C1"].iloc[3])
assert pd.isna(result["C2"].iloc[3])
assert pd.isna(result["C3"].iloc[3])
def test_fewer_dispatches_one_incomplete_one_complete(tmp_path: Path) -> None:
"""
Test imputation with kernel policy on one kernel incomplete, second complete.
kernel_a: 2 dispatches (missing C3 bucket)
kernel_b: 3 dispatches covering all 3 buckets
"""
data = {
"Dispatch_ID": [1, 2, 3, 4, 5],
"GPU_ID": [0, 0, 0, 0, 0],
"Grid_Size": [1024, 1024, 1024, 1024, 1024],
"Workgroup_Size": [64, 64, 64, 64, 64],
"LDS_Per_Workgroup": [32, 32, 32, 32, 32],
"Scratch_Per_Workitem": [0, 0, 0, 0, 0],
"Arch_VGPR": [16, 16, 16, 16, 16],
"Accum_VGPR": [0, 0, 0, 0, 0],
"SGPR": [32, 32, 32, 32, 32],
"Kernel_Name": [
"kernel_a",
"kernel_a",
"kernel_b",
"kernel_b",
"kernel_b",
],
"Start_Timestamp": [1000, 1200, 1400, 1600, 1800],
"End_Timestamp": [1500, 1700, 1900, 2100, 2300],
"Kernel_ID": [1, 1, 2, 2, 2],
"C1": [10, None, 50, None, None],
"C2": [None, 20, None, 60, None],
"C3": [None, None, None, None, 70],
}
df = pd.DataFrame(data)
# C1, C2, C3 form 3 round-robin buckets; kernel_a has only 2 dispatches.
num_counter_bucket = 3
seed_perfmon_files(tmp_path, count=num_counter_bucket)
result = utils.impute_counters_iteration_multiplex(df, "kernel", tmp_path)
result = result.sort_values(by="Dispatch_ID")
assert isinstance(result, pd.DataFrame)
assert len(result) == 5
# kernel_a (dispatches 1-2): C3 never collected → all rows nullified
assert pd.isna(result["C1"].iloc[0])
assert pd.isna(result["C2"].iloc[0])
assert pd.isna(result["C3"].iloc[0])
assert pd.isna(result["C1"].iloc[1])
assert pd.isna(result["C2"].iloc[1])
assert pd.isna(result["C3"].iloc[1])
# kernel_b (dispatches 3-5): all 3 counters fully imputed, no NaN → not nullified
assert result["C1"].iloc[2] == 50
assert result["C2"].iloc[2] == 60
assert result["C3"].iloc[2] == 70
assert result["C1"].iloc[3] == 50
assert result["C2"].iloc[3] == 60
assert result["C3"].iloc[3] == 70
assert result["C1"].iloc[4] == 50
assert result["C2"].iloc[4] == 60
assert result["C3"].iloc[4] == 70
def test_fewer_dispatches_same_kernel_different_launch_params(tmp_path: Path) -> None:
"""
Test imputation with kernel_launch_params on the same kernel
with different launch params.
kernel_launch_params policy splits into 2 groups, each incomplete.
Config 1 (Grid=1024, WG=64, LDS=32): buckets {C1}, {C2}
Config 2 (Grid=512, WG=32, LDS=16): buckets {C1}, {C2}
"""
data = {
"Dispatch_ID": [1, 2, 3, 4],
"GPU_ID": [0, 0, 0, 0],
"Grid_Size": [1024, 1024, 512, 512],
"Workgroup_Size": [64, 64, 32, 32],
"LDS_Per_Workgroup": [32, 32, 16, 16],
"Scratch_Per_Workitem": [0, 0, 0, 0],
"Arch_VGPR": [16, 16, 16, 16],
"Accum_VGPR": [0, 0, 0, 0],
"SGPR": [32, 32, 32, 32],
"Kernel_Name": [
"kernel_a",
"kernel_a",
"kernel_a",
"kernel_a",
],
"Start_Timestamp": [1000, 1200, 1400, 1600],
"End_Timestamp": [1500, 1700, 1900, 2100],
"Kernel_ID": [1, 1, 1, 1],
"C1": [10, None, 30, None],
"C2": [None, 20, None, 40],
"C3": [None, None, None, None],
}
df = pd.DataFrame(data)
# C1, C2, C3 form 3 round-robin buckets but each launch config has 2 dispatches.
num_counter_bucket = 3
seed_perfmon_files(tmp_path, count=num_counter_bucket)
result = utils.impute_counters_iteration_multiplex(
df, "kernel_launch_params", tmp_path
)
result = result.sort_values(by="Dispatch_ID")
assert isinstance(result, pd.DataFrame)
assert len(result) == 4
# Config 1 (dispatches 1-2): C3 never collected → all rows nullified
assert pd.isna(result["C1"].iloc[0])
assert pd.isna(result["C2"].iloc[0])
assert pd.isna(result["C3"].iloc[0])
assert pd.isna(result["C1"].iloc[1])
assert pd.isna(result["C2"].iloc[1])
assert pd.isna(result["C3"].iloc[1])
# Config 2 (dispatches 3-4): C3 never collected → all rows nullified
assert pd.isna(result["C1"].iloc[2])
assert pd.isna(result["C2"].iloc[2])
assert pd.isna(result["C3"].iloc[2])
assert pd.isna(result["C1"].iloc[3])
assert pd.isna(result["C2"].iloc[3])
assert pd.isna(result["C3"].iloc[3])
def test_fewer_dispatches_same_kernel_one_incomplete_one_complete(
tmp_path: Path,
) -> None:
"""
Test imputation with kernel_launch_params on one config incomplete, other complete.
kernel_launch_params policy:
Config 1 (Grid=1024, WG=64, LDS=32): 2 dispatches (missing C3 bucket)
Config 2 (Grid=512, WG=32, LDS=16): 3 dispatches (all 3 buckets)
"""
data = {
"Dispatch_ID": [1, 2, 3, 4, 5],
"GPU_ID": [0, 0, 0, 0, 0],
"Grid_Size": [1024, 1024, 512, 512, 512],
"Workgroup_Size": [64, 64, 32, 32, 32],
"LDS_Per_Workgroup": [32, 32, 16, 16, 16],
"Scratch_Per_Workitem": [0, 0, 0, 0, 0],
"Arch_VGPR": [16, 16, 16, 16, 16],
"Accum_VGPR": [0, 0, 0, 0, 0],
"SGPR": [32, 32, 32, 32, 32],
"Kernel_Name": [
"kernel_a",
"kernel_a",
"kernel_a",
"kernel_a",
"kernel_a",
],
"Start_Timestamp": [1000, 1200, 1400, 1600, 1800],
"End_Timestamp": [1500, 1700, 1900, 2100, 2300],
"Kernel_ID": [1, 1, 1, 1, 1],
"C1": [10, None, 50, None, None],
"C2": [None, 20, None, 60, None],
"C3": [None, None, None, None, 70],
}
df = pd.DataFrame(data)
# C1, C2, C3 form 3 round-robin buckets; the first launch config has 2 dispatches.
num_counter_bucket = 3
seed_perfmon_files(tmp_path, count=num_counter_bucket)
result = utils.impute_counters_iteration_multiplex(
df, "kernel_launch_params", tmp_path
)
result = result.sort_values(by="Dispatch_ID")
assert isinstance(result, pd.DataFrame)
assert len(result) == 5
# Config 1 (dispatches 1-2): C3 never collected → all rows nullified
assert pd.isna(result["C1"].iloc[0])
assert pd.isna(result["C2"].iloc[0])
assert pd.isna(result["C3"].iloc[0])
assert pd.isna(result["C1"].iloc[1])
assert pd.isna(result["C2"].iloc[1])
assert pd.isna(result["C3"].iloc[1])
# Config 2 (dispatches 3-5): all 3 counters fully imputed, no NaN → not nullified
assert result["C1"].iloc[2] == 50
assert result["C2"].iloc[2] == 60
assert result["C3"].iloc[2] == 70
assert result["C1"].iloc[3] == 50
assert result["C2"].iloc[3] == 60
assert result["C3"].iloc[3] == 70
assert result["C1"].iloc[4] == 50
assert result["C2"].iloc[4] == 60
assert result["C3"].iloc[4] == 70
def test_incomplete_last_group_single_kernel(tmp_path: Path) -> None:
"""
Test imputation with kernel policy on a single kernel with incomplete last group.
1 kernel, 2 counters, 3 dispatches (2 buckets, 1 full round + 1 trailing).
The trailing subgroup uses previous_fill_values to fill its gaps.
"""
data = {
"Dispatch_ID": [1, 2, 3],
"GPU_ID": [0, 0, 0],
"Grid_Size": [1024, 1024, 1024],
"Workgroup_Size": [64, 64, 64],
"LDS_Per_Workgroup": [32, 32, 32],
"Scratch_Per_Workitem": [0, 0, 0],
"Arch_VGPR": [16, 16, 16],
"Accum_VGPR": [0, 0, 0],
"SGPR": [32, 32, 32],
"Kernel_Name": ["kernel_a", "kernel_a", "kernel_a"],
"Start_Timestamp": [1000, 1200, 1400],
"End_Timestamp": [1500, 1700, 1900],
"Kernel_ID": [1, 1, 1],
"C1": [10, None, 30],
"C2": [None, 20, None],
}
df = pd.DataFrame(data)
result = utils.impute_counters_iteration_multiplex(df, "kernel", tmp_path)
result = result.sort_values(by="Dispatch_ID")
assert isinstance(result, pd.DataFrame)
assert len(result) == 3
# Subgroup 1 (dispatches 1-2): C1 and C2 imputed within the subgroup
assert result["C1"].iloc[0] == 10
assert result["C2"].iloc[0] == 20
assert result["C1"].iloc[1] == 10
assert result["C2"].iloc[1] == 20
# Subgroup 2 (dispatch 3, incomplete): C2 filled from previous subgroup
# via cross-subgroup ffill; no NaN remains so the row is kept as valid.
assert result["C1"].iloc[2] == 30
assert result["C2"].iloc[2] == 20
def test_incomplete_last_group_multiple_kernels_both_incomplete(tmp_path: Path) -> None:
"""
Test imputation with kernel policy on multiple kernels,
both with incomplete last groups.
kernel_a: 4 dispatches, 3 buckets {C1},{C2},{C3} (incomplete last)
kernel_b: 5 dispatches, 3 buckets {C1},{C2},{C3} (incomplete last)
"""
data = {
"Dispatch_ID": [1, 2, 3, 4, 5, 6, 7, 8, 9],
"GPU_ID": [0, 0, 0, 0, 0, 0, 0, 0, 0],
"Grid_Size": [1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024],
"Workgroup_Size": [64, 64, 64, 64, 64, 64, 64, 64, 64],
"LDS_Per_Workgroup": [32, 32, 32, 32, 32, 32, 32, 32, 32],
"Scratch_Per_Workitem": [0, 0, 0, 0, 0, 0, 0, 0, 0],
"Arch_VGPR": [16, 16, 16, 16, 16, 16, 16, 16, 16],
"Accum_VGPR": [0, 0, 0, 0, 0, 0, 0, 0, 0],
"SGPR": [32, 32, 32, 32, 32, 32, 32, 32, 32],
"Kernel_Name": [
"kernel_a",
"kernel_a",
"kernel_a",
"kernel_a",
"kernel_b",
"kernel_b",
"kernel_b",
"kernel_b",
"kernel_b",
],
"Start_Timestamp": [
1000,
1200,
1400,
1600,
1800,
2000,
2200,
2400,
2600,
],
"End_Timestamp": [
1500,
1700,
1900,
2100,
2300,
2500,
2700,
2900,
3100,
],
"Kernel_ID": [1, 1, 1, 1, 2, 2, 2, 2, 2],
"C1": [10, None, None, 40, 50, None, None, 80, None],
"C2": [None, 20, None, None, None, 60, None, None, 90],
"C3": [None, None, 30, None, None, None, 70, None, None],
}
df = pd.DataFrame(data)
result = utils.impute_counters_iteration_multiplex(df, "kernel", tmp_path)
result = result.sort_values(by="Dispatch_ID")
assert isinstance(result, pd.DataFrame)
assert len(result) == 9
# kernel_a subgroup 1 (dispatches 1-3): all 3 counters imputed within subgroup
assert result["C1"].iloc[0] == 10
assert result["C2"].iloc[0] == 20
assert result["C3"].iloc[0] == 30
assert result["C1"].iloc[1] == 10
assert result["C2"].iloc[1] == 20
assert result["C3"].iloc[1] == 30
assert result["C1"].iloc[2] == 10
assert result["C2"].iloc[2] == 20
assert result["C3"].iloc[2] == 30
# kernel_a subgroup 2 (dispatch 4, incomplete): filled via cross-subgroup ffill
assert result["C1"].iloc[3] == 40
assert result["C2"].iloc[3] == 20
assert result["C3"].iloc[3] == 30
# kernel_b subgroup 1 (dispatches 5-7): all 3 counters imputed within subgroup
assert result["C1"].iloc[4] == 50
assert result["C2"].iloc[4] == 60
assert result["C3"].iloc[4] == 70
assert result["C1"].iloc[5] == 50
assert result["C2"].iloc[5] == 60
assert result["C3"].iloc[5] == 70
assert result["C1"].iloc[6] == 50
assert result["C2"].iloc[6] == 60
assert result["C3"].iloc[6] == 70
# kernel_b subgroup 2 (dispatches 8-9, incomplete): filled via cross-subgroup ffill
assert result["C1"].iloc[7] == 80
assert result["C2"].iloc[7] == 90
assert result["C3"].iloc[7] == 70
assert result["C1"].iloc[8] == 80
assert result["C2"].iloc[8] == 90
assert result["C3"].iloc[8] == 70
def test_incomplete_last_group_one_incomplete_other_complete(tmp_path: Path) -> None:
"""
Test imputation with kernel policy on one kernel incomplete, second kernel complete.
kernel_a: 4 dispatches, 3 buckets {C1},{C2},{C3} (incomplete last)
kernel_b: 6 dispatches, 3 buckets {C1},{C2},{C3} (2 complete rounds)
"""
data = {
"Dispatch_ID": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
"GPU_ID": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"Grid_Size": [
1024,
1024,
1024,
1024,
1024,
1024,
1024,
1024,
1024,
1024,
],
"Workgroup_Size": [64, 64, 64, 64, 64, 64, 64, 64, 64, 64],
"LDS_Per_Workgroup": [32, 32, 32, 32, 32, 32, 32, 32, 32, 32],
"Scratch_Per_Workitem": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"Arch_VGPR": [16, 16, 16, 16, 16, 16, 16, 16, 16, 16],
"Accum_VGPR": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"SGPR": [32, 32, 32, 32, 32, 32, 32, 32, 32, 32],
"Kernel_Name": [
"kernel_a",
"kernel_a",
"kernel_a",
"kernel_a",
"kernel_b",
"kernel_b",
"kernel_b",
"kernel_b",
"kernel_b",
"kernel_b",
],
"Start_Timestamp": [
1000,
1200,
1400,
1600,
1800,
2000,
2200,
2400,
2600,
2800,
],
"End_Timestamp": [
1500,
1700,
1900,
2100,
2300,
2500,
2700,
2900,
3100,
3300,
],
"Kernel_ID": [1, 1, 1, 1, 2, 2, 2, 2, 2, 2],
"C1": [10, None, None, 40, 50, None, None, 80, None, None],
"C2": [None, 20, None, None, None, 60, None, None, 90, None],
"C3": [None, None, 30, None, None, None, 70, None, None, 100],
}
df = pd.DataFrame(data)
result = utils.impute_counters_iteration_multiplex(df, "kernel", tmp_path)
result = result.sort_values(by="Dispatch_ID")
assert isinstance(result, pd.DataFrame)
assert len(result) == 10
# kernel_a subgroup 1 (dispatches 1-3): all 3 counters imputed within subgroup
assert result["C1"].iloc[0] == 10
assert result["C2"].iloc[0] == 20
assert result["C3"].iloc[0] == 30
assert result["C1"].iloc[1] == 10
assert result["C2"].iloc[1] == 20
assert result["C3"].iloc[1] == 30
assert result["C1"].iloc[2] == 10
assert result["C2"].iloc[2] == 20
assert result["C3"].iloc[2] == 30
# kernel_a subgroup 2 (dispatch 4, incomplete): filled via cross-subgroup ffill
assert result["C1"].iloc[3] == 40
assert result["C2"].iloc[3] == 20
assert result["C3"].iloc[3] == 30
# kernel_b subgroup 1 (dispatches 5-7): all 3 counters imputed within subgroup
assert result["C1"].iloc[4] == 50
assert result["C2"].iloc[4] == 60
assert result["C3"].iloc[4] == 70
assert result["C1"].iloc[5] == 50
assert result["C2"].iloc[5] == 60
assert result["C3"].iloc[5] == 70
assert result["C1"].iloc[6] == 50
assert result["C2"].iloc[6] == 60
assert result["C3"].iloc[6] == 70
# kernel_b subgroup 2 (dispatches 8-10): complete round, no nullification
assert result["C1"].iloc[7] == 80
assert result["C2"].iloc[7] == 90
assert result["C3"].iloc[7] == 100
assert result["C1"].iloc[8] == 80
assert result["C2"].iloc[8] == 90
assert result["C3"].iloc[8] == 100
assert result["C1"].iloc[9] == 80
assert result["C2"].iloc[9] == 90
assert result["C3"].iloc[9] == 100
def test_incomplete_last_group_same_kernel_different_launch_params(
tmp_path: Path,
) -> None:
"""
Test imputation with kernel_launch_params on the same kernel
with different launch params.
kernel_launch_params policy, both configs have incomplete last subgroups.
Config 1 (Grid=1024, WG=64, LDS=32): 3 dispatches, 2 buckets
Config 2 (Grid=512, WG=32, LDS=16): 3 dispatches, 2 buckets
"""
data = {
"Dispatch_ID": [1, 2, 3, 4, 5, 6],
"GPU_ID": [0, 0, 0, 0, 0, 0],
"Grid_Size": [1024, 1024, 1024, 512, 512, 512],
"Workgroup_Size": [64, 64, 64, 32, 32, 32],
"LDS_Per_Workgroup": [32, 32, 32, 16, 16, 16],
"Scratch_Per_Workitem": [0, 0, 0, 0, 0, 0],
"Arch_VGPR": [16, 16, 16, 16, 16, 16],
"Accum_VGPR": [0, 0, 0, 0, 0, 0],
"SGPR": [32, 32, 32, 32, 32, 32],
"Kernel_Name": [
"kernel_a",
"kernel_a",
"kernel_a",
"kernel_a",
"kernel_a",
"kernel_a",
],
"Start_Timestamp": [1000, 1200, 1400, 1600, 1800, 2000],
"End_Timestamp": [1500, 1700, 1900, 2100, 2300, 2500],
"Kernel_ID": [1, 1, 1, 1, 1, 1],
"C1": [10, None, 30, 50, None, 70],
"C2": [None, 20, None, None, 60, None],
}
df = pd.DataFrame(data)
result = utils.impute_counters_iteration_multiplex(
df, "kernel_launch_params", tmp_path
)
result = result.sort_values(by="Dispatch_ID")
assert isinstance(result, pd.DataFrame)
assert len(result) == 6
# Config 1 (dispatches 1-3): subgroup 0 (1-2) complete, subgroup 1 (3) filled
# via cross-subgroup ffill; no NaN remains so dispatch 3 is kept as valid.
assert result["C1"].iloc[0] == 10
assert result["C2"].iloc[0] == 20
assert result["C1"].iloc[1] == 10
assert result["C2"].iloc[1] == 20
assert result["C1"].iloc[2] == 30
assert result["C2"].iloc[2] == 20
# Config 2 (dispatches 4-6): subgroup 0 (4-5) complete, subgroup 1 (6) filled
assert result["C1"].iloc[3] == 50
assert result["C2"].iloc[3] == 60
assert result["C1"].iloc[4] == 50
assert result["C2"].iloc[4] == 60
assert result["C1"].iloc[5] == 70
assert result["C2"].iloc[5] == 60
def test_incomplete_last_group_same_kernel_one_incomplete_one_complete(
tmp_path: Path,
) -> None:
"""
Test imputation with kernel_launch_params on the same kernel
with one config incomplete, other complete.
kernel_launch_params policy:
Config 1 (Grid=1024, WG=64, LDS=32): 3 dispatches, incomplete last
Config 2 (Grid=512, WG=32, LDS=16): 4 dispatches, 2 complete rounds
"""
data = {
"Dispatch_ID": [1, 2, 3, 4, 5, 6, 7],
"GPU_ID": [0, 0, 0, 0, 0, 0, 0],
"Grid_Size": [1024, 1024, 1024, 512, 512, 512, 512],
"Workgroup_Size": [64, 64, 64, 32, 32, 32, 32],
"LDS_Per_Workgroup": [32, 32, 32, 16, 16, 16, 16],
"Scratch_Per_Workitem": [0, 0, 0, 0, 0, 0, 0],
"Arch_VGPR": [16, 16, 16, 16, 16, 16, 16],
"Accum_VGPR": [0, 0, 0, 0, 0, 0, 0],
"SGPR": [32, 32, 32, 32, 32, 32, 32],
"Kernel_Name": [
"kernel_a",
"kernel_a",
"kernel_a",
"kernel_a",
"kernel_a",
"kernel_a",
"kernel_a",
],
"Start_Timestamp": [1000, 1200, 1400, 1600, 1800, 2000, 2200],
"End_Timestamp": [1500, 1700, 1900, 2100, 2300, 2500, 2700],
"Kernel_ID": [1, 1, 1, 1, 1, 1, 1],
"C1": [10, None, 30, 50, None, 70, None],
"C2": [None, 20, None, None, 60, None, 80],
}
df = pd.DataFrame(data)
result = utils.impute_counters_iteration_multiplex(
df, "kernel_launch_params", tmp_path
)
result = result.sort_values(by="Dispatch_ID")
assert isinstance(result, pd.DataFrame)
assert len(result) == 7
# Config 1 (dispatches 1-3): subgroup 0 (1-2) complete, subgroup 1 (3) filled
# via cross-subgroup ffill; no NaN remains so dispatch 3 is kept as valid.
assert result["C1"].iloc[0] == 10
assert result["C2"].iloc[0] == 20
assert result["C1"].iloc[1] == 10
assert result["C2"].iloc[1] == 20
assert result["C1"].iloc[2] == 30
assert result["C2"].iloc[2] == 20
# Config 2 (dispatches 4-7): 2 complete rounds, no nullification
assert result["C1"].iloc[3] == 50
assert result["C2"].iloc[3] == 60
assert result["C1"].iloc[4] == 50
assert result["C2"].iloc[4] == 60
assert result["C1"].iloc[5] == 70
assert result["C2"].iloc[5] == 80
assert result["C1"].iloc[6] == 70
assert result["C2"].iloc[6] == 80
def test_complete_last_group_single_kernel(tmp_path: Path) -> None:
"""
Test imputation with kernel policy on a single kernel with complete last group.
1 kernel, 2 counters, 4 dispatches (2 complete rounds of 2 buckets).
All imputation happens within subgroups; previous_fill_values fallback
is never needed.
"""
data = {
"Dispatch_ID": [1, 2, 3, 4],
"GPU_ID": [0, 0, 0, 0],
"Grid_Size": [1024, 1024, 1024, 1024],
"Workgroup_Size": [64, 64, 64, 64],
"LDS_Per_Workgroup": [32, 32, 32, 32],
"Scratch_Per_Workitem": [0, 0, 0, 0],
"Arch_VGPR": [16, 16, 16, 16],
"Accum_VGPR": [0, 0, 0, 0],
"SGPR": [32, 32, 32, 32],
"Kernel_Name": ["kernel_a", "kernel_a", "kernel_a", "kernel_a"],
"Start_Timestamp": [1000, 1200, 1400, 1600],
"End_Timestamp": [1500, 1700, 1900, 2100],
"Kernel_ID": [1, 1, 1, 1],
"C1": [10, None, 30, None],
"C2": [None, 20, None, 40],
}
df = pd.DataFrame(data)
result = utils.impute_counters_iteration_multiplex(df, "kernel", tmp_path)
result = result.sort_values(by="Dispatch_ID")
assert isinstance(result, pd.DataFrame)
assert len(result) == 4
# Subgroup 1 (dispatches 1-2): self-contained imputation
assert result["C1"].iloc[0] == 10
assert result["C2"].iloc[0] == 20
assert result["C1"].iloc[1] == 10
assert result["C2"].iloc[1] == 20
# Subgroup 2 (dispatches 3-4): self-contained, values don't bleed from subgroup 1
assert result["C1"].iloc[2] == 30
assert result["C2"].iloc[2] == 40
assert result["C1"].iloc[3] == 30
assert result["C2"].iloc[3] == 40
def test_complete_last_group_multiple_kernels_both_complete(tmp_path: Path) -> None:
"""
Test imputation with kernel policy on multiple kernels, both complete.
kernel_a: 6 dispatches, 3 buckets {C1},{C2},{C3}, 2 complete rounds
kernel_b: 6 dispatches, 3 buckets {C1},{C2},{C3}, 2 complete rounds
"""
data = {
"Dispatch_ID": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
"GPU_ID": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"Grid_Size": [
1024,
1024,
1024,
1024,
1024,
1024,
1024,
1024,
1024,
1024,
1024,
1024,
],
"Workgroup_Size": [64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64],
"LDS_Per_Workgroup": [
32,
32,
32,
32,
32,
32,
32,
32,
32,
32,
32,
32,
],
"Scratch_Per_Workitem": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"Arch_VGPR": [16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16],
"Accum_VGPR": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"SGPR": [32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32],
"Kernel_Name": [
"kernel_a",
"kernel_a",
"kernel_a",
"kernel_a",
"kernel_a",
"kernel_a",
"kernel_b",
"kernel_b",
"kernel_b",
"kernel_b",
"kernel_b",
"kernel_b",
],
"Start_Timestamp": [
1000,
1200,
1400,
1600,
1800,
2000,
2200,
2400,
2600,
2800,
3000,
3200,
],
"End_Timestamp": [
1500,
1700,
1900,
2100,
2300,
2500,
2700,
2900,
3100,
3300,
3500,
3700,
],
"Kernel_ID": [1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2],
"C1": [
10,
None,
None,
40,
None,
None,
70,
None,
None,
100,
None,
None,
],
"C2": [
None,
20,
None,
None,
50,
None,
None,
80,
None,
None,
110,
None,
],
"C3": [
None,
None,
30,
None,
None,
60,
None,
None,
90,
None,
None,
120,
],
}
df = pd.DataFrame(data)
result = utils.impute_counters_iteration_multiplex(df, "kernel", tmp_path)
result = result.sort_values(by="Dispatch_ID")
assert isinstance(result, pd.DataFrame)
assert len(result) == 12
# kernel_a round 1 (dispatches 1-3)
assert result["C1"].iloc[0] == 10
assert result["C2"].iloc[0] == 20
assert result["C3"].iloc[0] == 30
assert result["C1"].iloc[1] == 10
assert result["C2"].iloc[1] == 20
assert result["C3"].iloc[1] == 30
assert result["C1"].iloc[2] == 10
assert result["C2"].iloc[2] == 20
assert result["C3"].iloc[2] == 30
# kernel_a round 2 (dispatches 4-6)
assert result["C1"].iloc[3] == 40
assert result["C2"].iloc[3] == 50
assert result["C3"].iloc[3] == 60
assert result["C1"].iloc[4] == 40
assert result["C2"].iloc[4] == 50
assert result["C3"].iloc[4] == 60
assert result["C1"].iloc[5] == 40
assert result["C2"].iloc[5] == 50
assert result["C3"].iloc[5] == 60
# kernel_b round 1 (dispatches 7-9)
assert result["C1"].iloc[6] == 70
assert result["C2"].iloc[6] == 80
assert result["C3"].iloc[6] == 90
assert result["C1"].iloc[7] == 70
assert result["C2"].iloc[7] == 80
assert result["C3"].iloc[7] == 90
assert result["C1"].iloc[8] == 70
assert result["C2"].iloc[8] == 80
assert result["C3"].iloc[8] == 90
# kernel_b round 2 (dispatches 10-12)
assert result["C1"].iloc[9] == 100
assert result["C2"].iloc[9] == 110
assert result["C3"].iloc[9] == 120
assert result["C1"].iloc[10] == 100
assert result["C2"].iloc[10] == 110
assert result["C3"].iloc[10] == 120
assert result["C1"].iloc[11] == 100
assert result["C2"].iloc[11] == 110
assert result["C3"].iloc[11] == 120
def test_complete_last_group_same_kernel_different_launch_params(
tmp_path: Path,
) -> None:
"""
Test imputation with kernel_launch_params on the same kernel
with different launch params.
kernel_launch_params policy, both configs have 2 complete rounds.
Config 1 (Grid=1024, WG=64, LDS=32): 4 dispatches
Config 2 (Grid=512, WG=32, LDS=16): 4 dispatches
"""
data = {
"Dispatch_ID": [1, 2, 3, 4, 5, 6, 7, 8],
"GPU_ID": [0, 0, 0, 0, 0, 0, 0, 0],
"Grid_Size": [1024, 1024, 1024, 1024, 512, 512, 512, 512],
"Workgroup_Size": [64, 64, 64, 64, 32, 32, 32, 32],
"LDS_Per_Workgroup": [32, 32, 32, 32, 16, 16, 16, 16],
"Scratch_Per_Workitem": [0, 0, 0, 0, 0, 0, 0, 0],
"Arch_VGPR": [16, 16, 16, 16, 16, 16, 16, 16],
"Accum_VGPR": [0, 0, 0, 0, 0, 0, 0, 0],
"SGPR": [32, 32, 32, 32, 32, 32, 32, 32],
"Kernel_Name": [
"kernel_a",
"kernel_a",
"kernel_a",
"kernel_a",
"kernel_a",
"kernel_a",
"kernel_a",
"kernel_a",
],
"Start_Timestamp": [1000, 1200, 1400, 1600, 1800, 2000, 2200, 2400],
"End_Timestamp": [1500, 1700, 1900, 2100, 2300, 2500, 2700, 2900],
"Kernel_ID": [1, 1, 1, 1, 1, 1, 1, 1],
"C1": [10, None, 30, None, 50, None, 70, None],
"C2": [None, 20, None, 40, None, 60, None, 80],
}
df = pd.DataFrame(data)
result = utils.impute_counters_iteration_multiplex(
df, "kernel_launch_params", tmp_path
)
result = result.sort_values(by="Dispatch_ID")
assert isinstance(result, pd.DataFrame)
assert len(result) == 8
# Config 1 round 1 (dispatches 1-2)
assert result["C1"].iloc[0] == 10
assert result["C2"].iloc[0] == 20
assert result["C1"].iloc[1] == 10
assert result["C2"].iloc[1] == 20
# Config 1 round 2 (dispatches 3-4)
assert result["C1"].iloc[2] == 30
assert result["C2"].iloc[2] == 40
assert result["C1"].iloc[3] == 30
assert result["C2"].iloc[3] == 40
# Config 2 round 1 (dispatches 5-6)
assert result["C1"].iloc[4] == 50
assert result["C2"].iloc[4] == 60
assert result["C1"].iloc[5] == 50
assert result["C2"].iloc[5] == 60
# Config 2 round 2 (dispatches 7-8)
assert result["C1"].iloc[6] == 70
assert result["C2"].iloc[6] == 80
assert result["C1"].iloc[7] == 70
assert result["C2"].iloc[7] == 80
def test_impute_counters_iteration_multiplex_missing_kernel_name(
tmp_path: Path,
) -> None:
"""
Test imputation when the DataFrame is a valid 2-level MultiIndex
but without the Kernel_Name column raises a KeyError.
"""
data_no_kernel_name = {
"Dispatch_ID": [1, 2],
"GPU_ID": [0, 0],
"Grid_Size": [1024, 1024],
"Workgroup_Size": [64, 64],
"LDS_Per_Workgroup": [32, 32],
"Scratch_Per_Workitem": [0, 0],
"Arch_VGPR": [16, 16],
"Accum_VGPR": [0, 0],
"SGPR": [32, 32],
"Start_Timestamp": [1000, 1200],
"End_Timestamp": [1500, 1700],
"Kernel_ID": [1, 1],
"C1": [10, None],
"C2": [None, 20],
}
df_no_kn = pd.DataFrame(data_no_kernel_name)
with pytest.raises(KeyError):
utils.impute_counters_iteration_multiplex(df_no_kn, "kernel", tmp_path)
def test_impute_counters_iteration_multiplex_empty_dataframe(tmp_path: Path) -> None:
"""Test imputation when the DataFrame is a valid MultiIndex but has no data rows."""
data_empty = {
"Dispatch_ID": [],
"GPU_ID": [],
"Grid_Size": [],
"Workgroup_Size": [],
"LDS_Per_Workgroup": [],
"Scratch_Per_Workitem": [],
"Arch_VGPR": [],
"Accum_VGPR": [],
"SGPR": [],
"Kernel_Name": [],
"Start_Timestamp": [],
"End_Timestamp": [],
"Kernel_ID": [],
"C1": [],
"C2": [],
}
df_empty = pd.DataFrame(data_empty)
result = utils.impute_counters_iteration_multiplex(df_empty, "kernel", tmp_path)
# Empty-group fallback preserves the input schema with zero rows.
assert isinstance(result, pd.DataFrame)
assert list(result.columns) == list(df_empty.columns)
assert len(result) == 0
def test_impute_counters_iteration_multiplex_all_counters_nan(tmp_path: Path) -> None:
"""
Test imputation when all counter values are NaN.
The bucket-identification loop finds no non-empty frozensets, so
counter_groups stays empty and the group is skipped entirely.
"""
data_all_nan = {
"Dispatch_ID": [1, 2, 3],
"GPU_ID": [0, 0, 0],
"Grid_Size": [1024, 1024, 1024],
"Workgroup_Size": [64, 64, 64],
"LDS_Per_Workgroup": [32, 32, 32],
"Scratch_Per_Workitem": [0, 0, 0],
"Arch_VGPR": [16, 16, 16],
"Accum_VGPR": [0, 0, 0],
"SGPR": [32, 32, 32],
"Kernel_Name": ["kernel_a", "kernel_a", "kernel_a"],
"Start_Timestamp": [1000, 1200, 1400],
"End_Timestamp": [1500, 1700, 1900],
"Kernel_ID": [1, 1, 1],
"C1": [None, None, None],
"C2": [None, None, None],
}
df_all_nan = pd.DataFrame(data_all_nan)
result = utils.impute_counters_iteration_multiplex(df_all_nan, "kernel", tmp_path)
# Group was dropped (no valid counters) -- empty schema-aligned frame.
assert isinstance(result, pd.DataFrame)
assert list(result.columns) == list(df_all_nan.columns)
assert len(result) == 0
def test_impute_counters_iteration_multiplex_no_counter_columns(tmp_path: Path) -> None:
"""
Test imputation when the DataFrame contains only the 13 non-counter columns.
counter_columns is empty, so every row yields an empty frozenset
and the group is skipped.
"""
data_no_counters = {
"Dispatch_ID": [1, 2],
"GPU_ID": [0, 0],
"Grid_Size": [1024, 1024],
"Workgroup_Size": [64, 64],
"LDS_Per_Workgroup": [32, 32],
"Scratch_Per_Workitem": [0, 0],
"Arch_VGPR": [16, 16],
"Accum_VGPR": [0, 0],
"SGPR": [32, 32],
"Kernel_Name": ["kernel_a", "kernel_a"],
"Start_Timestamp": [1000, 1200],
"End_Timestamp": [1500, 1700],
"Kernel_ID": [1, 1],
}
df_no_counters = pd.DataFrame(data_no_counters)
result = utils.impute_counters_iteration_multiplex(
df_no_counters, "kernel", tmp_path
)
# Group was dropped (no counter columns exist) -- empty schema-aligned frame.
assert isinstance(result, pd.DataFrame)
assert list(result.columns) == list(df_no_counters.columns)
assert len(result) == 0
def test_impute_counters_iteration_multiplex_unrecognized_policy(
tmp_path: Path,
) -> None:
"""
Test imputation when the policy is unrecognized.
Any policy other than "kernel" falls through to the else branch
(same as "kernel_launch_params"). The output must match exactly.
"""
data_policy = {
"Dispatch_ID": [1, 2, 3],
"GPU_ID": [0, 0, 0],
"Grid_Size": [1024, 1024, 1024],
"Workgroup_Size": [64, 64, 64],
"LDS_Per_Workgroup": [32, 32, 32],
"Scratch_Per_Workitem": [0, 0, 0],
"Arch_VGPR": [16, 16, 16],
"Accum_VGPR": [0, 0, 0],
"SGPR": [32, 32, 32],
"Kernel_Name": ["kernel_a", "kernel_a", "kernel_a"],
"Start_Timestamp": [1000, 1200, 1400],
"End_Timestamp": [1500, 1700, 1900],
"Kernel_ID": [1, 1, 1],
"C1": [100, None, None],
"C2": [None, 500, 300],
}
df_policy = pd.DataFrame(data_policy)
result_invalid = utils.impute_counters_iteration_multiplex(
df_policy, "invalid_policy", tmp_path
)
result_klp = utils.impute_counters_iteration_multiplex(
df_policy, "kernel_launch_params", tmp_path
)
assert isinstance(result_invalid, pd.DataFrame)
pd.testing.assert_frame_equal(
result_invalid.sort_values(by="Dispatch_ID").reset_index(drop=True),
result_klp.sort_values(by="Dispatch_ID").reset_index(drop=True),
)
def test_incomplete_dispatches_nullify_counter_values(tmp_path: Path) -> None:
"""
After imputation, any dispatch row that still has at least one NaN counter
value should have ALL counter columns set to NaN (fully nullified).
Non-counter columns (timestamps, kernel name, etc.) must be preserved so
that Top Stats (Block 1) timing data remains accurate.
Scenario:
kernel_a: 2 dispatches, 3 counter buckets {C1}, {C2}, {C3}.
Only 2 dispatches are available so the {C3} bucket is never reached:
- Dispatch 1: C1=10, C2=NaN, C3=NaN
- Dispatch 2: C1=NaN, C2=20, C3=NaN
After bfill/ffill imputation:
- C1 and C2 are filled for both dispatches (C1=10, C2=20)
- C3 remains NaN for both dispatches (never collected)
Post-imputation nullification:
- Both dispatches have C3=NaN -> all counter columns set to NaN
- Timestamp and Kernel_Name columns are preserved
"""
data = {
"Dispatch_ID": [1, 2],
"GPU_ID": [0, 0],
"Grid_Size": [1024, 1024],
"Workgroup_Size": [64, 64],
"LDS_Per_Workgroup": [32, 32],
"Scratch_Per_Workitem": [0, 0],
"Arch_VGPR": [16, 16],
"Accum_VGPR": [0, 0],
"SGPR": [32, 32],
"Kernel_Name": ["kernel_a", "kernel_a"],
"Start_Timestamp": [1000, 1200],
"End_Timestamp": [1500, 1700],
"Kernel_ID": [1, 1],
"C1": [10, None],
"C2": [None, 20],
"C3": [None, None],
}
df = pd.DataFrame(data)
# C1, C2, C3 form 3 round-robin buckets but the kernel only had 2 dispatches.
num_counter_bucket = 3
seed_perfmon_files(tmp_path, count=num_counter_bucket)
result = utils.impute_counters_iteration_multiplex(df, "kernel", tmp_path)
result = result.sort_values(by="Dispatch_ID").reset_index(drop=True)
assert isinstance(result, pd.DataFrame)
assert len(result) == 2
# Both dispatches: C3 was never collected so it remains NaN after imputation,
# triggering nullification of all counter columns on both rows.
assert pd.isna(result["C1"].iloc[0])
assert pd.isna(result["C2"].iloc[0])
assert pd.isna(result["C3"].iloc[0])
assert pd.isna(result["C1"].iloc[1])
assert pd.isna(result["C2"].iloc[1])
assert pd.isna(result["C3"].iloc[1])
# Non-counter columns must still be populated on both dispatches
# (preserved for Top Stats / Block 1 timing display).
assert result["Start_Timestamp"].iloc[0] == 1000
assert result["End_Timestamp"].iloc[0] == 1500
assert result["Kernel_Name"].iloc[0] == "kernel_a"
assert result["Start_Timestamp"].iloc[1] == 1200
assert result["End_Timestamp"].iloc[1] == 1700
assert result["Kernel_Name"].iloc[1] == "kernel_a"
def test_undersampled_kernel_nullified_against_perfmon_file_count(
tmp_path: Path,
) -> None:
"""
A kernel whose dispatch count is below the number of configured perfmon
files must be nullified even when its visible counter columns are fully
imputed. This guards the degenerate case where some buckets never reached
the joined dataframe at all.
"""
# 5 perfmon buckets configured, kernel only has 2 dispatches; the visible
# counters look fully populated but bucket coverage is incomplete.
num_counter_bucket = 5
seed_perfmon_files(tmp_path, count=num_counter_bucket)
data = {
"Dispatch_ID": [1, 2],
"GPU_ID": [0, 0],
"Grid_Size": [1024, 1024],
"Workgroup_Size": [64, 64],
"LDS_Per_Workgroup": [32, 32],
"Scratch_Per_Workitem": [0, 0],
"Arch_VGPR": [16, 16],
"Accum_VGPR": [0, 0],
"SGPR": [32, 32],
"Kernel_Name": ["kernel_a", "kernel_a"],
"Start_Timestamp": [1000, 1200],
"End_Timestamp": [1500, 1700],
"Kernel_ID": [1, 1],
"C1": [10, 30],
"C2": [20, 40],
}
df = pd.DataFrame(data)
result = utils.impute_counters_iteration_multiplex(df, "kernel", tmp_path)
result = result.sort_values(by="Dispatch_ID").reset_index(drop=True)
assert pd.isna(result["C1"].iloc[0])
assert pd.isna(result["C2"].iloc[0])
assert pd.isna(result["C1"].iloc[1])
assert pd.isna(result["C2"].iloc[1])
# Timestamps and kernel name preserved for Top Stats.
assert result["Start_Timestamp"].iloc[0] == 1000
assert result["Kernel_Name"].iloc[0] == "kernel_a"