mirror of
https://github.com/ROCm/composable_kernel.git
synced 2026-07-07 23:57:14 +00:00
[CK Tile] Rule-based configuration generation in CK Dispatcher codegen (#8157) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Motivation The CK Tile Dispatcher code generation for CK Tile Profiler relies on flat JSON files to list the generated configurations. This approach has the following problems - The JSON files are verbose - The JSON files get easily out of sync with the CK Builder .config files from which they were generated from. - The JSON file based configuration make it hard to list explicitly the rules that govern the instance generation. ## Technical Details Replaced the JSON files with a rule based configuration. To preserve the existing functionality, the `profiler` and the `tests` instance sets are generated directly from the CK Builder config files. The JSON config files are removed from source control, and the "on-the-fly" generation guarantees that the Dispatcher codegen uses up to date configurations. This is PR introduces six different rule sets for the CK Tile Dispatcher code generation 1. `profiler`: matches with the old JSON set of profiler configurations. 2. `tests`: matches with the old JSON set of tests configurations. 3. `full`: full configuration set created from a rule-based config selection 4. `full-tests`: a subset of `full` for generating configurations for convolution integration tests. 5. `tiny`: a subset of `full-tests` to produce the minimal set of configurations to test the Dispatcher codegen. 6. `default`: the default rules, which corresponds to the existing heuristic rules for configuration selection. This ensures that ML based kernel selection doesn't get broken. The main use of the `full` rule set is to define a reasonable solution space for the possible implicit GEMM configurations. We start from the configurations that allowed by the device architecture. The `full` rule set defines the relevant tile sizes for each convolution direction. From the tile size we have a curated mapping to the number of waves over the different GEMM axes, i.e., we describe how many waves each GEMM dimensions corresponds to. The GEMM-K wave tile dimension can be computed from the other parameters and does not need to be listed explicitly. An orthogonal axis to the tiling strategy is the vectorization strategy. This mainly defined by the data type and hardware as in general, we want to use the maximum possible load widths. The maximum sizes for each convolution direction variant are defined by the implicit GEMM matrix dimensions. For cases where have a low number of channels per convolution group, we need smaller vector load sizes. These are captured by the `VecStrategy` enumeration in the codegen rules. The problem with the rule based configuration selection is that we "over generate" configurations. The old JSON configurations compose approximately 25% of all configuration that the `full` rule set creates. The additional configurations are valid, but they many not provide any performance benefits. Hence, we keep the `profiler` and `tests` rule set for now to avoid building an excessive amount configurations by default. The `full` rule set can be taken into use by specifying CMake configuration flag `-D DISPATCHER_RULE_SET=full`. By default, the `tests` rule set is used, i.e., we don't change the existing bahaviour. ## Test Plan Added a new stage in the CI/CD pipeline that ensures the Dispatcher codegen rules are up to date. Otherwise the functionality is covered by the existing CI/CD tests. There are no functional changes to the convolution kernels. Only how the different instances are generated. ## Test Result If the CK Tile conv instances build without errors, the Dispatcher codegen is generating valid code. If all tests in CI/CD pipeline are passing, the Dispatcher codegen generates valid instances. ## Submission Checklist - [x] Look over the contributing guidelines at https://github.com/ROCm/ROCm/blob/develop/CONTRIBUTING.md#pull-requests.
1794 lines
84 KiB
Python
1794 lines
84 KiB
Python
#!/usr/bin/env python3
|
||
|
||
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
|
||
# SPDX-License-Identifier: MIT
|
||
|
||
"""
|
||
Full rule set for Grouped Convolution Tile Configurations.
|
||
"""
|
||
|
||
import logging
|
||
import sys
|
||
from dataclasses import dataclass
|
||
from enum import Enum
|
||
from pathlib import Path
|
||
from typing import Dict, List, Optional, Set, Tuple, Union
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Path setup — allow importing arch_specs_generated from the codegen directory
|
||
# ---------------------------------------------------------------------------
|
||
_CODEGEN_DIR = Path(__file__).resolve().parent.parent
|
||
if str(_CODEGEN_DIR) not in sys.path:
|
||
sys.path.insert(0, str(_CODEGEN_DIR))
|
||
|
||
from arch_specs_generated import WARP_TILE_SUPPORTED_COMBINATIONS
|
||
|
||
from .tile_math import (
|
||
get_valid_vec_sizes as _tm_get_valid_vec_sizes,
|
||
get_valid_wave_warp_pairs as _tm_get_valid_wave_warp_pairs,
|
||
)
|
||
|
||
# Dtype string to dtype_key mapping (for tile_math filter functions).
|
||
DTYPE_TO_DTYPE_KEY: Dict[str, str] = {
|
||
"fp16": "fp16_fp16_fp32",
|
||
"bf16": "bf16_bf16_fp32",
|
||
"fp32": "fp32_fp32_fp32",
|
||
}
|
||
|
||
# =============================================================================
|
||
# Tile Lists
|
||
# =============================================================================
|
||
#
|
||
# ndim + concrete-dtype keyed tile tables
|
||
#
|
||
# Tiles diverge by both ndim (3D is a subset of 2D) and concrete dtype.
|
||
# Keyed by (ndim, dtype) where dtype is the concrete "fp16"/"bf16"/"fp32" string.
|
||
|
||
_FWD_TILES: Dict[Tuple[int, str], List[Tuple[int, int, int]]] = {
|
||
(2, 'bf16'): [(16, 16, 64), (16, 16, 128), (16, 32, 64), (16, 64, 64), (16, 128, 64), (16, 256, 64), (32, 16, 64), (32, 64, 32), (32, 64, 64), (32, 128, 32), (32, 128, 64), (32, 256, 64), (64, 16, 16), (64, 16, 64), (64, 32, 32), (64, 32, 64), (64, 64, 8), (64, 64, 32), (64, 64, 64), (64, 128, 32), (64, 128, 64), (128, 16, 64), (128, 32, 32), (128, 32, 64), (128, 64, 8), (128, 64, 16), (128, 64, 32), (128, 64, 64), (128, 128, 32), (128, 128, 64), (128, 256, 32), (224, 256, 64), (256, 16, 64), (256, 32, 64), (256, 64, 8), (256, 128, 32), (256, 224, 64), (256, 256, 32)],
|
||
(2, 'fp16'): [(16, 16, 64), (16, 16, 128), (16, 32, 64), (16, 64, 64), (16, 128, 64), (16, 256, 64), (32, 16, 64), (32, 64, 32), (32, 64, 64), (32, 128, 32), (32, 128, 64), (32, 256, 64), (64, 16, 16), (64, 16, 64), (64, 32, 32), (64, 32, 64), (64, 64, 8), (64, 64, 32), (64, 64, 64), (64, 128, 32), (64, 128, 64), (128, 16, 64), (128, 32, 32), (128, 32, 64), (128, 64, 8), (128, 64, 16), (128, 64, 32), (128, 64, 64), (128, 128, 32), (128, 128, 64), (128, 256, 32), (224, 256, 64), (256, 16, 64), (256, 32, 64), (256, 64, 8), (256, 128, 32), (256, 224, 64), (256, 256, 32)],
|
||
(2, 'fp32'): [(16, 16, 64), (16, 16, 128), (16, 32, 64), (16, 64, 64), (16, 128, 64), (32, 16, 64), (32, 64, 16), (32, 64, 64), (32, 128, 16), (32, 128, 64), (64, 16, 16), (64, 16, 64), (64, 32, 16), (64, 32, 64), (64, 64, 16), (64, 64, 32), (64, 128, 16), (128, 16, 64), (128, 32, 16), (128, 32, 64), (128, 64, 16), (128, 128, 16), (128, 128, 32), (128, 128, 64), (128, 192, 16), (128, 256, 16), (256, 128, 16)],
|
||
(3, 'bf16'): [(16, 16, 64), (16, 16, 128), (16, 32, 64), (16, 64, 64), (16, 128, 64), (16, 256, 64), (32, 16, 64), (32, 64, 32), (32, 64, 64), (32, 128, 32), (32, 128, 64), (32, 256, 64), (64, 16, 16), (64, 16, 64), (64, 32, 32), (64, 32, 64), (64, 64, 32), (64, 64, 64), (64, 128, 32), (64, 128, 64), (128, 16, 64), (128, 32, 32), (128, 32, 64), (128, 64, 32), (128, 64, 64), (128, 128, 32), (128, 128, 64), (128, 256, 32), (224, 256, 64), (256, 16, 64), (256, 32, 64), (256, 128, 32), (256, 224, 64), (256, 256, 32)],
|
||
(3, 'fp16'): [(16, 16, 64), (16, 16, 128), (16, 32, 64), (16, 64, 64), (16, 128, 64), (16, 256, 64), (32, 16, 64), (32, 64, 32), (32, 64, 64), (32, 128, 32), (32, 128, 64), (32, 256, 64), (64, 16, 16), (64, 16, 64), (64, 32, 32), (64, 32, 64), (64, 64, 32), (64, 64, 64), (64, 128, 32), (64, 128, 64), (128, 16, 64), (128, 32, 32), (128, 32, 64), (128, 64, 32), (128, 64, 64), (128, 128, 32), (128, 128, 64), (128, 256, 32), (224, 256, 64), (256, 16, 64), (256, 32, 64), (256, 128, 32), (256, 224, 64), (256, 256, 32)],
|
||
(3, 'fp32'): [(16, 16, 64), (16, 16, 128), (16, 32, 64), (16, 64, 64), (16, 128, 64), (32, 16, 64), (32, 64, 16), (32, 64, 64), (32, 128, 16), (32, 128, 64), (64, 16, 16), (64, 16, 64), (64, 32, 16), (64, 32, 64), (64, 64, 16), (64, 64, 32), (64, 128, 16), (128, 16, 64), (128, 32, 16), (128, 32, 64), (128, 64, 16), (128, 128, 16), (128, 128, 32), (128, 128, 64), (128, 192, 16), (128, 256, 16), (256, 128, 16)],
|
||
}
|
||
|
||
_BWD_DATA_TILES: Dict[Tuple[int, str], List[Tuple[int, int, int]]] = {
|
||
(2, 'bf16'): [(16, 64, 32), (32, 64, 32), (32, 128, 32), (64, 16, 16), (64, 16, 32), (64, 16, 64), (64, 32, 32), (64, 64, 32), (64, 64, 64), (64, 128, 32), (128, 32, 16), (128, 32, 32), (128, 32, 64), (128, 64, 32), (128, 64, 64), (128, 128, 32), (128, 256, 32), (256, 32, 64), (256, 128, 32), (256, 128, 64)],
|
||
(2, 'fp16'): [(16, 64, 32), (32, 64, 32), (32, 128, 32), (64, 16, 16), (64, 16, 32), (64, 16, 64), (64, 32, 32), (64, 64, 32), (64, 64, 64), (64, 128, 32), (128, 32, 16), (128, 32, 32), (128, 32, 64), (128, 64, 32), (128, 64, 64), (128, 128, 32), (128, 256, 32), (256, 32, 64), (256, 128, 32), (256, 128, 64)],
|
||
(2, 'fp32'): [(16, 64, 32), (32, 64, 32), (32, 128, 32), (64, 16, 16), (64, 16, 32), (64, 32, 32), (64, 64, 32), (64, 128, 32), (128, 32, 16), (128, 32, 32), (128, 64, 32), (128, 128, 32), (128, 256, 32), (256, 128, 32)],
|
||
(3, 'bf16'): [(16, 64, 32), (32, 64, 32), (32, 128, 32), (64, 16, 16), (64, 16, 32), (64, 16, 64), (64, 32, 32), (64, 64, 32), (64, 128, 32), (128, 32, 16), (128, 32, 32), (128, 32, 64), (128, 64, 32), (128, 128, 32), (128, 256, 32), (256, 128, 32)],
|
||
(3, 'fp16'): [(16, 64, 32), (32, 64, 32), (32, 128, 32), (64, 16, 16), (64, 16, 32), (64, 16, 64), (64, 32, 32), (64, 64, 32), (64, 128, 32), (128, 32, 16), (128, 32, 32), (128, 32, 64), (128, 64, 32), (128, 128, 32), (128, 256, 32), (256, 128, 32)],
|
||
(3, 'fp32'): [(16, 64, 32), (32, 64, 32), (32, 128, 32), (64, 16, 16), (64, 16, 32), (64, 32, 32), (64, 64, 32), (64, 128, 32), (128, 32, 16), (128, 32, 32), (128, 64, 32), (128, 128, 32), (128, 256, 32), (256, 128, 32)],
|
||
}
|
||
|
||
_BWD_WEIGHT_TILES: Dict[Tuple[int, str], List[Tuple[int, int, int]]] = {
|
||
(2, 'bf16'): [(16, 16, 32), (16, 16, 64), (16, 32, 64), (16, 64, 64), (16, 128, 32), (16, 128, 64), (16, 256, 32), (16, 256, 64), (32, 16, 64), (32, 32, 32), (32, 64, 32), (32, 128, 32), (64, 16, 64), (64, 32, 32), (64, 32, 64), (64, 64, 32), (64, 64, 64), (64, 128, 32), (64, 128, 64), (128, 16, 64), (128, 32, 32), (128, 64, 32), (128, 128, 32), (128, 128, 64), (128, 256, 32), (256, 16, 64), (256, 32, 64), (256, 128, 32), (256, 256, 32)],
|
||
(2, 'fp16'): [(16, 16, 32), (16, 16, 64), (16, 32, 64), (16, 64, 64), (16, 128, 32), (16, 128, 64), (16, 256, 32), (16, 256, 64), (32, 16, 64), (32, 32, 32), (32, 64, 32), (32, 128, 32), (64, 16, 64), (64, 32, 32), (64, 32, 64), (64, 64, 32), (64, 64, 64), (64, 128, 32), (64, 128, 64), (128, 16, 64), (128, 32, 32), (128, 64, 32), (128, 128, 32), (128, 128, 64), (128, 256, 32), (256, 16, 64), (256, 32, 64), (256, 128, 32), (256, 256, 32)],
|
||
(2, 'fp32'): [(16, 16, 32), (16, 32, 64), (32, 16, 64), (32, 64, 16), (32, 128, 16), (64, 16, 64), (64, 32, 16), (64, 64, 16), (64, 64, 64), (64, 128, 16), (128, 32, 16), (128, 32, 32), (128, 64, 16), (128, 128, 16), (128, 256, 16), (256, 128, 16)],
|
||
(3, 'bf16'): [(16, 16, 32), (16, 16, 64), (16, 32, 64), (16, 64, 64), (16, 128, 32), (16, 128, 64), (16, 256, 32), (16, 256, 64), (32, 16, 64), (32, 32, 32), (32, 64, 32), (32, 128, 32), (64, 16, 64), (64, 32, 32), (64, 64, 32), (64, 64, 64), (64, 128, 32), (128, 16, 64), (128, 32, 32), (128, 64, 32), (128, 128, 32), (128, 128, 64), (128, 256, 32), (256, 16, 64), (256, 128, 32), (256, 256, 32)],
|
||
(3, 'fp16'): [(16, 16, 32), (16, 16, 64), (16, 32, 64), (16, 64, 64), (16, 128, 32), (16, 128, 64), (16, 256, 32), (16, 256, 64), (32, 16, 64), (32, 32, 32), (32, 64, 32), (32, 128, 32), (64, 16, 64), (64, 32, 32), (64, 64, 32), (64, 64, 64), (64, 128, 32), (128, 16, 64), (128, 32, 32), (128, 64, 32), (128, 128, 32), (128, 128, 64), (128, 256, 32), (256, 16, 64), (256, 128, 32), (256, 256, 32)],
|
||
(3, 'fp32'): [(16, 16, 32), (16, 32, 64), (32, 16, 64), (32, 64, 16), (32, 128, 16), (64, 16, 64), (64, 32, 16), (64, 64, 16), (64, 64, 64), (64, 128, 16), (128, 32, 16), (128, 32, 32), (128, 64, 16), (128, 128, 16), (128, 256, 16), (256, 128, 16)],
|
||
}
|
||
|
||
_TILES_PER_VARIANT: Dict[str, Dict[Tuple[int, str], List[Tuple[int, int, int]]]] = {
|
||
"forward": _FWD_TILES,
|
||
"bwd_data": _BWD_DATA_TILES,
|
||
"bwd_weight": _BWD_WEIGHT_TILES,
|
||
}
|
||
|
||
# Override the tile sizes for split-image feature.
|
||
_SPLIT_IMAGE_TILES: List[Tuple[int, int, int]] = [
|
||
(64, 64, 16), (64, 64, 32),
|
||
(256, 128, 16), (256, 128, 32),
|
||
]
|
||
|
||
# Tiles that support compv4 pipeline
|
||
# compv4 has stricter requirements due to double buffering and LDS constraints
|
||
COMPV4_COMPATIBLE_TILES: List[Tuple[int, int, int]] = [
|
||
# warp_tile [16,16,16] - all work with compv4
|
||
(16, 64, 64),
|
||
(32, 64, 64),
|
||
(64, 64, 64),
|
||
# warp_tile [16,16,32] - all work with compv4
|
||
(16, 64, 128),
|
||
(32, 64, 128),
|
||
(64, 64, 128),
|
||
]
|
||
|
||
def get_tiles_for_variant(variant: str) -> List[Tuple[int, int, int]]:
|
||
"""Return all tiles available for the given conv variant.
|
||
|
||
Returns the union of tiles across all (ndim, dtype) keys, sorted.
|
||
"""
|
||
all_tiles: set = set()
|
||
for tiles in _TILES_PER_VARIANT.get(variant, {}).values():
|
||
all_tiles.update(tiles)
|
||
return sorted(all_tiles)
|
||
|
||
def get_tiles(variant: str, ndim: int, dtype: str) -> List[Tuple[int, int, int]]:
|
||
"""Return tiles for a (variant, ndim, concrete-dtype).
|
||
|
||
``dtype`` is the concrete "fp16"/"bf16"/"fp32" string. Returns the exact
|
||
tile list observed in the corresponding profiler JSON, or [] if none.
|
||
"""
|
||
return list(_TILES_PER_VARIANT.get(variant, {}).get((ndim, dtype), []))
|
||
|
||
|
||
# Warp (m, n) shapes excluded from code generation.
|
||
# These are possible shapes, but old CK doesn't
|
||
# use them. However, they can potentially be useful.
|
||
# (4, 64) / (64, 4) are asymmetric MFMA shapes that only ever appear as
|
||
# hand-written native instances; warp_tile_k is no longer part of the key.
|
||
_EXCLUDED_WARP_SHAPES: Set[Tuple[int, int]] = {
|
||
(4, 64), (64, 4),
|
||
}
|
||
|
||
# =============================================================================
|
||
# Wave (block-to-wave split) strategies
|
||
# =============================================================================
|
||
#
|
||
# A macro tile is partitioned among the waves of a thread block along M, N and K.
|
||
# The split is described by a WaveStrategy that yields the wave *counts*
|
||
# (wave_m, wave_n, wave_k); the per-wave tile is then tile / wave_count.
|
||
#
|
||
# compute_wave_counts() turns a strategy into the (wave_m, wave_n,wave_k) triple,
|
||
# and compute_wave_tile() derives the per-wave tile size from the
|
||
# macro tile and strategy.
|
||
|
||
|
||
class WaveStrategy(Enum):
|
||
"""Block-to-wave partitioning strategies for grouped convolution configs.
|
||
|
||
Each strategy maps to a (wave_m, wave_n, wave_k) wave-count triple.
|
||
"""
|
||
SINGLE = "single" # (1, 1, 1) — one wave covers the whole tile
|
||
SPLIT_M = "split_m" # (2, 1, 1) — 2 waves along M
|
||
SPLIT_N = "split_n" # (1, 2, 1) — 2 waves along N
|
||
SPLIT_MN = "split_mn" # (2, 2, 1) — 2x2 waves along M and N
|
||
SPLIT_M4 = "split_m4" # (4, 1, 1) — 4 waves along M
|
||
SPLIT_N4 = "split_n4" # (1, 4, 1) — 4 waves along N
|
||
SPLIT_MK = "split_mk" # (2, 1, 2) — 2 waves along M and 2 along K
|
||
|
||
|
||
_WAVE_STRATEGY_COUNTS: Dict[WaveStrategy, Tuple[int, int, int]] = {
|
||
WaveStrategy.SINGLE: (1, 1, 1),
|
||
WaveStrategy.SPLIT_M: (2, 1, 1),
|
||
WaveStrategy.SPLIT_N: (1, 2, 1),
|
||
WaveStrategy.SPLIT_MN: (2, 2, 1),
|
||
WaveStrategy.SPLIT_M4: (4, 1, 1),
|
||
WaveStrategy.SPLIT_N4: (1, 4, 1),
|
||
WaveStrategy.SPLIT_MK: (2, 1, 2),
|
||
}
|
||
|
||
|
||
def compute_wave_counts(strategy: WaveStrategy) -> Tuple[int, int, int]:
|
||
"""Return the (wave_m, wave_n, wave_k) wave-count triple for a strategy."""
|
||
return _WAVE_STRATEGY_COUNTS[strategy]
|
||
|
||
|
||
def compute_wave_tile(
|
||
strategy: WaveStrategy, tile_m: int, tile_n: int, tile_k: int,
|
||
) -> Tuple[int, int, int]:
|
||
"""Return the per-wave tile (macro tile / wave counts) for a strategy."""
|
||
wm, wn, wk = compute_wave_counts(strategy)
|
||
return (tile_m // wm, tile_n // wn, tile_k // wk)
|
||
|
||
|
||
# Curated wave strategies as a shared base table plus small per-variant
|
||
# override tables. Most tiles use the same strategy across the variants that
|
||
# use them (_BASE_TILE_WAVE_STRATEGIES); the handful that genuinely differ are
|
||
# captured in _WAVE_STRATEGY_OVERRIDES. Tiles absent from both fall back to
|
||
# [WaveStrategy.SINGLE] in get_wave_strategies().
|
||
|
||
_BASE_TILE_WAVE_STRATEGIES: Dict[Tuple[int, int, int], List[WaveStrategy]] = {
|
||
(16, 16, 32): [WaveStrategy.SINGLE],
|
||
(16, 16, 64): [WaveStrategy.SINGLE],
|
||
(16, 16, 128): [WaveStrategy.SINGLE],
|
||
(16, 32, 64): [WaveStrategy.SPLIT_N],
|
||
(16, 64, 32): [WaveStrategy.SINGLE],
|
||
(16, 64, 64): [WaveStrategy.SPLIT_N],
|
||
(16, 128, 32): [WaveStrategy.SINGLE],
|
||
(16, 128, 64): [WaveStrategy.SPLIT_N],
|
||
(16, 256, 32): [WaveStrategy.SINGLE],
|
||
(16, 256, 64): [WaveStrategy.SPLIT_N4],
|
||
(32, 16, 64): [WaveStrategy.SPLIT_M, WaveStrategy.SPLIT_MK],
|
||
(32, 32, 32): [WaveStrategy.SINGLE],
|
||
(32, 64, 16): [WaveStrategy.SINGLE],
|
||
(32, 64, 32): [WaveStrategy.SINGLE],
|
||
(32, 64, 64): [WaveStrategy.SPLIT_N],
|
||
(32, 128, 16): [WaveStrategy.SPLIT_N],
|
||
(32, 128, 32): [WaveStrategy.SPLIT_N],
|
||
(32, 128, 64): [WaveStrategy.SPLIT_N],
|
||
(32, 256, 64): [WaveStrategy.SPLIT_N4],
|
||
(64, 16, 16): [WaveStrategy.SINGLE],
|
||
(64, 16, 32): [WaveStrategy.SINGLE, WaveStrategy.SPLIT_M4],
|
||
(64, 16, 64): [WaveStrategy.SPLIT_M, WaveStrategy.SPLIT_MK],
|
||
(64, 32, 16): [WaveStrategy.SINGLE],
|
||
(64, 32, 32): [WaveStrategy.SINGLE],
|
||
(64, 32, 64): [WaveStrategy.SPLIT_M],
|
||
(64, 64, 8): [WaveStrategy.SPLIT_M],
|
||
(64, 64, 16): [WaveStrategy.SINGLE],
|
||
(64, 64, 32): [WaveStrategy.SINGLE],
|
||
(64, 64, 64): [WaveStrategy.SPLIT_MN],
|
||
(64, 128, 16): [WaveStrategy.SPLIT_N, WaveStrategy.SPLIT_MN],
|
||
(64, 128, 32): [WaveStrategy.SPLIT_N, WaveStrategy.SPLIT_MN],
|
||
(64, 128, 64): [WaveStrategy.SPLIT_MN],
|
||
(128, 16, 64): [WaveStrategy.SPLIT_M, WaveStrategy.SPLIT_MK],
|
||
(128, 32, 16): [WaveStrategy.SPLIT_M],
|
||
(128, 32, 32): [WaveStrategy.SPLIT_M, WaveStrategy.SPLIT_MK],
|
||
(128, 32, 64): [WaveStrategy.SPLIT_M],
|
||
(128, 64, 8): [WaveStrategy.SPLIT_M, WaveStrategy.SPLIT_MN],
|
||
(128, 64, 16): [WaveStrategy.SPLIT_M, WaveStrategy.SPLIT_MN],
|
||
(128, 64, 32): [WaveStrategy.SPLIT_M, WaveStrategy.SPLIT_MN],
|
||
(128, 64, 64): [WaveStrategy.SPLIT_MN],
|
||
(128, 128, 16): [WaveStrategy.SPLIT_N, WaveStrategy.SPLIT_MN],
|
||
(128, 128, 32): [WaveStrategy.SPLIT_N, WaveStrategy.SPLIT_MN],
|
||
(128, 128, 64): [WaveStrategy.SPLIT_MN],
|
||
(128, 192, 16): [WaveStrategy.SPLIT_MN],
|
||
(128, 256, 16): [WaveStrategy.SPLIT_MN],
|
||
(128, 256, 32): [WaveStrategy.SPLIT_MN],
|
||
(224, 256, 64): [WaveStrategy.SPLIT_MN],
|
||
(256, 16, 64): [WaveStrategy.SPLIT_M4],
|
||
(256, 32, 64): [WaveStrategy.SPLIT_M4],
|
||
(256, 64, 8): [WaveStrategy.SPLIT_MN],
|
||
(256, 128, 16): [WaveStrategy.SPLIT_MN],
|
||
(256, 128, 32): [WaveStrategy.SPLIT_MN],
|
||
(256, 128, 64): [WaveStrategy.SPLIT_MN],
|
||
(256, 224, 64): [WaveStrategy.SPLIT_MN],
|
||
(256, 256, 32): [WaveStrategy.SPLIT_MN],
|
||
}
|
||
|
||
# Per-variant deviations from _BASE_TILE_WAVE_STRATEGIES. An entry here
|
||
# fully replaces the base strategy list for that (variant, tile). Only the
|
||
# tiles where a variant genuinely differs appear; bwd_data is the main
|
||
# deviator (it prefers SPLIT_M4 over SPLIT_M for several skinny-N tiles).
|
||
_WAVE_STRATEGY_OVERRIDES: Dict[str, Dict[Tuple[int, int, int], List[WaveStrategy]]] = {
|
||
"forward": {
|
||
(64, 64, 32): [WaveStrategy.SINGLE, WaveStrategy.SPLIT_MN],
|
||
},
|
||
"bwd_data": {
|
||
(64, 16, 16): [WaveStrategy.SPLIT_M4],
|
||
(64, 16, 64): [WaveStrategy.SPLIT_M4],
|
||
(128, 32, 16): [WaveStrategy.SPLIT_M4],
|
||
(128, 32, 32): [WaveStrategy.SPLIT_M, WaveStrategy.SPLIT_M4],
|
||
(128, 32, 64): [WaveStrategy.SPLIT_M4],
|
||
},
|
||
"bwd_weight": {
|
||
(16, 64, 64): [WaveStrategy.SPLIT_N, WaveStrategy.SPLIT_N4],
|
||
(32, 128, 32): [WaveStrategy.SINGLE, WaveStrategy.SPLIT_N],
|
||
(64, 32, 64): [WaveStrategy.SPLIT_M, WaveStrategy.SPLIT_N],
|
||
(128, 32, 32): [WaveStrategy.SINGLE, WaveStrategy.SPLIT_M],
|
||
},
|
||
}
|
||
|
||
# Borderline (variant, tile, wave) pairs whose warp (m, n) selection deviates
|
||
# from _default_warp_mn(). These are the per-wave-tile == 32x32 cases, where the
|
||
# reference data is not consistent with the size threshold:
|
||
# - forward (64, 64, 32)/(2,2,1) uses 16x16 despite a 32x32 per-wave tile;
|
||
# - forward (256, 256, 32)/(2,2,1) and bwd_weight (64, 64, 64)/(2,2,1) use
|
||
# BOTH 16x16 and 32x32.
|
||
_WARP_MN_EXCEPTIONS: Dict[
|
||
Tuple[str, Tuple[int, int, int], Tuple[int, int, int]], List[Tuple[int, int]]
|
||
] = {
|
||
("forward", (64, 64, 32), (2, 2, 1)): [(16, 16)],
|
||
("forward", (64, 64, 64), (2, 2, 1)): [(16, 16), (32, 32)],
|
||
("forward", (128, 128, 64), (2, 2, 1)): [(16, 16), (32, 32)],
|
||
("forward", (256, 256, 32), (2, 2, 1)): [(16, 16), (32, 32)],
|
||
("bwd_weight", (64, 64, 64), (2, 2, 1)): [(16, 16), (32, 32)],
|
||
# bwd_data wavelet tiles: wavelet instances use a 16x16 MFMA
|
||
# warp tile (warp_tile_k=32 for half) even though the per-wave tile is 32x32.
|
||
("bwd_data", (64, 64, 64), (2, 2, 1)): [(16, 16)],
|
||
("bwd_data", (128, 64, 64), (2, 2, 1)): [(16, 16)],
|
||
("bwd_data", (256, 32, 64), (4, 1, 1)): [(16, 16)],
|
||
("bwd_data", (256, 128, 32), (2, 2, 1)): [(16, 16), (32, 32)],
|
||
("bwd_data", (256, 128, 64), (2, 2, 1)): [(16, 16)],
|
||
}
|
||
|
||
|
||
def _default_warp_mn(tile_m: int, tile_n: int, wave_m: int, wave_n: int) -> Tuple[int, int]:
|
||
"""Return the default warp (m, n) MFMA shape for a (tile, wave).
|
||
|
||
A 32x32 warp tile is used when the per-wave tile (tile / wave) is at least
|
||
32 and a multiple of 32 in both M and N; otherwise a 16x16 warp tile is used.
|
||
Borderline cases are overridden via _WARP_MN_EXCEPTIONS.
|
||
"""
|
||
pm, pn = tile_m // wave_m, tile_n // wave_n
|
||
if pm >= 32 and pn >= 32 and pm % 32 == 0 and pn % 32 == 0:
|
||
return (32, 32)
|
||
return (16, 16)
|
||
|
||
|
||
def get_warp_mn_candidates(
|
||
tile_m: int, tile_n: int, tile_k: int,
|
||
wave_m: int, wave_n: int, wave_k: int,
|
||
variant: str = "forward",
|
||
) -> List[Tuple[int, int]]:
|
||
"""Return the curated warp (m, n) candidates for a (variant, tile, wave).
|
||
|
||
Uses _WARP_MN_EXCEPTIONS when present, otherwise _default_warp_mn().
|
||
"""
|
||
key = (variant, (tile_m, tile_n, tile_k), (wave_m, wave_n, wave_k))
|
||
if key in _WARP_MN_EXCEPTIONS:
|
||
return list(_WARP_MN_EXCEPTIONS[key])
|
||
return [_default_warp_mn(tile_m, tile_n, wave_m, wave_n)]
|
||
|
||
|
||
def get_wave_strategies(
|
||
tile_m: int, tile_n: int, tile_k: int, variant: str,
|
||
) -> List[WaveStrategy]:
|
||
"""Return the curated WaveStrategy list for a (variant, macro tile).
|
||
|
||
A per-variant override (_WAVE_STRATEGY_OVERRIDES) takes precedence over the
|
||
shared base table (_BASE_TILE_WAVE_STRATEGIES). Tiles absent from both fall
|
||
back to [WaveStrategy.SINGLE].
|
||
"""
|
||
tile = (tile_m, tile_n, tile_k)
|
||
override = _WAVE_STRATEGY_OVERRIDES.get(variant, {}).get(tile)
|
||
if override is not None:
|
||
return list(override)
|
||
entry = _BASE_TILE_WAVE_STRATEGIES.get(tile)
|
||
if entry is None:
|
||
logging.warning(
|
||
"No curated wave strategies for variant=%s tile=(%d,%d,%d); "
|
||
"falling back to [WaveStrategy.SINGLE]",
|
||
variant, tile_m, tile_n, tile_k,
|
||
)
|
||
return [WaveStrategy.SINGLE]
|
||
return list(entry)
|
||
|
||
|
||
def get_wave_configs(
|
||
tile_m: int, tile_n: int, tile_k: int, variant: str,
|
||
) -> List[Tuple[int, int, int]]:
|
||
"""Return wave-count configs for a tile+variant.
|
||
|
||
Each curated WaveStrategy is turned into its (wave_m, wave_n, wave_k) triple.
|
||
Falls back to generic [(1, 1, 1)] for unknown tiles.
|
||
"""
|
||
return [
|
||
compute_wave_counts(strategy)
|
||
for strategy in get_wave_strategies(tile_m, tile_n, tile_k, variant)
|
||
]
|
||
|
||
|
||
def get_all_valid_vector_sizes(
|
||
tile_m: int, tile_n: int, tile_k: int,
|
||
wave_m: int, wave_n: int, wave_k: int,
|
||
wt_m: int, wt_n: int, wt_k: int,
|
||
dtype_key: str,
|
||
) -> Set[Tuple[int, int, int]]:
|
||
"""Return the set of (vec_a, vec_b, vec_c) triples tile_math considers valid.
|
||
|
||
Thin wrapper around tile_math.get_valid_vec_sizes for use as a hard gate:
|
||
curated/strategy vec triples are kept only if present in this set.
|
||
"""
|
||
return set(_tm_get_valid_vec_sizes(
|
||
tile_m, tile_n, tile_k,
|
||
wave_m, wave_n, wave_k,
|
||
wt_m, wt_n, wt_k,
|
||
dtype_key,
|
||
))
|
||
|
||
|
||
def get_k_mfma(dtype_key: str, m_per_xdl: int) -> int:
|
||
"""Return the MFMA K dimension for a square warp tile (m_per_xdl == n_per_xdl).
|
||
|
||
Single authority mirroring generate_instances.get_k_mfma (the CK Builder).
|
||
Arch-independent (reflects the current CDNA4 MFMA shapes):
|
||
- half (fp16/bf16): 32x32 -> 16, 16x16 -> 32
|
||
- float (fp32): 32x32 -> 2, 16x16 -> 4
|
||
"""
|
||
if dtype_key.startswith("fp32"):
|
||
return 2 if m_per_xdl == 32 else 4
|
||
return 16 if m_per_xdl == 32 else 32
|
||
|
||
|
||
def compute_warp_tile_k(
|
||
dtype_key: str, warp_tile_m: int, tile_k: int, streamk: bool = False,
|
||
use_legacy: bool = False,
|
||
) -> int:
|
||
"""Derive warp_tile_k (the MFMA K) for a config.
|
||
|
||
Mirrors the CK Builder formula (generate_instances.py)
|
||
k_per_xdl = min(max(k1, get_k_mfma(dtype, m_per_xdl, n_per_xdl)), k_per_block)
|
||
where k1 is the per-instance AK1. For half precision get_k_mfma dominates k1,
|
||
so warp_tile_k = min(get_k_mfma, tile_k). For fp32, k1 dominates; the reference
|
||
instances use k1 = 4 when tile_k <= 16 else 8.
|
||
|
||
Hand-written StreamK *native* instances bypass get_k_mfma and carry the legacy
|
||
(gfx942) warp_tile_k verbatim: half 32x32 -> 8, 16x16 -> 16, clamped to tile_k
|
||
(fp32 native instances clamp to tile_k // 4 instead).
|
||
|
||
``use_legacy`` applies the same legacy formula for non-streamk contexts that
|
||
nevertheless use the gfx942 warp_tile_k convention (e.g. some wavelet bwd_weight
|
||
instances).
|
||
"""
|
||
if streamk or use_legacy:
|
||
legacy_half_k = 8 if warp_tile_m == 32 else 16
|
||
cap = tile_k // 4 if dtype_key.startswith("fp32") else tile_k
|
||
return min(legacy_half_k, cap)
|
||
k1 = 4 if tile_k <= 16 else 8
|
||
return min(max(k1, get_k_mfma(dtype_key, warp_tile_m)), tile_k)
|
||
|
||
|
||
def get_warp_configs_for_tile_and_wave(
|
||
tile_m: int, tile_n: int, tile_k: int,
|
||
wave_m: int, wave_n: int, wave_k: int,
|
||
dtype_key: str, arch: str = "gfx942", variant: str = "forward",
|
||
) -> List[Tuple[int, int]]:
|
||
"""Return curated (warp_tile_m, warp_tile_n) shapes for a (variant, wave).
|
||
|
||
The (warp_tile_m, warp_tile_n) shape is derived by ``get_warp_mn_candidates``
|
||
(a size-threshold function plus a small exception table); warp_tile_k is
|
||
derived later by ``compute_warp_tile_k``.
|
||
|
||
A (warp_tile_m, warp_tile_n) shape is kept only when it:
|
||
- is arch/dtype-supported (WARP_TILE_SUPPORTED_COMBINATIONS[arch][dtype_key]),
|
||
- is not in _EXCLUDED_WARP_SHAPES,
|
||
- divides the macro tile: tile_m % (wave_m*warp_tile_m) == 0 and
|
||
tile_n % (wave_n*warp_tile_n) == 0.
|
||
"""
|
||
tile = (tile_m, tile_n, tile_k)
|
||
wave = (wave_m, wave_n, wave_k)
|
||
curated_waves = {
|
||
compute_wave_counts(strategy)
|
||
for strategy in get_wave_strategies(tile_m, tile_n, tile_k, variant)
|
||
}
|
||
|
||
if wave not in curated_waves:
|
||
raise ValueError(f"No curated warp tiles for variant={variant} tile={tile} wave={wave}")
|
||
curated = get_warp_mn_candidates(tile_m, tile_n, tile_k, wave_m, wave_n, wave_k, variant)
|
||
|
||
supported_mn = {
|
||
(wt[0], wt[1])
|
||
for wt in WARP_TILE_SUPPORTED_COMBINATIONS.get(arch, {}).get(dtype_key, [])
|
||
}
|
||
|
||
result: List[Tuple[int, int]] = []
|
||
seen: Set[Tuple[int, int]] = set()
|
||
for wt in curated:
|
||
mn = (wt[0], wt[1])
|
||
if mn in _EXCLUDED_WARP_SHAPES:
|
||
continue
|
||
if mn not in supported_mn:
|
||
continue
|
||
if tile_m % (wave_m * mn[0]) != 0 or tile_n % (wave_n * mn[1]) != 0:
|
||
continue
|
||
if mn not in seen:
|
||
seen.add(mn)
|
||
result.append(mn)
|
||
return result
|
||
|
||
|
||
def get_wave_warp_pairs(
|
||
tile_m: int, tile_n: int, tile_k: int,
|
||
variant: str, dtype_key: str, arch: str = "gfx942",
|
||
) -> List[Tuple[Tuple[int, int, int], Tuple[int, int]]]:
|
||
"""Return (wave, warp_tile_mn) pairs: curated waves x curated warp (m, n) shapes.
|
||
|
||
Combines curated wave configs with the curated per-variant wave->warp map
|
||
(both from profiler JSON), filtered by arch/dtype support and divisibility.
|
||
warp_tile_k is not part of the pair; it is derived later via
|
||
``compute_warp_tile_k``.
|
||
|
||
The curated pairs are gated against _tm_get_valid_wave_warp_pairs (imported from
|
||
tile_math.py) on the (wave, warp_m, warp_n) granularity, and any curated pair
|
||
rejected is dropped with a warning.
|
||
"""
|
||
tm_pairs = _tm_get_valid_wave_warp_pairs(tile_m, tile_n, tile_k, dtype_key, arch)
|
||
tm_pairs_mn = {(wave, (wt[0], wt[1])) for (wave, wt) in tm_pairs}
|
||
result = []
|
||
for wave in get_wave_configs(tile_m, tile_n, tile_k, variant):
|
||
for mn in get_warp_configs_for_tile_and_wave(
|
||
tile_m, tile_n, tile_k, *wave, dtype_key, arch, variant,
|
||
):
|
||
if (wave, mn) not in tm_pairs_mn:
|
||
logging.warning(
|
||
"Dropping curated wave/warp pair %s rejected by tile_math: "
|
||
"tile=(%d,%d,%d) variant=%s dtype_key=%s",
|
||
(wave, mn), tile_m, tile_n, tile_k, variant, dtype_key,
|
||
)
|
||
continue
|
||
result.append((wave, mn))
|
||
|
||
# If no curated pairs survived (e.g. for architectures whose supported wave
|
||
# combos don't match the CDNA-derived curated strategies, such as rdna4/gfx1250),
|
||
# fall back to all tile_math-valid pairs so those arches still get kernels.
|
||
if not result and tm_pairs:
|
||
for wave, wt in tm_pairs:
|
||
result.append((wave, (wt[0], wt[1])))
|
||
|
||
return result
|
||
|
||
|
||
# =============================================================================
|
||
# Vector Size Strategies (tile + dtype based, wave/warp independent)
|
||
# =============================================================================
|
||
#
|
||
# Convolution GEMM tensors vectorize along different logical dimensions:
|
||
# Forward: vec_a, vec_b -> C (channels), vec_c -> K (output channels)
|
||
# BWD data: vec_a -> K, vec_b, vec_c -> C
|
||
# BWD weight: vec_a -> K, vec_b, vec_c -> C
|
||
#
|
||
# Each strategy produces one (vec_a, vec_b, vec_c) triple from the dtype-determined max.
|
||
|
||
|
||
class VecStrategy(Enum):
|
||
"""Vectorization strategies for grouped convolution GEMM configs.
|
||
|
||
Each strategy produces a single (vec_a, vec_b, vec_c) triple derived
|
||
from the dtype-determined maximum vector sizes.
|
||
"""
|
||
GENERIC = "generic" # (1, 1, 1) — minimum fallback
|
||
UNIFORM_MAX = "uniform_max" # (max, max, max) — balanced throughput
|
||
MAX_AB_HALF_C = "max_ab_half_c" # (max, max, max/2) — fwd (8,8,4) pattern
|
||
MAX_A_MIN_BC = "max_a_min_bc" # (max, 1, 1) — bwd (4,1,1)/(8,1,1) pattern
|
||
MIN_A_MAX_BC = "min_a_max_bc" # (1, max, max) — bwd (1,4,4)/(1,8,8) pattern
|
||
HALF_UNIFORM = "half_uniform" # (max/2, max/2, max/2) — (2,2,2) pattern
|
||
QUARTER_AB_MAX_C = "quarter_ab_max_c" # (max/2, max/2, max) — fwd (4,4,8)/(1,1,8) pattern
|
||
MAX_AB_QUARTER_C = "max_ab_quarter_c" # (max, max, max/4) — half (8,8,2), fp32 (4,4,1)
|
||
MAX_A_QUARTER_BC = "max_a_quarter_bc" # (max, max/4, max/4) — half (8,2,2)
|
||
MIN_AB_MAX_C = "min_ab_max_c" # (1, 1, max) — half (1,1,8), fp32 (1,1,4)
|
||
MAX_A_HALF_BC = "max_a_half_bc" # (max, max/2, max/2) — fp32 (4,2,2)
|
||
MAX_AB_MIN_C = "max_ab_min_c" # (max, max, 1) — half (8,8,1)
|
||
MIN_AB_QUARTER_C = "min_ab_quarter_c" # (1, 1, max/4) — half (1,1,2)
|
||
MIN_A_MAX_B_HALF_C = "min_a_max_b_half_c" # (1, max, max/2) — half (1,8,4), fp32 (1,4,2) - TODO: Is this valid?
|
||
HALF_A_MAX_BC = "half_a_max_bc" # (max/2, max, max) — fp32 (2,4,4)
|
||
HALF_A_MIN_BC = "half_a_min_bc" # (max/2, 1, 1) — fp32 (2,1,1)
|
||
|
||
|
||
def _max_vec(dtype_class: str) -> int:
|
||
"""Return the maximum vector width for a dtype class."""
|
||
if dtype_class == "float":
|
||
return 4
|
||
elif dtype_class in ("half", "fp16", "bf16"):
|
||
return 8
|
||
else:
|
||
raise ValueError(f"Unknown dtype class: {dtype_class}")
|
||
|
||
|
||
def compute_vector_size(
|
||
strategy: VecStrategy,
|
||
dtype_class: str,
|
||
) -> Tuple[int, int, int]:
|
||
"""Compute a (vec_a, vec_b, vec_c) triple for a given strategy and dtype.
|
||
|
||
Args:
|
||
strategy: Which vectorization pattern to use.
|
||
dtype_class: "float" (fp32) or "half" (fp16/bf16).
|
||
|
||
Returns:
|
||
A single (vec_a, vec_b, vec_c) triple.
|
||
"""
|
||
m = _max_vec(dtype_class)
|
||
h = max(1, m // 2)
|
||
q = max(1, m // 4)
|
||
|
||
if strategy == VecStrategy.GENERIC:
|
||
return (1, 1, 1)
|
||
elif strategy == VecStrategy.UNIFORM_MAX:
|
||
return (m, m, m)
|
||
elif strategy == VecStrategy.MAX_AB_HALF_C:
|
||
return (m, m, h)
|
||
elif strategy == VecStrategy.MAX_A_MIN_BC:
|
||
return (m, 1, 1)
|
||
elif strategy == VecStrategy.MIN_A_MAX_BC:
|
||
return (1, m, m)
|
||
elif strategy == VecStrategy.HALF_UNIFORM:
|
||
return (h, h, h)
|
||
elif strategy == VecStrategy.QUARTER_AB_MAX_C:
|
||
return (h, h, m)
|
||
elif strategy == VecStrategy.MAX_AB_QUARTER_C:
|
||
return (m, m, q)
|
||
elif strategy == VecStrategy.MAX_A_QUARTER_BC:
|
||
return (m, q, q)
|
||
elif strategy == VecStrategy.MIN_AB_MAX_C:
|
||
return (1, 1, m)
|
||
elif strategy == VecStrategy.MAX_A_HALF_BC:
|
||
return (m, h, h)
|
||
elif strategy == VecStrategy.MAX_AB_MIN_C:
|
||
return (m, m, 1)
|
||
elif strategy == VecStrategy.MIN_AB_QUARTER_C:
|
||
return (1, 1, q)
|
||
elif strategy == VecStrategy.MIN_A_MAX_B_HALF_C:
|
||
return (1, m, h)
|
||
elif strategy == VecStrategy.HALF_A_MAX_BC:
|
||
return (h, m, m)
|
||
elif strategy == VecStrategy.HALF_A_MIN_BC:
|
||
return (h, 1, 1)
|
||
else:
|
||
return (1, 1, 1)
|
||
|
||
|
||
# =============================================================================
|
||
# Per-Tile VecStrategy Tables
|
||
# =============================================================================
|
||
# Per-variant mapping: tile -> {dtype_class -> list[VecStrategy]}.
|
||
# Derived per (variant, tile, dtype_class) via greedy set-cover over VecStrategy.
|
||
# Tiles not in the table fall back to [GENERIC] (dtype-independent).
|
||
#
|
||
|
||
_FWD_TILE_STRATEGIES: Dict[Tuple[int, int, int], Dict[str, List[VecStrategy]]] = {
|
||
(16, 16, 64): {"half": [VecStrategy.MAX_AB_HALF_C], "float": [VecStrategy.UNIFORM_MAX]},
|
||
(16, 16, 128): {"half": [VecStrategy.MAX_AB_HALF_C], "float": [VecStrategy.UNIFORM_MAX]},
|
||
(16, 32, 64): {"half": [VecStrategy.MAX_AB_HALF_C], "float": [VecStrategy.UNIFORM_MAX]},
|
||
(16, 64, 64): {"half": [VecStrategy.MAX_AB_HALF_C], "float": [VecStrategy.UNIFORM_MAX]},
|
||
(16, 128, 64): {"half": [VecStrategy.MAX_AB_HALF_C], "float": [VecStrategy.UNIFORM_MAX]},
|
||
(16, 256, 64): {"half": [VecStrategy.MAX_AB_HALF_C]},
|
||
(32, 16, 64): {"half": [VecStrategy.MAX_AB_QUARTER_C], "float": [VecStrategy.MAX_AB_HALF_C]},
|
||
(32, 64, 16): {"float": [VecStrategy.UNIFORM_MAX]},
|
||
(32, 64, 32): {"half": [VecStrategy.UNIFORM_MAX, VecStrategy.MIN_AB_MAX_C]},
|
||
(32, 64, 64): {"half": [VecStrategy.UNIFORM_MAX]},
|
||
(32, 128, 16): {"float": [VecStrategy.UNIFORM_MAX]},
|
||
(32, 128, 32): {"half": [VecStrategy.UNIFORM_MAX, VecStrategy.MIN_AB_MAX_C]},
|
||
(32, 128, 64): {"half": [VecStrategy.UNIFORM_MAX]},
|
||
(32, 256, 64): {"half": [VecStrategy.UNIFORM_MAX]},
|
||
(64, 16, 16): {"half": [VecStrategy.HALF_A_MIN_BC], "float": [VecStrategy.MAX_A_MIN_BC]},
|
||
(64, 16, 64): {"half": [VecStrategy.MAX_AB_QUARTER_C], "float": [VecStrategy.MAX_AB_HALF_C]},
|
||
(64, 32, 16): {"float": [VecStrategy.UNIFORM_MAX, VecStrategy.MAX_AB_QUARTER_C]},
|
||
(64, 32, 32): {"half": [VecStrategy.UNIFORM_MAX, VecStrategy.MIN_AB_MAX_C, VecStrategy.MAX_AB_MIN_C]},
|
||
(64, 32, 64): {"half": [VecStrategy.MAX_AB_HALF_C], "float": [VecStrategy.UNIFORM_MAX]},
|
||
(64, 64, 8): {"half": [VecStrategy.MIN_AB_MAX_C]},
|
||
(64, 64, 16): {"float": [VecStrategy.GENERIC, VecStrategy.UNIFORM_MAX]},
|
||
(64, 64, 32): {"half": [VecStrategy.GENERIC, VecStrategy.UNIFORM_MAX, VecStrategy.HALF_UNIFORM, VecStrategy.MIN_AB_MAX_C], "float": [VecStrategy.UNIFORM_MAX]},
|
||
(64, 64, 64): {"half": [VecStrategy.UNIFORM_MAX]},
|
||
(64, 128, 16): {"float": [VecStrategy.UNIFORM_MAX]},
|
||
(64, 128, 32): {"half": [VecStrategy.UNIFORM_MAX, VecStrategy.MIN_AB_MAX_C]},
|
||
(64, 128, 64): {"half": [VecStrategy.UNIFORM_MAX]},
|
||
(128, 16, 64): {"half": [VecStrategy.MAX_AB_QUARTER_C], "float": [VecStrategy.MAX_AB_HALF_C]},
|
||
(128, 32, 16): {"float": [VecStrategy.UNIFORM_MAX]},
|
||
(128, 32, 32): {"half": [VecStrategy.UNIFORM_MAX, VecStrategy.HALF_UNIFORM, VecStrategy.MIN_AB_MAX_C]},
|
||
(128, 32, 64): {"half": [VecStrategy.MAX_AB_HALF_C], "float": [VecStrategy.UNIFORM_MAX]},
|
||
(128, 64, 8): {"half": [VecStrategy.MIN_AB_MAX_C]},
|
||
(128, 64, 16): {"half": [VecStrategy.MIN_AB_MAX_C], "float": [VecStrategy.UNIFORM_MAX]},
|
||
(128, 64, 32): {"half": [VecStrategy.UNIFORM_MAX, VecStrategy.MIN_AB_MAX_C]},
|
||
(128, 64, 64): {"half": [VecStrategy.UNIFORM_MAX]},
|
||
(128, 128, 16): {"float": [VecStrategy.UNIFORM_MAX, VecStrategy.MIN_AB_MAX_C]},
|
||
(128, 128, 32): {"half": [VecStrategy.UNIFORM_MAX, VecStrategy.MIN_AB_MAX_C]},
|
||
(128, 128, 64): {"half": [VecStrategy.UNIFORM_MAX]},
|
||
(128, 192, 16): {"float": [VecStrategy.UNIFORM_MAX]},
|
||
(128, 256, 16): {"float": [VecStrategy.UNIFORM_MAX]},
|
||
(128, 256, 32): {"half": [VecStrategy.UNIFORM_MAX, VecStrategy.MIN_AB_MAX_C]},
|
||
(224, 256, 64): {"half": [VecStrategy.UNIFORM_MAX]},
|
||
(256, 16, 64): {"half": [VecStrategy.MAX_AB_QUARTER_C]},
|
||
(256, 32, 64): {"half": [VecStrategy.MAX_AB_HALF_C]},
|
||
(256, 64, 8): {"half": [VecStrategy.MIN_AB_MAX_C]},
|
||
(256, 128, 16): {"float": [VecStrategy.UNIFORM_MAX]},
|
||
(256, 128, 32): {"half": [VecStrategy.UNIFORM_MAX, VecStrategy.MIN_AB_MAX_C]},
|
||
(256, 224, 64): {"half": [VecStrategy.UNIFORM_MAX]},
|
||
(256, 256, 32): {"half": [VecStrategy.UNIFORM_MAX]},
|
||
}
|
||
|
||
|
||
_BWD_DATA_TILE_STRATEGIES: Dict[Tuple[int, int, int], Dict[str, List[VecStrategy]]] = {
|
||
(16, 64, 32): {"half": [VecStrategy.MAX_AB_HALF_C, VecStrategy.MAX_A_MIN_BC, VecStrategy.MIN_A_MAX_B_HALF_C], "float": [VecStrategy.UNIFORM_MAX, VecStrategy.MAX_A_MIN_BC, VecStrategy.MIN_A_MAX_BC]},
|
||
(32, 64, 32): {"half": [VecStrategy.UNIFORM_MAX], "float": [VecStrategy.UNIFORM_MAX]},
|
||
(32, 128, 32): {"half": [VecStrategy.UNIFORM_MAX], "float": [VecStrategy.UNIFORM_MAX]},
|
||
(64, 16, 16): {"half": [VecStrategy.HALF_A_MIN_BC], "float": [VecStrategy.MAX_A_MIN_BC]},
|
||
(64, 16, 32): {"half": [VecStrategy.MAX_AB_HALF_C, VecStrategy.MAX_A_MIN_BC, VecStrategy.MAX_A_QUARTER_BC, VecStrategy.MIN_A_MAX_B_HALF_C], "float": [VecStrategy.UNIFORM_MAX, VecStrategy.MAX_A_MIN_BC, VecStrategy.MIN_A_MAX_BC]},
|
||
(64, 32, 32): {"half": [VecStrategy.UNIFORM_MAX], "float": [VecStrategy.UNIFORM_MAX]},
|
||
(64, 64, 32): {"half": [VecStrategy.GENERIC, VecStrategy.UNIFORM_MAX], "float": [VecStrategy.GENERIC, VecStrategy.UNIFORM_MAX]},
|
||
(64, 128, 32): {"half": [VecStrategy.UNIFORM_MAX, VecStrategy.MIN_A_MAX_BC], "float": [VecStrategy.UNIFORM_MAX, VecStrategy.MIN_A_MAX_BC]},
|
||
(128, 32, 16): {"half": [VecStrategy.HALF_A_MIN_BC], "float": [VecStrategy.MAX_A_MIN_BC, VecStrategy.MAX_A_HALF_BC]},
|
||
(128, 32, 32): {"half": [VecStrategy.UNIFORM_MAX, VecStrategy.MAX_A_MIN_BC, VecStrategy.MAX_A_QUARTER_BC], "float": [VecStrategy.UNIFORM_MAX, VecStrategy.MAX_A_MIN_BC]},
|
||
(128, 64, 32): {"half": [VecStrategy.UNIFORM_MAX], "float": [VecStrategy.UNIFORM_MAX]},
|
||
(128, 128, 32): {"half": [VecStrategy.UNIFORM_MAX], "float": [VecStrategy.UNIFORM_MAX]},
|
||
(128, 256, 32): {"half": [VecStrategy.UNIFORM_MAX], "float": [VecStrategy.UNIFORM_MAX]},
|
||
(256, 128, 32): {"half": [VecStrategy.UNIFORM_MAX], "float": [VecStrategy.UNIFORM_MAX]},
|
||
}
|
||
|
||
|
||
_BWD_WEIGHT_TILE_STRATEGIES: Dict[Tuple[int, int, int], Dict[str, List[VecStrategy]]] = {
|
||
(16, 16, 32): {"half": [VecStrategy.GENERIC, VecStrategy.MIN_AB_QUARTER_C]},
|
||
(16, 16, 64): {"half": [VecStrategy.GENERIC, VecStrategy.HALF_UNIFORM, VecStrategy.HALF_A_MIN_BC]},
|
||
(16, 32, 64): {"half": [VecStrategy.GENERIC, VecStrategy.MAX_AB_HALF_C], "float": [VecStrategy.HALF_A_MAX_BC]},
|
||
(16, 64, 64): {"half": [VecStrategy.GENERIC, VecStrategy.MAX_AB_HALF_C, VecStrategy.MIN_A_MAX_B_HALF_C]},
|
||
(16, 128, 64): {"half": [VecStrategy.MIN_A_MAX_B_HALF_C]},
|
||
(16, 256, 32): {"half": [VecStrategy.MAX_AB_MIN_C]},
|
||
(16, 256, 64): {"half": [VecStrategy.MIN_A_MAX_B_HALF_C]},
|
||
(32, 16, 64): {"half": [VecStrategy.GENERIC, VecStrategy.HALF_A_MIN_BC], "float": [VecStrategy.MAX_A_HALF_BC]},
|
||
(32, 64, 16): {"float": [VecStrategy.UNIFORM_MAX, VecStrategy.MIN_A_MAX_BC]},
|
||
(32, 64, 32): {"half": [VecStrategy.UNIFORM_MAX]},
|
||
(32, 128, 16): {"float": [VecStrategy.UNIFORM_MAX]},
|
||
(32, 128, 32): {"half": [VecStrategy.UNIFORM_MAX, VecStrategy.MAX_AB_QUARTER_C, VecStrategy.MAX_AB_MIN_C]},
|
||
(64, 16, 64): {"half": [VecStrategy.GENERIC, VecStrategy.MAX_A_MIN_BC, VecStrategy.MAX_A_QUARTER_BC]},
|
||
(64, 32, 16): {"float": [VecStrategy.UNIFORM_MAX]},
|
||
(64, 32, 32): {"half": [VecStrategy.UNIFORM_MAX]},
|
||
(64, 64, 16): {"float": [VecStrategy.GENERIC, VecStrategy.UNIFORM_MAX]},
|
||
(64, 64, 32): {"half": [VecStrategy.UNIFORM_MAX]},
|
||
(64, 64, 64): {"half": [VecStrategy.GENERIC, VecStrategy.UNIFORM_MAX, VecStrategy.MAX_AB_HALF_C, VecStrategy.MAX_AB_QUARTER_C, VecStrategy.HALF_A_MIN_BC], "float": [VecStrategy.GENERIC, VecStrategy.UNIFORM_MAX, VecStrategy.MAX_A_MIN_BC, VecStrategy.MIN_A_MAX_BC]},
|
||
(64, 128, 16): {"float": [VecStrategy.UNIFORM_MAX]},
|
||
(64, 128, 32): {"half": [VecStrategy.UNIFORM_MAX]},
|
||
(64, 128, 64): {"half": [VecStrategy.UNIFORM_MAX]},
|
||
(128, 16, 64): {"half": [VecStrategy.MAX_A_MIN_BC, VecStrategy.MAX_A_QUARTER_BC]},
|
||
(128, 32, 16): {"float": [VecStrategy.UNIFORM_MAX, VecStrategy.MAX_A_MIN_BC]},
|
||
(128, 32, 32): {"half": [VecStrategy.UNIFORM_MAX, VecStrategy.MAX_AB_QUARTER_C, VecStrategy.MAX_A_QUARTER_BC, VecStrategy.MAX_AB_MIN_C]},
|
||
(128, 64, 16): {"float": [VecStrategy.UNIFORM_MAX]},
|
||
(128, 64, 32): {"half": [VecStrategy.UNIFORM_MAX]},
|
||
(128, 128, 16): {"float": [VecStrategy.UNIFORM_MAX]},
|
||
(128, 128, 32): {"half": [VecStrategy.UNIFORM_MAX]},
|
||
(128, 128, 64): {"half": [VecStrategy.MAX_AB_HALF_C, VecStrategy.HALF_UNIFORM]},
|
||
(128, 256, 16): {"float": [VecStrategy.UNIFORM_MAX]},
|
||
(128, 256, 32): {"half": [VecStrategy.UNIFORM_MAX]},
|
||
(256, 16, 64): {"half": [VecStrategy.MAX_A_MIN_BC, VecStrategy.MAX_A_QUARTER_BC]},
|
||
(256, 32, 64): {"half": [VecStrategy.UNIFORM_MAX]},
|
||
(256, 128, 16): {"float": [VecStrategy.UNIFORM_MAX]},
|
||
(256, 128, 32): {"half": [VecStrategy.UNIFORM_MAX]},
|
||
(256, 256, 32): {"half": [VecStrategy.MAX_AB_HALF_C, VecStrategy.HALF_UNIFORM]},
|
||
}
|
||
|
||
|
||
def get_vec_strategies(
|
||
tile_m: int, tile_n: int, tile_k: int, variant: str,
|
||
dtype_class: Optional[str] = None,
|
||
) -> List[VecStrategy]:
|
||
"""Return curated VecStrategy list for a tile+variant (from profiler JSON).
|
||
|
||
Tables are dtype-class-keyed. If ``dtype_class`` ("half"/"float") is given,
|
||
return only that dtype's strategies. If None, return the union across dtype
|
||
classes (preserving order, deduplicated). Falls back to [GENERIC] for tiles
|
||
not in the table.
|
||
"""
|
||
table = {
|
||
"forward": _FWD_TILE_STRATEGIES,
|
||
"bwd_data": _BWD_DATA_TILE_STRATEGIES,
|
||
"bwd_weight": _BWD_WEIGHT_TILE_STRATEGIES,
|
||
}.get(variant, {})
|
||
entry = table.get((tile_m, tile_n, tile_k))
|
||
if entry is None:
|
||
return [VecStrategy.GENERIC]
|
||
if dtype_class is not None:
|
||
return entry.get(dtype_class, [])
|
||
# Union across dtype classes, deduplicated, order-preserving.
|
||
out: List[VecStrategy] = []
|
||
seen = set()
|
||
for dc in ("half", "float"):
|
||
for s in entry.get(dc, []):
|
||
if s not in seen:
|
||
seen.add(s)
|
||
out.append(s)
|
||
return out
|
||
|
||
|
||
# =============================================================================
|
||
# Explicit Per-Tile Vec Overrides (don't fit any strategy family)
|
||
# =============================================================================
|
||
# A small set of (variant, tile) -> extra vec triples used by the JSON profiler
|
||
# configs that no VecStrategy formula produces. These are added on top of the
|
||
# strategy-derived vecs (superset semantics). Includes bwd_data's vec_a=16
|
||
# (A vectorizes along K, beyond the dtype-class max) and a handful of
|
||
# idiosyncratic per-tile triples.
|
||
|
||
_EXTRA_VEC_TRIPLES: Dict[str, Dict[Tuple[int, int, int], Dict[str, List[Tuple[int, int, int]]]]] = {
|
||
"forward": {
|
||
(32, 64, 64): {"float": [(4, 4, 8)]},
|
||
(32, 128, 64): {"float": [(4, 4, 8)]},
|
||
(64, 64, 32): {"half": [(1, 2, 1), (2, 1, 2)], "float": [(1, 2, 1), (2, 1, 2)]},
|
||
(64, 64, 64): {"half": [(8, 8, 1), (8, 8, 4)]},
|
||
(128, 128, 32): {"float": [(4, 4, 8)]},
|
||
(128, 128, 64): {"half": [(8, 8, 1), (8, 8, 8)], "float": [(4, 4, 8)]},
|
||
(256, 128, 32): {"half": [(2, 2, 2)]},
|
||
},
|
||
"bwd_data": {
|
||
(64, 16, 32): {"float": [(8, 1, 1), (8, 2, 2)]},
|
||
(64, 16, 64): {"half": [(16, 1, 1), (16, 2, 2)]},
|
||
(64, 64, 64): {"half": [(8, 8, 4), (8, 8, 8)]},
|
||
(128, 32, 16): {"half": [(4, 2, 2)]},
|
||
(128, 32, 32): {"float": [(8, 1, 1), (8, 2, 2)]},
|
||
(128, 32, 64): {"half": [(16, 1, 1), (16, 2, 2), (16, 8, 8)]},
|
||
(128, 64, 64): {"half": [(8, 8, 8)]},
|
||
(128, 256, 32): {"half": [(8, 4, 8)]},
|
||
(256, 32, 64): {"half": [(8, 8, 8)]},
|
||
(256, 128, 32): {"half": [(4, 8, 8)]},
|
||
(256, 128, 64): {"half": [(8, 8, 4), (8, 8, 8)]},
|
||
},
|
||
"bwd_weight": {
|
||
(16, 16, 32): {"float": [(1, 1, 2)]},
|
||
(16, 16, 64): {"half": [(1, 4, 4)]},
|
||
(16, 32, 64): {"half": [(1, 2, 4), (1, 4, 4), (2, 1, 1), (2, 2, 4), (2, 4, 4)]},
|
||
(16, 64, 64): {"half": [(2, 1, 1), (2, 8, 4), (4, 8, 8)]},
|
||
(16, 128, 32): {"half": [(4, 4, 1)]},
|
||
(16, 128, 64): {"half": [(2, 8, 4)]},
|
||
(16, 256, 64): {"half": [(2, 8, 4)]},
|
||
(32, 16, 64): {"half": [(1, 2, 2), (2, 1, 1), (2, 2, 2), (4, 2, 2)]},
|
||
(32, 32, 32): {"half": [(2, 2, 1), (2, 2, 2)]},
|
||
(32, 64, 32): {"half": [(2, 2, 1), (2, 8, 8), (4, 4, 1), (4, 4, 2)]},
|
||
(64, 16, 64): {"half": [(1, 2, 2)], "float": [(8, 2, 2)]},
|
||
(64, 32, 32): {"half": [(4, 4, 1), (4, 4, 2)]},
|
||
(64, 32, 64): {"half": [(8, 8, 8)]},
|
||
(64, 64, 32): {"half": [(2, 2, 2)]},
|
||
(64, 64, 64): {"half": [(1, 4, 4), (2, 2, 2), (2, 2, 4), (4, 8, 8)]},
|
||
(128, 32, 32): {"float": [(8, 2, 2)]},
|
||
},
|
||
}
|
||
|
||
|
||
def get_extra_vec_triples(
|
||
tile_m: int, tile_n: int, tile_k: int, variant: str,
|
||
dtype_class: Optional[str] = None,
|
||
) -> List[Tuple[int, int, int]]:
|
||
"""Return explicit per-tile vec triples not produced by any VecStrategy.
|
||
|
||
Dtype-class-keyed. If ``dtype_class`` is given, return only that
|
||
dtype's extra triples; if None, return the union across dtype classes.
|
||
Empty for tiles whose vecs are fully covered by strategies.
|
||
"""
|
||
per_dc = _EXTRA_VEC_TRIPLES.get(variant, {}).get((tile_m, tile_n, tile_k))
|
||
if not per_dc:
|
||
return []
|
||
if dtype_class is not None:
|
||
return per_dc.get(dtype_class, [])
|
||
out: List[Tuple[int, int, int]] = []
|
||
seen = set()
|
||
for dc in ("half", "float"):
|
||
for tr in per_dc.get(dc, []):
|
||
if tr not in seen:
|
||
seen.add(tr)
|
||
out.append(tr)
|
||
return out
|
||
|
||
|
||
# bwd_weight wavelet tiles that use the legacy gfx942 warp_tile_k formula
|
||
# (same as streamk native instances) rather than the new get_k_mfma formula.
|
||
# Derived from the CK Builder conf files: these tiles carry warp_tile_k=16 for
|
||
# half (warp_tile_m=16) instead of the new formula value of 32.
|
||
_BWD_WEIGHT_WAVELET_LEGACY_WARP_K_TILES: Set[Tuple[int, int, int]] = {
|
||
(64, 32, 64),
|
||
(64, 64, 64),
|
||
}
|
||
|
||
# =============================================================================
|
||
# Pipeline / Scheduler Rules (per-tile, replaces cross-product)
|
||
# =============================================================================
|
||
|
||
_COMPV4_SET: Set[Tuple[int, int, int]] = set(COMPV4_COMPATIBLE_TILES)
|
||
|
||
|
||
# =============================================================================
|
||
# Curated per-tile (pipeline, scheduler) map
|
||
# =============================================================================
|
||
# Per-variant mapping: tile -> list of (pipeline, scheduler).
|
||
# Preferred over the rule-based computation below; tiles absent here fall back to
|
||
# the shape-based rules.
|
||
|
||
_FWD_TILE_PIPELINES: Dict[Tuple[int, int, int], List[Tuple[str, str]]] = {
|
||
(16, 16, 64): [('compv1', 'interwave'), ('compv1', 'intrawave'), ('mem', 'interwave'), ('mem', 'intrawave')],
|
||
(16, 16, 128): [('compv1', 'interwave'), ('compv1', 'intrawave'), ('mem', 'interwave'), ('mem', 'intrawave')],
|
||
(16, 32, 64): [('compv1', 'interwave'), ('compv1', 'intrawave'), ('mem', 'interwave'), ('mem', 'intrawave')],
|
||
(16, 64, 64): [('mem', 'interwave'), ('mem', 'intrawave')],
|
||
(16, 128, 64): [('mem', 'interwave'), ('mem', 'intrawave')],
|
||
(16, 256, 64): [('mem', 'interwave'), ('mem', 'intrawave')],
|
||
(32, 16, 64): [('compv1', 'interwave'), ('compv1', 'intrawave'), ('mem', 'interwave'), ('mem', 'intrawave')],
|
||
(32, 64, 16): [('compv1', 'intrawave')],
|
||
(32, 64, 32): [('compv1', 'intrawave')],
|
||
(32, 64, 64): [('mem', 'interwave'), ('mem', 'intrawave')],
|
||
(32, 128, 16): [('compv1', 'intrawave')],
|
||
(32, 128, 32): [('compv1', 'intrawave')],
|
||
(32, 128, 64): [('mem', 'interwave'), ('mem', 'intrawave')],
|
||
(32, 256, 64): [('mem', 'interwave'), ('mem', 'intrawave')],
|
||
(64, 16, 16): [('compv1', 'intrawave')],
|
||
(64, 16, 64): [('mem', 'interwave'), ('mem', 'intrawave')],
|
||
(64, 32, 16): [('compv1', 'intrawave')],
|
||
(64, 32, 32): [('compv1', 'intrawave')],
|
||
(64, 32, 64): [('mem', 'interwave'), ('mem', 'intrawave')],
|
||
(64, 64, 8): [('compv1', 'intrawave')],
|
||
(64, 64, 16): [('compv1', 'intrawave')],
|
||
(64, 64, 32): [('compv1', 'intrawave')],
|
||
(64, 64, 64): [('compv3', 'intrawave')],
|
||
(64, 128, 16): [('compv1', 'intrawave')],
|
||
(64, 128, 32): [('compv1', 'intrawave')],
|
||
(64, 128, 64): [('compv3', 'intrawave')],
|
||
(128, 16, 64): [('mem', 'interwave'), ('mem', 'intrawave')],
|
||
(128, 32, 16): [('compv1', 'intrawave')],
|
||
(128, 32, 32): [('compv1', 'interwave'), ('compv1', 'intrawave')],
|
||
(128, 32, 64): [('mem', 'interwave'), ('mem', 'intrawave')],
|
||
(128, 64, 8): [('compv1', 'intrawave')],
|
||
(128, 64, 16): [('compv1', 'intrawave')],
|
||
(128, 64, 32): [('compv1', 'intrawave')],
|
||
(128, 64, 64): [('compv3', 'intrawave')],
|
||
(128, 128, 16): [('compv1', 'intrawave')],
|
||
(128, 128, 32): [('compv1', 'intrawave'), ('compv4', 'intrawave')],
|
||
(128, 128, 64): [('compv1', 'interwave'), ('compv3', 'intrawave'), ('compv4', 'intrawave'), ('compv6', 'intrawave')],
|
||
(128, 192, 16): [('compv1', 'intrawave')],
|
||
(128, 256, 16): [('compv1', 'intrawave')],
|
||
(128, 256, 32): [('compv1', 'interwave'), ('compv1', 'intrawave')],
|
||
(224, 256, 64): [('compv3', 'intrawave')],
|
||
(256, 16, 64): [('mem', 'interwave'), ('mem', 'intrawave')],
|
||
(256, 32, 64): [('mem', 'interwave'), ('mem', 'intrawave')],
|
||
(256, 64, 8): [('compv1', 'intrawave')],
|
||
(256, 128, 16): [('compv1', 'intrawave')],
|
||
(256, 128, 32): [('compv1', 'interwave'), ('compv1', 'intrawave')],
|
||
(256, 224, 64): [('compv3', 'intrawave')],
|
||
(256, 256, 32): [('compv3', 'intrawave'), ('compv4', 'intrawave'), ('compv6', 'intrawave')],
|
||
}
|
||
|
||
_BWD_DATA_TILE_PIPELINES: Dict[Tuple[int, int, int], List[Tuple[str, str]]] = {
|
||
(16, 64, 32): [('compv1', 'intrawave')],
|
||
(32, 64, 32): [('compv1', 'intrawave')],
|
||
(32, 128, 32): [('compv1', 'intrawave')],
|
||
(64, 16, 16): [('compv1', 'intrawave')],
|
||
(64, 16, 32): [('compv1', 'intrawave')],
|
||
(64, 16, 64): [('compv1', 'intrawave')],
|
||
(64, 32, 32): [('compv1', 'intrawave')],
|
||
(64, 64, 32): [('compv1', 'intrawave')],
|
||
(64, 64, 64): [('wavelet', 'intrawave')],
|
||
(64, 128, 32): [('compv1', 'intrawave')],
|
||
(128, 32, 16): [('compv1', 'intrawave')],
|
||
(128, 32, 32): [('compv1', 'intrawave')],
|
||
(128, 32, 64): [('compv1', 'intrawave')],
|
||
(128, 64, 32): [('compv1', 'intrawave')],
|
||
(128, 64, 64): [('wavelet', 'intrawave')],
|
||
(128, 128, 32): [('compv1', 'intrawave')],
|
||
(128, 256, 32): [('compv1', 'intrawave')],
|
||
(256, 32, 64): [('wavelet', 'intrawave')],
|
||
(256, 128, 32): [('compv1', 'intrawave'), ('wavelet', 'intrawave')],
|
||
(256, 128, 64): [('wavelet', 'intrawave')],
|
||
}
|
||
|
||
_BWD_WEIGHT_TILE_PIPELINES: Dict[Tuple[int, int, int], List[Tuple[str, str]]] = {
|
||
(16, 16, 32): [('compv1', 'intrawave'), ('compv6', 'intrawave'), ('mem', 'intrawave')],
|
||
(16, 16, 64): [('compv1', 'interwave'), ('compv1', 'intrawave'), ('mem', 'interwave'), ('mem', 'intrawave')],
|
||
(16, 32, 64): [('basic_async_v1', 'intrawave'), ('compv1', 'interwave'), ('compv1', 'intrawave'), ('mem', 'interwave'), ('mem', 'intrawave')],
|
||
(16, 64, 64): [('basic_async_v1', 'intrawave'), ('mem', 'interwave'), ('mem', 'intrawave')],
|
||
(16, 128, 32): [('compv1', 'intrawave')],
|
||
(16, 128, 64): [('mem', 'interwave'), ('mem', 'intrawave')],
|
||
(16, 256, 32): [('compv1', 'intrawave')],
|
||
(16, 256, 64): [('mem', 'interwave'), ('mem', 'intrawave')],
|
||
(32, 16, 64): [('compv1', 'interwave'), ('compv1', 'intrawave'), ('mem', 'interwave'), ('mem', 'intrawave')],
|
||
(32, 32, 32): [('compv1', 'intrawave'), ('compv6', 'intrawave'), ('mem', 'intrawave')],
|
||
(32, 64, 16): [('compv1', 'intrawave')],
|
||
(32, 64, 32): [('compv1', 'intrawave'), ('compv6', 'intrawave'), ('mem', 'intrawave')],
|
||
(32, 128, 16): [('compv1', 'intrawave')],
|
||
(32, 128, 32): [('compv1', 'intrawave'), ('compv6', 'intrawave'), ('mem', 'intrawave')],
|
||
(64, 16, 64): [('mem', 'interwave'), ('mem', 'intrawave')],
|
||
(64, 32, 16): [('compv1', 'intrawave')],
|
||
(64, 32, 32): [('compv1', 'intrawave'), ('compv6', 'intrawave'), ('mem', 'intrawave')],
|
||
(64, 64, 16): [('compv1', 'intrawave')],
|
||
(64, 64, 32): [('compv1', 'intrawave')],
|
||
(64, 64, 64): [('basic_async_v1', 'intrawave'), ('compv1', 'intrawave')],
|
||
(64, 128, 16): [('compv1', 'intrawave')],
|
||
(64, 128, 32): [('compv1', 'intrawave')],
|
||
(64, 128, 64): [('basic_async_v1', 'intrawave')],
|
||
(128, 16, 64): [('mem', 'interwave'), ('mem', 'intrawave')],
|
||
(128, 32, 16): [('compv1', 'intrawave')],
|
||
(128, 32, 32): [('compv1', 'intrawave'), ('compv6', 'intrawave'), ('mem', 'intrawave')],
|
||
(128, 64, 16): [('compv1', 'intrawave')],
|
||
(128, 64, 32): [('compv1', 'intrawave')],
|
||
(128, 128, 16): [('compv1', 'intrawave')],
|
||
(128, 128, 32): [('compv1', 'intrawave')],
|
||
(128, 128, 64): [('compv1', 'interwave'), ('compv3', 'intrawave'), ('compv4', 'intrawave'), ('compv6', 'intrawave')],
|
||
(128, 256, 16): [('compv1', 'intrawave')],
|
||
(128, 256, 32): [('compv1', 'intrawave')],
|
||
(256, 16, 64): [('mem', 'interwave'), ('mem', 'intrawave')],
|
||
(256, 32, 64): [('basic_async_v1', 'intrawave')],
|
||
(256, 128, 16): [('compv1', 'intrawave')],
|
||
(256, 128, 32): [('compv1', 'intrawave')],
|
||
(256, 256, 32): [('compv3', 'intrawave'), ('compv4', 'intrawave'), ('compv6', 'intrawave')],
|
||
}
|
||
|
||
|
||
# Dtype-class-keyed pipeline overrides for tiles whose (pipeline, scheduler)
|
||
# set differs between half (bf16/fp16) and float (fp32). Consulted first when a
|
||
# dtype_class is provided. Tiles used by only one dtype-class are handled by the
|
||
# ndim+dtype tile tables, so only tiles present in BOTH classes appear here.
|
||
# half = union(bf16, fp16) pipes; float = fp32 pipes (from profiler JSON).
|
||
_BWD_WEIGHT_TILE_PIPELINES_DCLASS: Dict[Tuple[int, int, int], Dict[str, List[Tuple[str, str]]]] = {
|
||
(16, 16, 32): {'half': [('compv1', 'intrawave'), ('compv6', 'intrawave'), ('mem', 'intrawave')], 'float': [('compv6', 'intrawave'), ('mem', 'intrawave')]},
|
||
(16, 32, 64): {'half': [('basic_async_v1', 'intrawave'), ('compv1', 'interwave'), ('compv1', 'intrawave'), ('mem', 'interwave'), ('mem', 'intrawave')], 'float': [('compv1', 'intrawave')]},
|
||
(16, 64, 64): {'half': [('basic_async_v1', 'intrawave'), ('mem', 'interwave'), ('mem', 'intrawave'), ('wavelet', 'intrawave')], 'float': []},
|
||
(32, 16, 64): {'half': [('compv1', 'interwave'), ('compv1', 'intrawave'), ('mem', 'interwave'), ('mem', 'intrawave')], 'float': [('compv1', 'intrawave')]},
|
||
(64, 16, 64): {'half': [('mem', 'interwave'), ('mem', 'intrawave')], 'float': [('mem', 'intrawave')]},
|
||
(64, 32, 64): {'half': [('wavelet', 'intrawave')], 'float': []},
|
||
(64, 64, 64): {'half': [('basic_async_v1', 'intrawave'), ('compv1', 'intrawave'), ('wavelet', 'intrawave')], 'float': [('compv1', 'intrawave')]},
|
||
(128, 32, 32): {'half': [('compv1', 'intrawave'), ('compv6', 'intrawave'), ('mem', 'intrawave')], 'float': [('compv1', 'intrawave')]},
|
||
}
|
||
|
||
_FWD_TILE_PIPELINES_DCLASS: Dict[Tuple[int, int, int], Dict[str, List[Tuple[str, str]]]] = {
|
||
(64, 64, 64): {'half': [('compv3', 'intrawave'), ('wavelet', 'intrawave')], 'float': [('compv3', 'intrawave')]},
|
||
(128, 128, 32): {'half': [('compv1', 'intrawave'), ('compv4', 'intrawave')], 'float': [('compv4', 'intrawave')]},
|
||
(128, 128, 64): {'half': [('compv1', 'interwave'), ('compv3', 'intrawave'), ('compv4', 'intrawave'), ('compv6', 'intrawave'), ('wavelet', 'intrawave')], 'float': [('compv1', 'interwave'), ('compv3', 'intrawave'), ('compv6', 'intrawave')]},
|
||
}
|
||
|
||
|
||
def get_pipelines_for_tile(
|
||
tile_m: int, tile_n: int, tile_k: int, variant: str,
|
||
dtype_class: Optional[str] = None,
|
||
) -> List[Tuple[str, str]]:
|
||
"""Return list of (pipeline, scheduler) pairs for a tile shape and variant.
|
||
|
||
When ``dtype_class`` ("half"/"float") is given and a dtype-class override
|
||
exists for this (variant, tile), that override is returned (trims half-only
|
||
pipelines from fp32 and vice versa). Otherwise prefers the curated per-tile
|
||
map (from profiler JSON) when the tile is present, falling back to the
|
||
shape-based rules below.
|
||
"""
|
||
tile_key = (tile_m, tile_n, tile_k)
|
||
|
||
if dtype_class is not None:
|
||
dclass_table = {
|
||
"forward": _FWD_TILE_PIPELINES_DCLASS,
|
||
"bwd_weight": _BWD_WEIGHT_TILE_PIPELINES_DCLASS,
|
||
}.get(variant, {})
|
||
override = dclass_table.get(tile_key)
|
||
if override is not None and dtype_class in override:
|
||
return list(override[dtype_class])
|
||
|
||
curated = {
|
||
"forward": _FWD_TILE_PIPELINES,
|
||
"bwd_data": _BWD_DATA_TILE_PIPELINES,
|
||
"bwd_weight": _BWD_WEIGHT_TILE_PIPELINES,
|
||
}.get(variant, {})
|
||
if tile_key in curated:
|
||
return list(curated[tile_key])
|
||
|
||
tile_area = tile_m * tile_n
|
||
min_dim = min(tile_m, tile_n)
|
||
|
||
if variant == "forward":
|
||
pipes: List[Tuple[str, str]] = [("compv1", "intrawave")]
|
||
if tile_k >= 32:
|
||
pipes.append(("compv1", "interwave"))
|
||
if min_dim <= 32 and tile_k >= 64:
|
||
pipes.append(("mem", "intrawave"))
|
||
pipes.append(("mem", "interwave"))
|
||
if tile_area >= 4096 and tile_k >= 32:
|
||
pipes.append(("compv3", "intrawave"))
|
||
if tile_key in _COMPV4_SET or (tile_area >= 16384 and tile_k >= 32):
|
||
pipes.append(("compv4", "intrawave"))
|
||
if tile_area >= 4096 and tile_k >= 32:
|
||
pipes.append(("compv6", "intrawave"))
|
||
return pipes
|
||
|
||
elif variant == "bwd_data":
|
||
return [("compv1", "intrawave")]
|
||
|
||
elif variant == "bwd_weight":
|
||
pipes = [("compv1", "intrawave")]
|
||
if tile_k >= 64:
|
||
pipes.append(("compv1", "interwave"))
|
||
if tile_k >= 32:
|
||
pipes.append(("mem", "intrawave"))
|
||
if tile_k >= 64:
|
||
pipes.append(("mem", "interwave"))
|
||
if tile_area >= 4096 and tile_k >= 32:
|
||
pipes.append(("compv3", "intrawave"))
|
||
if tile_key in _COMPV4_SET or (tile_area >= 16384 and tile_k >= 32):
|
||
pipes.append(("compv4", "intrawave"))
|
||
if tile_k >= 32:
|
||
pipes.append(("compv6", "intrawave"))
|
||
if tile_k == 64:
|
||
pipes.append(("basic_async_v1", "intrawave"))
|
||
return pipes
|
||
|
||
return [("compv1", "intrawave")]
|
||
|
||
|
||
# =============================================================================
|
||
# Specialization Rules (per-tile, replaces cross-product)
|
||
# =============================================================================
|
||
|
||
|
||
def get_specs_for_tile(
|
||
tile_m: int, tile_n: int, tile_k: int, variant: str,
|
||
) -> List[str]:
|
||
"""Return list of specialization strings for a tile shape and variant.
|
||
|
||
Rule-based specialization assignment — assigns only the specializations
|
||
that make sense for the given tile dimensions.
|
||
"""
|
||
if variant == "forward":
|
||
if tile_k == 8:
|
||
return ["default"]
|
||
# tile_k >= 16
|
||
specs = ["default", "filter1x1_pad0", "filter1x1_stride1_pad0"]
|
||
if tile_m * tile_n <= 4096:
|
||
specs.append("filter3x3")
|
||
return specs
|
||
elif variant in ("bwd_data", "bwd_weight"):
|
||
return ["default", "filter1x1_stride1_pad0"]
|
||
|
||
return ["default"]
|
||
|
||
|
||
# =============================================================================
|
||
# Pipeline / Scheduler Rules (variant-level, kept for backward compat)
|
||
# =============================================================================
|
||
|
||
# Valid (pipeline, scheduler) pairs per variant
|
||
# Derived from JSON profiler configs (union of bf16 + fp32).
|
||
VARIANT_PIPELINE_SCHEDULER: Dict[str, List[Tuple[str, str]]] = {
|
||
"forward": [
|
||
("compv1", "intrawave"),
|
||
("compv1", "interwave"),
|
||
("compv3", "intrawave"),
|
||
("compv4", "intrawave"),
|
||
("compv6", "intrawave"),
|
||
("mem", "intrawave"),
|
||
("mem", "interwave"),
|
||
],
|
||
"bwd_data": [
|
||
("compv1", "intrawave"),
|
||
],
|
||
"bwd_weight": [
|
||
("compv1", "intrawave"),
|
||
("compv1", "interwave"),
|
||
("compv3", "intrawave"),
|
||
("compv4", "intrawave"),
|
||
("compv6", "intrawave"),
|
||
("mem", "intrawave"),
|
||
("mem", "interwave"),
|
||
("basic_async_v1", "intrawave"),
|
||
],
|
||
}
|
||
|
||
# Specializations per variant
|
||
VARIANT_SPECIALIZATIONS: Dict[str, List[str]] = {
|
||
"forward": ["default", "filter1x1_pad0", "filter1x1_stride1_pad0", "filter3x3"],
|
||
"bwd_data": ["default", "filter1x1_stride1_pad0"],
|
||
"bwd_weight": ["default", "filter1x1_stride1_pad0"],
|
||
}
|
||
|
||
# =============================================================================
|
||
# Feature Flag Rules (Phase 5)
|
||
# =============================================================================
|
||
|
||
|
||
@dataclass
|
||
class StreamKSpec:
|
||
"""StreamK parameters for a feature config."""
|
||
strategy: str = "TREE" # "TREE" | "LINEAR"
|
||
persistent: bool = False # non-persistent by default
|
||
|
||
|
||
@dataclass
|
||
class FeatureSpec:
|
||
"""A single feature-flag variant rule.
|
||
|
||
Fields that are False/1/None represent 'off'.
|
||
tile_override / pipeline_override = None means use variant defaults.
|
||
"""
|
||
split_image: bool = False
|
||
explicit_gemm: bool = False
|
||
two_stage: bool = False
|
||
double_smem_buffer: bool = False
|
||
num_groups_to_merge: int = 1
|
||
streamk_config: Optional[StreamKSpec] = None
|
||
|
||
# Optional overrides (None = use variant defaults)
|
||
tile_override: Optional[List[Tuple[int, int, int]]] = None
|
||
pipeline_override: Optional[List[Tuple[str, str]]] = None
|
||
|
||
# Optional restrictions (None = applies to all). When set, the feature is
|
||
# only emitted for the listed dtype classes ("half"/"float") and/or ndims.
|
||
dtype_classes: Optional[List[str]] = None
|
||
ndims: Optional[List[int]] = None
|
||
|
||
|
||
# Tiles used per feature (derived from JSON profiler analysis).
|
||
# Restricting features to specific tiles prevents config explosion.
|
||
_FWD_GM_TILES: List[Tuple[int, int, int]] = [
|
||
(64, 16, 16), (128, 32, 32),
|
||
]
|
||
_BWD_EG_TILES: List[Tuple[int, int, int]] = [
|
||
(16, 16, 64), (16, 32, 64), (16, 64, 64), (16, 128, 64), (16, 256, 64),
|
||
(32, 16, 64), (64, 16, 64), (128, 16, 64), (128, 128, 64),
|
||
(256, 16, 64), (256, 256, 32),
|
||
]
|
||
_BWD_EG_2S_TILES: List[Tuple[int, int, int]] = [
|
||
(16, 16, 64), (16, 32, 64), (16, 64, 64),
|
||
(32, 16, 64), (64, 16, 64), (128, 16, 64), (256, 16, 64),
|
||
]
|
||
_BWD_2S_TILES: List[Tuple[int, int, int]] = [
|
||
(16, 16, 32), (64, 64, 64),
|
||
]
|
||
_BWD_GM2_2S_TILES: List[Tuple[int, int, int]] = [
|
||
(32, 32, 32), (32, 64, 32),
|
||
]
|
||
_BWD_GM4_2S_TILES: List[Tuple[int, int, int]] = [
|
||
(16, 128, 32), (32, 64, 32), (64, 32, 32),
|
||
]
|
||
_BWD_GM8_2S_TILES: List[Tuple[int, int, int]] = [
|
||
(16, 256, 32), (32, 128, 32), (128, 32, 32),
|
||
]
|
||
_BWD_SK_TILES: List[Tuple[int, int, int]] = [
|
||
(16, 16, 32), (16, 32, 64), (32, 16, 64),
|
||
(64, 16, 64), (64, 64, 64), (128, 32, 32),
|
||
]
|
||
|
||
VARIANT_FEATURES: Dict[str, List[FeatureSpec]] = {
|
||
"forward": [
|
||
# split_image: small tile subset, compv1/intrawave only
|
||
FeatureSpec(
|
||
split_image=True,
|
||
tile_override=_SPLIT_IMAGE_TILES,
|
||
pipeline_override=[("compv1", "intrawave")],
|
||
),
|
||
# num_groups_to_merge (restricted to tiles that benefit from merging)
|
||
FeatureSpec(num_groups_to_merge=8, tile_override=_FWD_GM_TILES),
|
||
FeatureSpec(num_groups_to_merge=16, tile_override=[(64, 16, 16)]),
|
||
FeatureSpec(num_groups_to_merge=32, tile_override=[(64, 16, 16)]),
|
||
],
|
||
"bwd_data": [
|
||
# num_groups_to_merge=32 wavelet instance for the 256x32x64 tile
|
||
FeatureSpec(
|
||
num_groups_to_merge=32,
|
||
tile_override=[(256, 32, 64)],
|
||
pipeline_override=[("wavelet", "intrawave")],
|
||
dtype_classes=["half"],
|
||
),
|
||
],
|
||
"bwd_weight": [
|
||
# explicit_gemm / two_stage / num_groups_to_merge are half-only:
|
||
# the fp32 bwd_weight profiler JSON contains none of these features.
|
||
# explicit_gemm only: tiles with tile_k=64 (larger internal GEMM)
|
||
FeatureSpec(explicit_gemm=True, tile_override=_BWD_EG_TILES, dtype_classes=["half"]),
|
||
# two_stage only
|
||
FeatureSpec(two_stage=True, tile_override=_BWD_2S_TILES, dtype_classes=["half"]),
|
||
# two_stage + explicit_gemm
|
||
FeatureSpec(two_stage=True, explicit_gemm=True, tile_override=_BWD_EG_2S_TILES, dtype_classes=["half"]),
|
||
# num_groups_to_merge + two_stage combinations
|
||
FeatureSpec(num_groups_to_merge=2, two_stage=True, tile_override=_BWD_GM2_2S_TILES, dtype_classes=["half"]),
|
||
FeatureSpec(num_groups_to_merge=4, two_stage=True, tile_override=_BWD_GM4_2S_TILES, dtype_classes=["half"]),
|
||
FeatureSpec(num_groups_to_merge=8, two_stage=True, tile_override=_BWD_GM8_2S_TILES, dtype_classes=["half"]),
|
||
# basic_async_v1 + num_groups_to_merge=2
|
||
FeatureSpec(
|
||
num_groups_to_merge=2,
|
||
tile_override=[(16, 32, 64), (16, 64, 64), (64, 128, 64)],
|
||
pipeline_override=[("basic_async_v1", "intrawave")],
|
||
dtype_classes=["half"],
|
||
),
|
||
# StreamK non-persistent
|
||
FeatureSpec(
|
||
streamk_config=StreamKSpec(strategy="TREE", persistent=False),
|
||
tile_override=_BWD_SK_TILES,
|
||
pipeline_override=[
|
||
("compv1", "intrawave"),
|
||
("mem", "intrawave"),
|
||
("basic_async_v1", "intrawave"),
|
||
],
|
||
),
|
||
# StreamK persistent
|
||
FeatureSpec(
|
||
streamk_config=StreamKSpec(strategy="TREE", persistent=True),
|
||
tile_override=_BWD_SK_TILES,
|
||
pipeline_override=[
|
||
("compv1", "intrawave"),
|
||
("mem", "intrawave"),
|
||
("basic_async_v1", "intrawave"),
|
||
],
|
||
),
|
||
],
|
||
}
|
||
|
||
# =============================================================================
|
||
# Pipeline Variant Suffixes (single source of truth — kept for backward compat)
|
||
# =============================================================================
|
||
# Empirically verified valid (pipeline, wave_mode, has_dsb, has_si) combinations
|
||
# observed in the 2D and 3D bf16 gfx950 benchmark CSVs. 30 entries total per ndim.
|
||
# Each tuple: (pipeline, wave_mode, has_dsb, has_si)
|
||
# wave_mode: "intrawave" | "interwave"
|
||
# has_dsb: 1 if "_dsb" suffix present (double smem buffer), else 0
|
||
# has_si: 1 if "_si" suffix present (store immediate), else 0
|
||
PIPELINE_VARIANTS: List[Tuple[str, str, int, int]] = [
|
||
# basic_v1: both intra/inter × {∅, dsb, si, dsb_si} = 8 combos
|
||
("basic_v1", "intrawave", 0, 0),
|
||
("basic_v1", "intrawave", 1, 0),
|
||
("basic_v1", "intrawave", 0, 1),
|
||
("basic_v1", "intrawave", 1, 1),
|
||
("basic_v1", "interwave", 0, 0),
|
||
("basic_v1", "interwave", 1, 0),
|
||
("basic_v1", "interwave", 0, 1),
|
||
("basic_v1", "interwave", 1, 1),
|
||
# compv3: intrawave × {∅, dsb, si, dsb_si} = 4 combos
|
||
("compv3", "intrawave", 0, 0),
|
||
("compv3", "intrawave", 1, 0),
|
||
("compv3", "intrawave", 0, 1),
|
||
("compv3", "intrawave", 1, 1),
|
||
# compv4: intrawave × {dsb, dsb_si} only = 2 combos
|
||
("compv4", "intrawave", 1, 0),
|
||
("compv4", "intrawave", 1, 1),
|
||
# compv5: intrawave × {∅, dsb, si, dsb_si} = 4 combos
|
||
("compv5", "intrawave", 0, 0),
|
||
("compv5", "intrawave", 1, 0),
|
||
("compv5", "intrawave", 0, 1),
|
||
("compv5", "intrawave", 1, 1),
|
||
# compv6: intrawave × {∅, dsb, si, dsb_si} = 4 combos
|
||
("compv6", "intrawave", 0, 0),
|
||
("compv6", "intrawave", 1, 0),
|
||
("compv6", "intrawave", 0, 1),
|
||
("compv6", "intrawave", 1, 1),
|
||
# mem: both intra/inter × {∅, dsb, si, dsb_si} = 8 combos
|
||
("mem", "intrawave", 0, 0),
|
||
("mem", "intrawave", 1, 0),
|
||
("mem", "intrawave", 0, 1),
|
||
("mem", "intrawave", 1, 1),
|
||
("mem", "interwave", 0, 0),
|
||
("mem", "interwave", 1, 0),
|
||
("mem", "interwave", 0, 1),
|
||
("mem", "interwave", 1, 1),
|
||
]
|
||
|
||
|
||
def iter_pipeline_variants(pipelines: List[str] = None):
|
||
"""Iterate (pipeline, wave_mode, has_dsb, has_si) tuples, optionally filtered.
|
||
|
||
Args:
|
||
pipelines: optional list of pipeline names to keep. If None, yield all.
|
||
"""
|
||
if pipelines is None:
|
||
for entry in PIPELINE_VARIANTS:
|
||
yield entry
|
||
return
|
||
keep = set(pipelines)
|
||
for entry in PIPELINE_VARIANTS:
|
||
if entry[0] in keep:
|
||
yield entry
|
||
|
||
|
||
# Valid pipelines per variant (kept for backward compat; full list)
|
||
VARIANT_PIPELINES: Dict[str, List[str]] = {
|
||
"forward": [
|
||
"basic_v1",
|
||
"mem",
|
||
"compv3",
|
||
"compv4",
|
||
"compv5",
|
||
"compv6",
|
||
"comp_async",
|
||
"basic_async_v1",
|
||
],
|
||
"bwd_data": [
|
||
"basic_v1",
|
||
"mem",
|
||
"compv3",
|
||
"compv4",
|
||
"compv5",
|
||
"compv6",
|
||
"comp_async",
|
||
"basic_async_v1",
|
||
],
|
||
"bwd_weight": [
|
||
"basic_v1",
|
||
"mem",
|
||
"compv3",
|
||
"compv4",
|
||
"compv5",
|
||
"compv6",
|
||
"comp_async",
|
||
"basic_async_v1",
|
||
],
|
||
}
|
||
|
||
# =============================================================================
|
||
# Shared Validation Rules
|
||
# =============================================================================
|
||
# These functions are the single source of truth for validation rules
|
||
# for convolution code generation.
|
||
|
||
# --- Vector size validation ---
|
||
|
||
|
||
def is_valid_vector_size(vec: int) -> bool:
|
||
"""AMD GPUs only support vector widths 1, 2, 4, 8, 16."""
|
||
return vec == 1 or vec % 2 == 0
|
||
|
||
|
||
def check_vectors(vec_a: int, vec_b: int, vec_c: int) -> bool:
|
||
"""Check all three vector sizes are valid (1 or even)."""
|
||
return all(is_valid_vector_size(v) for v in (vec_a, vec_b, vec_c))
|
||
|
||
|
||
|
||
# --- Pipeline-variant restrictions ---
|
||
|
||
UNSUPPORTED_VARIANT_PIPELINES = {
|
||
"bwd_weight": {"compv5"},
|
||
"bwd_data": {"compv5"},
|
||
}
|
||
|
||
|
||
def is_valid_pipeline_for_variant(pipeline: str, variant: str) -> bool:
|
||
"""Check pipeline is supported for the given conv variant.
|
||
|
||
Backward weight and backward data reject compv5 due to transpose_tile2d /
|
||
get_length issues.
|
||
"""
|
||
blocked = UNSUPPORTED_VARIANT_PIPELINES.get(variant, set())
|
||
return pipeline not in blocked
|
||
|
||
|
||
# --- Stream-K restrictions ---
|
||
|
||
|
||
def is_streamk_valid_for_variant(variant: str) -> bool:
|
||
"""Stream-K is only supported for backward weight."""
|
||
return variant == "bwd_weight"
|
||
|
||
|
||
# =============================================================================
|
||
# Depthwise Convolution Parameter Space
|
||
# =============================================================================
|
||
|
||
DEPTHWISE_TILE_SIZES: List[Tuple[int, int]] = [
|
||
(8, 8), (14, 28), (16, 16), (28, 28), (32, 32),
|
||
]
|
||
|
||
DEPTHWISE_FILTER_SIZES: List[int] = [3, 5]
|
||
|
||
DEPTHWISE_STRIDES: List[Tuple[int, int]] = [(1, 1), (2, 2)]
|
||
|
||
# Curated depthwise configs matching the JSON profiler set.
|
||
# Each tuple: (tile_h, tile_w, filt, str_h, str_w, sub_h, sub_w, nbatch, in_vec, out_vec)
|
||
# Padding is derived: pad = (filt - 1) // 2.
|
||
# Validated by is_valid_depthwise_config() at module load time.
|
||
DEPTHWISE_PARAMS: List[Tuple[int, ...]] = [
|
||
# Filter 3, Stride (1,1)
|
||
(8, 8, 3, 1, 1, 2, 2, 8, 2, 2),
|
||
(16, 16, 3, 1, 1, 1, 4, 8, 8, 8),
|
||
(16, 16, 3, 1, 1, 2, 2, 1, 2, 2),
|
||
(28, 28, 3, 1, 1, 4, 4, 1, 8, 8),
|
||
(32, 32, 3, 1, 1, 4, 4, 1, 8, 8),
|
||
# Filter 3, Stride (2,2)
|
||
(14, 28, 3, 2, 2, 2, 4, 1, 8, 8),
|
||
(16, 16, 3, 2, 2, 1, 4, 1, 8, 8),
|
||
(16, 16, 3, 2, 2, 1, 4, 2, 8, 8),
|
||
(16, 16, 3, 2, 2, 2, 2, 1, 2, 2),
|
||
(16, 16, 3, 2, 2, 2, 2, 1, 8, 8),
|
||
(32, 32, 3, 2, 2, 2, 8, 1, 8, 8),
|
||
(32, 32, 3, 2, 2, 4, 4, 1, 4, 4),
|
||
(32, 32, 3, 2, 2, 4, 4, 1, 8, 8),
|
||
(32, 32, 3, 2, 2, 4, 4, 2, 8, 8),
|
||
# Filter 5, Stride (1,1)
|
||
(8, 8, 5, 1, 1, 1, 1, 1, 1, 1),
|
||
(8, 8, 5, 1, 1, 2, 2, 8, 2, 2),
|
||
(16, 16, 5, 1, 1, 1, 4, 1, 8, 8),
|
||
(16, 16, 5, 1, 1, 1, 4, 8, 8, 8),
|
||
(28, 28, 5, 1, 1, 4, 4, 8, 8, 8),
|
||
(32, 32, 5, 1, 1, 4, 4, 4, 8, 8),
|
||
]
|
||
|
||
|
||
def get_depthwise_configs():
|
||
"""Get curated depthwise convolution configurations.
|
||
|
||
Returns the profiler config set, with each entry validated by
|
||
tile_math.is_valid_depthwise_config().
|
||
|
||
Returns:
|
||
List of tile_math.DepthwiseConfig objects.
|
||
"""
|
||
from .tile_math import DepthwiseConfig, is_valid_depthwise_config
|
||
|
||
configs = []
|
||
for params in DEPTHWISE_PARAMS:
|
||
th, tw, filt, sh, sw, sub_h, sub_w, nb, iv, ov = params
|
||
pad = (filt - 1) // 2
|
||
cfg = DepthwiseConfig(th, tw, filt, sh, sw, pad, pad, nb, sub_h, sub_w, iv, ov)
|
||
assert is_valid_depthwise_config(cfg), f"Invalid depthwise config: {params}"
|
||
configs.append(cfg)
|
||
return configs
|
||
|
||
|
||
log = logging.getLogger(__name__)
|
||
|
||
|
||
def get_configs(
|
||
arch: str,
|
||
variants: List,
|
||
ndims: List[int],
|
||
datatypes: List[str],
|
||
) -> List:
|
||
"""Build all available configs for the "profiler" (JSON-derived) rule set.
|
||
|
||
Unified rule-set entry point used by
|
||
``unified_grouped_conv_codegen.get_default_configs``. Iterates over all tile
|
||
shapes per (variant, ndim, datatype), selecting wave/warp pairs, vector
|
||
sizes, pipelines, and specializations via this module's rule helpers; each
|
||
emitted config is tagged with its concrete ``datatype``.
|
||
"""
|
||
from unified_grouped_conv_codegen import (
|
||
GroupedConvVariant,
|
||
GroupedConvTraitConfig,
|
||
GroupedConvKernelConfig,
|
||
TileConfig,
|
||
StreamKConfig,
|
||
StreamKReductionStrategy,
|
||
DepthwiseConvKernelConfig,
|
||
)
|
||
|
||
seen: set = set()
|
||
configs: List[Union[GroupedConvKernelConfig, DepthwiseConvKernelConfig]] = []
|
||
|
||
def _add_config(config: "GroupedConvKernelConfig") -> None:
|
||
"""Deduplicate and add config if arch-valid."""
|
||
if not config.is_valid_for_arch():
|
||
return
|
||
key = (
|
||
config.variant,
|
||
config.ndim_spatial,
|
||
config.tile.tile_m, config.tile.tile_n, config.tile.tile_k,
|
||
config.tile.warp_m, config.tile.warp_n, config.tile.warp_k,
|
||
config.tile.warp_tile_m, config.tile.warp_tile_n, config.tile.warp_tile_k,
|
||
config.trait.pipeline, config.trait.scheduler, config.trait.epilogue,
|
||
config.vector_size_a, config.vector_size_b, config.vector_size_c,
|
||
config.trait.double_smem_buffer, config.trait.two_stage,
|
||
config.trait.explicit_gemm, config.trait.split_image,
|
||
config.trait.num_groups_to_merge, config.trait.specialization,
|
||
getattr(config.trait.streamk_config, "streamk_enabled", False),
|
||
getattr(config.trait.streamk_config, "streamk_persistent", False),
|
||
config.datatype,
|
||
)
|
||
if key in seen:
|
||
return
|
||
seen.add(key)
|
||
configs.append(config)
|
||
|
||
def _make_streamk_config(spec_sk) -> "StreamKConfig":
|
||
"""Convert a StreamKSpec from this module into a StreamKConfig."""
|
||
return StreamKConfig(
|
||
streamk_enabled=True,
|
||
strategy=StreamKReductionStrategy.TREE if spec_sk.strategy == "TREE"
|
||
else StreamKReductionStrategy.LINEAR,
|
||
streamk_persistent=spec_sk.persistent,
|
||
)
|
||
|
||
def _emit_configs_for_tile(
|
||
tile_m: int, tile_n: int, tile_k: int,
|
||
pipelines: List[Tuple[str, str]],
|
||
specializations: List[str],
|
||
variant,
|
||
ndim: int,
|
||
datatype: str,
|
||
dtype_class: str,
|
||
dtype_key: str,
|
||
feat: Optional["FeatureSpec"] = None,
|
||
) -> None:
|
||
"""Emit all valid configs for a single tile shape + pipeline list, for
|
||
one concrete datatype. Wave/warp pairs and vec triples are selected for
|
||
this dtype only; each emitted config is tagged with ``datatype``."""
|
||
|
||
var_str = {
|
||
GroupedConvVariant.FORWARD: "forward",
|
||
GroupedConvVariant.BACKWARD_DATA: "bwd_data",
|
||
GroupedConvVariant.BACKWARD_WEIGHT: "bwd_weight",
|
||
}.get(variant, "forward")
|
||
|
||
pairs = get_wave_warp_pairs(tile_m, tile_n, tile_k, var_str, dtype_key, arch)
|
||
if not pairs:
|
||
return
|
||
|
||
# Candidate vec triples for this dtype class (strategies + long-tail
|
||
# extras). Gated per (wave, warp) below against tile_math, the authority.
|
||
vec_set: set = set()
|
||
for strategy in get_vec_strategies(tile_m, tile_n, tile_k, var_str, dtype_class):
|
||
vec_set.add(compute_vector_size(strategy, dtype_class))
|
||
for triple in get_extra_vec_triples(tile_m, tile_n, tile_k, var_str, dtype_class):
|
||
vec_set.add(triple)
|
||
vec_list = sorted(vec_set)
|
||
|
||
# bwd_weight wavelet tiles that use legacy warp_tile_k (gfx942 convention)
|
||
_bww_legacy = (
|
||
var_str == "bwd_weight"
|
||
and (tile_m, tile_n, tile_k) in _BWD_WEIGHT_WAVELET_LEGACY_WARP_K_TILES
|
||
)
|
||
|
||
for (wave_m, wave_n, wave_k), (warp_tile_m, warp_tile_n) in pairs:
|
||
# Derive warp_tile_k (the MFMA K) for the non-streamk case; this is
|
||
# the value used both for the vec gate and for non-streamk configs.
|
||
# StreamK configs re-derive it per-config below.
|
||
# Some bwd_weight wavelet tiles use the legacy gfx942 formula.
|
||
warp_tile_k = compute_warp_tile_k(
|
||
dtype_key, warp_tile_m, tile_k, streamk=False, use_legacy=_bww_legacy,
|
||
)
|
||
# tile_math gate: a candidate vec triple survives only if it is valid
|
||
# for this (tile, wave, warp) under this dtype_key.
|
||
tm_valid = get_all_valid_vector_sizes(
|
||
tile_m, tile_n, tile_k,
|
||
wave_m, wave_n, wave_k,
|
||
warp_tile_m, warp_tile_n, warp_tile_k,
|
||
dtype_key,
|
||
)
|
||
gated_vecs = []
|
||
for triple in vec_list:
|
||
if triple in tm_valid:
|
||
gated_vecs.append(triple)
|
||
else:
|
||
log.debug(
|
||
"tile_math rejects vec %s for tile=(%d,%d,%d) "
|
||
"wave=(%d,%d,%d) warp=(%d,%d,%d)",
|
||
triple, tile_m, tile_n, tile_k,
|
||
wave_m, wave_n, wave_k,
|
||
warp_tile_m, warp_tile_n, warp_tile_k,
|
||
)
|
||
|
||
for vec_a, vec_b, vec_c in gated_vecs:
|
||
for pipeline, scheduler in pipelines:
|
||
# The wavelet pipeline uses its own direct kernel path and
|
||
# does not implement the UniversalGemmKernel interface that
|
||
# the explicit_gemm path routes through (its operator() takes
|
||
# 4 arguments, not the 6 UniversalGemmKernel::RunGemm passes).
|
||
# The two are therefore incompatible.
|
||
if pipeline == "wavelet" and feat and feat.explicit_gemm:
|
||
continue
|
||
|
||
# KNOWN LIMITATION (bwd_weight, half precision):
|
||
# The interwave pipelines -- "compv1" (runtime BASIC_V1) and
|
||
# "mem" (runtime MEMORY) -- produce INCORRECT results for the
|
||
# bwd_weight Default specialization when the macro tile is
|
||
# split across multiple waves (wave_m * wave_n > 1) in fp16/bf16
|
||
# on grouped convolutions with a small per-group GEMM-M
|
||
# (per-group K). Observed failing tiles: 64x64x64 (2,2,1),
|
||
# 64x16x64 (2,1,1), 16x32x64 (1,2,1) -- ~50% of the weight
|
||
# output (whole groups) comes back zero.
|
||
#
|
||
# This problems shows up when the CK Tile convolution integration
|
||
# tests are executed against the instances generated with "full" rule set.
|
||
# Rule set "tests" doesn't create the problematic instances.
|
||
|
||
# compv4 forces double_smem_buffer
|
||
dsb = (pipeline == "compv4") or (feat.double_smem_buffer if feat else False)
|
||
|
||
for spec in specializations:
|
||
# Build StreamKConfig
|
||
if feat and feat.streamk_config:
|
||
sk_cfg = _make_streamk_config(feat.streamk_config)
|
||
else:
|
||
sk_cfg = StreamKConfig() # disabled by default
|
||
|
||
trait = GroupedConvTraitConfig(
|
||
pipeline=pipeline,
|
||
scheduler=scheduler,
|
||
epilogue="cshuffle",
|
||
double_smem_buffer=dsb,
|
||
pad_m=True,
|
||
pad_n=True,
|
||
pad_k=True,
|
||
two_stage=(feat.two_stage if feat else False),
|
||
explicit_gemm=(feat.explicit_gemm if feat else False),
|
||
split_image=(feat.split_image if feat else False),
|
||
num_groups_to_merge=(feat.num_groups_to_merge if feat else 1),
|
||
specialization=spec,
|
||
streamk_config=sk_cfg,
|
||
)
|
||
|
||
if not trait.is_valid():
|
||
continue
|
||
|
||
# Derive warp_tile_k for this config. Hand-written StreamK
|
||
# native instances bypass get_k_mfma and carry the legacy
|
||
# warp_tile_k, handled inside compute_warp_tile_k.
|
||
# Same legacy convention applies to some bwd_weight wavelet tiles.
|
||
eff_warp_tile_k = compute_warp_tile_k(
|
||
dtype_key, warp_tile_m, tile_k,
|
||
streamk=sk_cfg.streamk_enabled,
|
||
use_legacy=_bww_legacy and pipeline == "wavelet",
|
||
)
|
||
|
||
tile_cfg = TileConfig(
|
||
tile_m=tile_m,
|
||
tile_n=tile_n,
|
||
tile_k=tile_k,
|
||
warp_m=wave_m,
|
||
warp_n=wave_n,
|
||
warp_k=wave_k,
|
||
warp_tile_m=warp_tile_m,
|
||
warp_tile_n=warp_tile_n,
|
||
warp_tile_k=eff_warp_tile_k,
|
||
)
|
||
|
||
if not tile_cfg.is_valid():
|
||
continue
|
||
|
||
config = GroupedConvKernelConfig(
|
||
tile=tile_cfg,
|
||
trait=trait,
|
||
variant=variant,
|
||
ndim_spatial=ndim,
|
||
arch=arch,
|
||
vector_size_a=vec_a,
|
||
vector_size_b=vec_b,
|
||
vector_size_c=vec_c,
|
||
datatype=datatype,
|
||
)
|
||
_add_config(config)
|
||
|
||
for variant in variants:
|
||
# FORWARD_DEPTHWISE has no GEMM tile loop; its configs are emitted by the
|
||
# depthwise block below (tied to FORWARD). Skip the GEMM loop for it.
|
||
variant_str = {
|
||
GroupedConvVariant.FORWARD: "forward",
|
||
GroupedConvVariant.BACKWARD_DATA: "bwd_data",
|
||
GroupedConvVariant.BACKWARD_WEIGHT: "bwd_weight",
|
||
GroupedConvVariant.FORWARD_DEPTHWISE: None,
|
||
}.get(variant)
|
||
|
||
if variant_str is not None:
|
||
features = VARIANT_FEATURES.get(variant_str, [])
|
||
|
||
for ndim in ndims:
|
||
for datatype in datatypes:
|
||
dtype_key = DTYPE_TO_DTYPE_KEY.get(datatype)
|
||
if dtype_key is None:
|
||
continue
|
||
dclass = "float" if datatype == "fp32" else "half"
|
||
|
||
# Tiles for this exact (variant, ndim, dtype).
|
||
base_tiles = get_tiles(variant_str, ndim, datatype)
|
||
|
||
# --- Base configs (no feature flags) ---
|
||
for tile_m, tile_n, tile_k in base_tiles:
|
||
tile_pipelines = get_pipelines_for_tile(
|
||
tile_m, tile_n, tile_k, variant_str, dclass
|
||
)
|
||
tile_specs = get_specs_for_tile(tile_m, tile_n, tile_k, variant_str)
|
||
_emit_configs_for_tile(
|
||
tile_m, tile_n, tile_k,
|
||
tile_pipelines, tile_specs,
|
||
variant, ndim,
|
||
datatype, dclass, dtype_key,
|
||
feat=None,
|
||
)
|
||
|
||
# --- Feature-flag configs ---
|
||
for feat in features:
|
||
if feat.dtype_classes is not None and dclass not in feat.dtype_classes:
|
||
continue
|
||
if feat.ndims is not None and ndim not in feat.ndims:
|
||
continue
|
||
feat_tiles = feat.tile_override if feat.tile_override is not None else base_tiles
|
||
for tile_m, tile_n, tile_k in feat_tiles:
|
||
if feat.pipeline_override is not None:
|
||
feat_pipes = feat.pipeline_override
|
||
else:
|
||
feat_pipes = get_pipelines_for_tile(
|
||
tile_m, tile_n, tile_k, variant_str, dclass
|
||
)
|
||
tile_specs = get_specs_for_tile(tile_m, tile_n, tile_k, variant_str)
|
||
_emit_configs_for_tile(
|
||
tile_m, tile_n, tile_k,
|
||
feat_pipes, tile_specs,
|
||
variant, ndim,
|
||
datatype, dclass, dtype_key,
|
||
feat=feat,
|
||
)
|
||
|
||
# --- Depthwise configs (forward only, 2D only) ---
|
||
if variant == GroupedConvVariant.FORWARD and 2 in ndims:
|
||
dw_seen: set = set()
|
||
for dw_cfg in get_depthwise_configs():
|
||
for dt in (datatypes or ["fp16"]):
|
||
dw_key = (dw_cfg.tile_h, dw_cfg.tile_w, dw_cfg.filt,
|
||
dw_cfg.str_h, dw_cfg.str_w, dw_cfg.pad_h,
|
||
dw_cfg.pad_w, dw_cfg.nbatch, dw_cfg.sub_h,
|
||
dw_cfg.sub_w, dw_cfg.in_vec, dw_cfg.out_vec, dt)
|
||
if dw_key in dw_seen:
|
||
continue
|
||
dw_seen.add(dw_key)
|
||
configs.append(DepthwiseConvKernelConfig(
|
||
tile_h=dw_cfg.tile_h, tile_w=dw_cfg.tile_w,
|
||
filt=dw_cfg.filt,
|
||
str_h=dw_cfg.str_h, str_w=dw_cfg.str_w,
|
||
pad_h=dw_cfg.pad_h, pad_w=dw_cfg.pad_w,
|
||
nbatch=dw_cfg.nbatch,
|
||
sub_h=dw_cfg.sub_h, sub_w=dw_cfg.sub_w,
|
||
in_vec=dw_cfg.in_vec, out_vec=dw_cfg.out_vec,
|
||
ndim_spatial=2,
|
||
arch=arch,
|
||
layout="ngchw",
|
||
datatype=dt,
|
||
))
|
||
|
||
return configs
|
||
|
||
|
||
if __name__ == "__main__":
|
||
all_tiles = sorted(
|
||
set(get_tiles_for_variant("forward"))
|
||
| set(get_tiles_for_variant("bwd_data"))
|
||
| set(get_tiles_for_variant("bwd_weight"))
|
||
)
|
||
print(f"Total unique tiles: {len(all_tiles)}")
|
||
for variant in ("forward", "bwd_data", "bwd_weight"):
|
||
print(f" {variant}: {len(get_tiles_for_variant(variant))}")
|