This commit is contained in:
Ding, Yi
2026-03-11 23:03:20 -04:00
commit e6cd3f1e3f
6330 changed files with 1132789 additions and 0 deletions

View File

@@ -0,0 +1,263 @@
# Build Trace Analysis
Simple to use, fast python tools for analyzing Clang `-ftime-trace` build performance data.
## Overview
We're kicking off a systematic effort to dramatically reduce CK and CK-Tile build times, [#3575](https://github.com/ROCm/composable_kernel/issues/3575). A key part of this work is improving our C++ metaprogramming to reduce the burden on the compiler.
In order to prioritize work and measure our progress, we need data on template instantiation. For single files, Clang's `-ftime-trace` build performance data is easy to analyze with the Perfetto UI. The problem we are solving here is how to analyze instantiation data across thousands of compilation units.
The python code in this directory provides helper functions to quickly load JSON files into pandas DataFrames that can be used for analysis in Jupyter notebooks.
## Directory Structure
```
script/analyze_build/
├── trace_analysis/ # Core library
│ ├── __init__.py # Main exports
│ ├── parse_file.py # Fast parsing of JSON trace files
│ ├── template_analysis.py # Template instantiation analysis
│ ├── template_parser.py # Template name parsing utilities
│ └── phase_breakdown.py # Compilation phase breakdown
├── notebooks/ # Jupyter notebooks for analysis
│ └── file_analysis_example.ipynb # Template analysis example
├── requirements.txt # Python dependencies
└── README.md # This file
```
## Python Requirements
See `requirements.txt` for the complete list of dependencies:
* **pandas** - DataFrame manipulation and analysis
* **orjson** - Fast JSON parsing for trace files
* **plotly** - Interactive visualizations (sunburst, treemap)
* **nbformat** - Jupyter notebook format support
* **ipykernel** - Kernel for running notebooks in VSCode/Jupyter
* **kaleido** - Static image export from Plotly charts
* **jupyter** - Full Jupyter environment
## Quick Start
### Setup
1. Create a virtual environment (recommended):
```bash
cd script/analyze_build
python3 -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
```
2. Install dependencies:
```bash
pip install -r requirements.txt
```
3. Install VSCode extensions if you want to run notebooks in VSCode:
* Jupyter
* Data Wrangler (interact with Pandas DataFrames)
### Analyzing a Single File
Use the `parse_file` function to load a `-ftime-trace` JSON file into a Pandas DataFrame:
```python
from trace_analysis import parse_file
# Parse the trace file
df = parse_file('path/to/trace.json')
# View basic info
print(f"Total events: {len(df)}")
print(df.columns)
# Analyze duration statistics
print(df['dur'].describe())
```
### Extracting Compilation Metadata
Get high-level metadata about the compilation:
```python
from trace_analysis import get_metadata
# Extract metadata from trace file
metadata = get_metadata('trace.json')
print(f"Source file: {metadata['source_file']}")
print(f"Compilation time: {metadata['total_wall_time_s']:.2f}s")
print(f"Started: {metadata['wall_start_datetime']}")
print(f"Ended: {metadata['wall_end_datetime']}")
```
The metadata includes:
- `source_file`: Main .cpp/.c file being compiled
- `time_granularity`: Time unit used ("microseconds")
- `beginning_of_time`: Epoch timestamp in microseconds
- `wall_start_time`: Wall clock start (microseconds since epoch)
- `wall_end_time`: Wall clock end (microseconds since epoch)
- `wall_start_datetime`: Human-readable start time
- `wall_end_datetime`: Human-readable end time
- `total_wall_time_us`: Total compilation time in microseconds
- `total_wall_time_s`: Total compilation time in seconds
### Template Instantiation Analysis
The module includes specialized functions for analyzing C++ template instantiation costs:
```python
from trace_analysis import (
parse_file,
get_template_instantiation_events,
get_phase_breakdown,
)
df = parse_file('trace.json')
# Get all template instantiation events with parsed template information
template_events = get_template_instantiation_events(df)
# The returned DataFrame includes parsed columns:
# - namespace: Top-level namespace (e.g., 'std', 'ck')
# - template_name: Template name without parameters
# - full_qualified_name: Full namespace::template_name
# - param_count: Number of template parameters
# - is_ck_type: Boolean indicating CK library types
# - is_nested: Boolean indicating nested templates
# Find slowest template instantiations
top_templates = template_events.nlargest(20, 'dur')
print(top_templates[['template_name', 'namespace', 'param_count', 'dur']])
# Analyze by namespace
namespace_summary = template_events.groupby('namespace').agg({
'dur': ['count', 'sum', 'mean']
})
print(namespace_summary)
```
### Compilation Phase Breakdown
Analyze how compilation time is distributed across different phases:
```python
from trace_analysis import get_phase_breakdown, PhaseBreakdown
df = parse_file('trace.json')
# Get hierarchical phase breakdown
breakdown = get_phase_breakdown(df)
# Display in Jupyter (automatic rich HTML display)
display(breakdown)
# Print text representation
print(breakdown)
# Access the underlying DataFrame
print(breakdown.df)
# Convert to plotly format for visualization
import plotly.express as px
data = breakdown.to_plotly()
fig = px.sunburst(**data)
fig.show()
```
The `PhaseBreakdown` class provides:
- Hierarchical breakdown of compilation phases
- Automatic calculation of "Other" residual time at each level
- Validation that children don't exceed parent durations
- Multiple output formats (text, DataFrame, Plotly)
## DataFrame Schema
The parsed DataFrame contains the following columns from the `-ftime-trace` format:
- `name`: Event name (function, template instantiation, etc.)
- `ph`: Phase character ('X' for complete, 'B' for begin, 'E' for end, 'i' for instant)
- `ts`: Timestamp in microseconds
- `dur`: Duration in microseconds (for complete events)
- `pid`: Process ID
- `tid`: Thread ID
- `arg_*`: Flattened arguments from the event's `args` field
### Template Event Columns
When using `get_template_instantiation_events()`, additional parsed columns are included:
- `namespace`: Top-level namespace extracted from the template name
- `template_name`: Template name without namespace or parameters
- `full_qualified_name`: Complete namespace::template_name
- `param_count`: Number of template parameters
- `is_ck_type`: Boolean flag for CK library types (namespace starts with 'ck')
- `is_nested`: Boolean flag indicating nested template instantiations
## Use in Jupyter Notebooks
The module is designed to work seamlessly in Jupyter notebooks. See `notebooks/file_analysis_example.ipynb` for a complete example workflow that demonstrates:
- Loading and parsing trace files
- Extracting compilation metadata
- Analyzing phase breakdown with visualizations
- Template instantiation analysis with parsed columns
- Filtering and grouping by namespace
- Identifying CK-specific template costs
To use in a notebook:
```python
import sys
from pathlib import Path
# Add trace_analysis to path
sys.path.insert(0, str(Path.cwd().parent))
from trace_analysis import (
parse_file,
get_metadata,
get_template_instantiation_events,
get_phase_breakdown,
)
# Load and analyze
df = parse_file('path/to/trace.json')
breakdown = get_phase_breakdown(df)
templates = get_template_instantiation_events(df)
# Visualize
import plotly.express as px
fig = px.sunburst(**breakdown.to_plotly())
fig.show()
```
## API Reference
### Core Functions
- `parse_file(filepath)`: Parse a `-ftime-trace` JSON file into a pandas DataFrame
- `get_metadata(filepath_or_df)`: Extract compilation metadata from trace file or DataFrame
### Template Analysis
- `get_template_instantiation_events(df)`: Filter to template instantiation events with parsed template information
### Phase Breakdown
- `get_phase_breakdown(df)`: Generate hierarchical compilation phase breakdown
- `PhaseBreakdown`: Class representing phase breakdown with multiple output formats
## Contributing
This is an experimental project for analyzing and improving C++ metaprogramming build times. Contributions are welcome! When adding new analysis functions:
1. Add the function to the appropriate module in `trace_analysis/`
2. Export it in `__init__.py`
3. Update this README with usage examples
4. Consider adding a notebook example if the feature is substantial
## License
Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
SPDX-License-Identifier: MIT

View File

@@ -0,0 +1,251 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Template Instantiation Analysis Example\n",
"\n",
"This notebook demonstrates how to use the template analysis functions to understand C++ template instantiation costs in Clang's `-ftime-trace` output.\n",
"\n",
"## Setup"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%load_ext autoreload\n",
"%autoreload 2\n",
"\n",
"import sys\n",
"from pathlib import Path\n",
"\n",
"# Add parent directory to path\n",
"sys.path.insert(0, str(Path.cwd().parent))\n",
"\n",
"from trace_analysis import (\n",
" parse_file,\n",
" get_template_instantiation_events,\n",
" get_phase_breakdown,\n",
" get_metadata,\n",
")\n",
"\n",
"import pandas as pd\n",
"from datetime import datetime\n",
"import plotly.express as px\n",
"\n",
"\n",
"# Display settings\n",
"pd.set_option(\"display.max_rows\", 100)\n",
"pd.set_option(\"display.max_columns\", None)\n",
"pd.set_option(\"display.width\", None)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Load Trace File"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Load your trace file\n",
"trace_file = Path(\n",
" \"../../../build-trace/library/src/tensor_operation_instance/gpu/conv2d_fwd/CMakeFiles/device_conv2d_fwd_instance.dir/device_conv2d_fwd_xdl_nhwc_kyxc_nhwk_f32_instance.cpp.json\"\n",
")\n",
"df = parse_file(trace_file)\n",
"\n",
"print(f\"Total events: {len(df):,}\")\n",
"starting_timestamp = datetime.fromtimestamp(df.attrs[\"beginningOfTime\"] / 1e6)\n",
"print(f\"Starting timestamp: {starting_timestamp.strftime('%Y-%m-%d:%H:%M:%S')}\")\n",
"print(f\"Source file: {df.attrs['sourceFile']}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"get_metadata(df)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Compilation Overview"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Get phase breakdown and display it\n",
"breakdown = get_phase_breakdown(df)\n",
"print(breakdown)\n",
"display(breakdown)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Extract data for plotly charts (sunburst, tree-map, or icicle)\n",
"plotly_data = breakdown.to_plotly()\n",
"fig = px.sunburst(**plotly_data)\n",
"fig.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Template Instantiation Analysis"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Get all template instantiation events (now with parsed columns!)\n",
"template_events = get_template_instantiation_events(df)\n",
"\n",
"print(f\"Total template instantiation events: {len(template_events):,}\")\n",
"print(f\"Total template time: {template_events['dur'].sum() / 1000:.1f} ms\")\n",
"display(template_events)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Examine Parsed Columns\n",
"\n",
"The `get_template_instantiation_events()` function automatically parses the `arg_detail` column into structured fields:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Show the new parsed columns\n",
"print(\"Parsed columns available:\")\n",
"print(\"- namespace: Top-level namespace (e.g., 'std', 'ck')\")\n",
"print(\"- template_name: Template name without parameters\")\n",
"print(\"- full_qualified_name: Full namespace::template_name\")\n",
"print(\"- param_count: Number of template parameters\")\n",
"print(\"- is_ck_type: Boolean indicating CK library types\")\n",
"print(\"- is_nested: Boolean indicating nested templates\")\n",
"print()\n",
"\n",
"# Display sample of parsed data\n",
"template_events[\n",
" [\"namespace\", \"template_name\", \"param_count\", \"is_ck_type\", \"is_nested\", \"dur\"]\n",
"].head(20)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Analysis by Namespace"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Group by namespace to see where time is spent\n",
"namespace_summary = (\n",
" template_events.groupby(\"namespace\")\n",
" .agg({\"dur\": [\"count\", \"sum\", \"mean\"], \"param_count\": \"mean\"})\n",
" .round(2)\n",
")\n",
"\n",
"namespace_summary.columns = [\"count\", \"total_dur\", \"avg_dur\", \"avg_params\"]\n",
"namespace_summary[\"total_ms\"] = namespace_summary[\"total_dur\"] / 1000\n",
"namespace_summary = namespace_summary.sort_values(\"total_dur\", ascending=False)\n",
"\n",
"print(\"\\nTemplate Instantiation Time by Namespace:\")\n",
"display(namespace_summary)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### CK Library Templates Analysis"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Filter to CK types only\n",
"ck_templates = template_events[template_events[\"is_ck_type\"]].copy()\n",
"\n",
"print(f\"CK template instantiations: {len(ck_templates):,}\")\n",
"print(f\"CK template time: {ck_templates['dur'].sum() / 1000:.1f} ms\")\n",
"print(\n",
" f\"Percentage of total template time: {100 * ck_templates['dur'].sum() / template_events['dur'].sum():.1f}%\"\n",
")\n",
"print()\n",
"\n",
"# Top CK templates by time\n",
"ck_by_name = (\n",
" ck_templates.groupby(\"template_name\")\n",
" .agg({\"dur\": [\"count\", \"sum\", \"mean\"]})\n",
" .round(2)\n",
")\n",
"ck_by_name.columns = [\"count\", \"total_dur\", \"avg_dur\"]\n",
"ck_by_name[\"total_ms\"] = ck_by_name[\"total_dur\"] / 1000\n",
"ck_by_name = ck_by_name.sort_values(\"total_dur\", ascending=False)\n",
"\n",
"print(\"\\nTop CK Templates by Total Time:\")\n",
"display(ck_by_name.head(20))"
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.3"
}
},
"nbformat": 4,
"nbformat_minor": 4
}

View File

@@ -0,0 +1,24 @@
# Build Trace Analysis - Python Dependencies
# Core data processing
pandas>=2.0.0
orjson>=3.9.0
# Statistical analysis
statsmodels>=0.14.0
# Jupyter notebook support
nbformat>=4.2.0
ipykernel>=6.0.0
# Interactive visualizations
plotly>=5.0.0
# Static image export from Plotly
kaleido>=0.2.0
# Full Jupyter environment (if not using VSCode)
jupyter>=1.0.0
# Progress meter in notebook
tqdm>=4.0.0

View File

@@ -0,0 +1,53 @@
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
"""
Build Trace Analysis - Core library for analyzing Clang -ftime-trace data.
This package provides tools to parse and analyze Clang's -ftime-trace JSON output
for build performance analysis.
"""
from .parse_file import (
parse_file,
get_metadata,
)
from .template_analysis import (
get_template_instantiation_events,
)
from .phase_breakdown import (
get_phase_breakdown,
PhaseBreakdown,
)
from .parse_build import (
find_trace_files,
read_trace_files,
)
from .pipeline import (
Pipeline,
)
from .build_helpers import (
get_trace_file,
)
__all__ = [
# Core parsing and filtering
"parse_file",
"get_metadata",
"find_trace_files",
"read_trace_files",
# Pipeline processing
"Pipeline",
# Template analysis
"get_template_instantiation_events",
# Phase breakdown
"get_phase_breakdown",
"PhaseBreakdown",
# Build helpers
"get_trace_file",
]

View File

@@ -0,0 +1,96 @@
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
"""
Utility functions for trace analysis.
Helper functions for file discovery, path handling, and other common operations.
"""
import subprocess
import pandas as pd
from pathlib import Path
from typing import List
def find_trace_files(trace_dir: Path) -> List[Path]:
"""
Find all JSON trace files in a directory.
Uses Unix 'find' command when available (2-5x faster than Python),
with automatic fallback to Python's rglob for cross-platform compatibility.
Args:
trace_dir: Directory to search for trace files
Returns:
List of Path objects pointing to .json files
Example:
>>> from pathlib import Path
>>> from trace_analysis import find_trace_files
>>> trace_files = find_trace_files(Path("build/CMakeFiles"))
>>> print(f"Found {len(trace_files)} trace files")
"""
try:
# Try Unix find (2-5x faster than Python)
result = subprocess.run(
["find", str(trace_dir), "-name", "*.cpp.json", "-type", "f"],
capture_output=True,
text=True,
timeout=30,
check=True,
)
json_files = [Path(p) for p in result.stdout.strip().split("\n") if p]
except (subprocess.SubprocessError, FileNotFoundError, OSError):
# Fallback to Python (cross-platform)
print("Using Python to find trace files (this may be slower)...")
json_files = list(trace_dir.rglob("*.cpp.json"))
return json_files
def read_trace_files(json_files: List[Path], workers: int = -1) -> List["pd.DataFrame"]:
"""
Parse trace files in parallel and return list of DataFrames.
This is a convenience function that uses the Pipeline API to parse
multiple trace files in parallel with progress tracking.
Args:
json_files: List of paths to trace JSON files
workers: Number of parallel workers to use:
- -1: Use all available CPUs (default)
- None: Sequential processing (single-threaded)
- N > 0: Use N worker processes
Returns:
List of parsed DataFrames, one per file
Example:
>>> from pathlib import Path
>>> from trace_analysis import find_trace_files, read_trace_files
>>>
>>> # Find and parse all trace files
>>> trace_files = find_trace_files(Path("build/CMakeFiles"))
>>> dataframes = read_trace_files(trace_files, workers=8)
>>> print(f"Parsed {len(dataframes)} files")
>>>
>>> # Use Pipeline directly for more control
>>> from trace_analysis import Pipeline
>>> from trace_analysis.parse_file import parse_file
>>>
>>> pipeline = Pipeline(trace_files).map(parse_file, workers=8)
>>> all_events, metadata = pipeline.tee(
... lambda dfs: pd.concat(dfs, ignore_index=True),
... lambda dfs: [get_metadata(df) for df in dfs]
... )
"""
from trace_analysis.pipeline import Pipeline
from trace_analysis.parse_file import parse_file
return (
Pipeline(json_files)
.map(parse_file, workers=workers, desc="Parsing trace files")
.collect()
)

View File

@@ -0,0 +1,403 @@
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
"""
Parse a single Clang -ftime-trace JSON file into a Pandas DataFrame.
This module provides fast parsing of Clang's -ftime-trace output using orjson
for performance. The JSON file is typically a single-line array of trace events.
"""
import orjson
import pandas as pd
from pathlib import Path
from typing import Union, Optional
from datetime import datetime
from dataclasses import dataclass
# Expected schema for trace event DataFrames with optimized dtypes
# This enforces strict column validation and memory-efficient types
# The memory usage is dominated by arg detail, but we optimize each series.
TRACE_EVENT_DTYPES = {
"pid": "int32", # Process ID (max observed: ~2.3M, fits in int32)
"tid": "int32", # Thread ID (max observed: ~2.3M, fits in int32)
"ts": "int64", # Timestamp in microseconds (requires int64 for epoch times)
"cat": "category", # Category (low cardinality, use categorical)
"ph": "category", # Phase type (very low cardinality: X, B, E, i, etc.)
"id": "int64", # Event ID
"name": "category", # Event name (medium cardinality, use categorical)
"dur": "int64", # Duration in microseconds (max 10 days = 864B μs, requires int64)
"arg_detail": "string", # Detail string (high cardinality, keep as string)
"arg_count": "int64", # Argument count
"arg_avg ms": "int64", # Average milliseconds
"arg_name": "category", # Argument name (medium cardinality, use categorical)
}
@dataclass
class FileMetadata:
"""
Processed metadata with computed fields for compilation analysis.
This extends the raw metadata with derived values like formatted timestamps
and converted time units for convenience.
Attributes:
source_file: Main .cpp/.c file being compiled
time_granularity: Time unit used in trace (always "microseconds" for Clang)
beginning_of_time: Epoch timestamp in microseconds from JSON root
execute_compiler_ts: Timestamp of ExecuteCompiler event (microseconds)
execute_compiler_dur: Duration of ExecuteCompiler event (microseconds)
total_wall_time_us: Total compilation time in microseconds (same as execute_compiler_dur)
total_wall_time_s: Total compilation time in seconds (computed from microseconds)
wall_start_time: Wall clock start time in microseconds since epoch (computed)
wall_end_time: Wall clock end time in microseconds since epoch (computed)
wall_start_datetime: Human-readable start time string (formatted)
wall_end_datetime: Human-readable end time string (formatted)
"""
source_file: Optional[str] = None
time_granularity: str = "microseconds"
beginning_of_time: Optional[int] = None
execute_compiler_ts: Optional[int] = None
execute_compiler_dur: Optional[int] = None
total_wall_time_us: Optional[int] = None
total_wall_time_s: Optional[float] = None
wall_start_time: Optional[int] = None
wall_end_time: Optional[int] = None
wall_start_datetime: Optional[str] = None
wall_end_datetime: Optional[str] = None
def __repr__(self):
# auto-generate pretty lines
fields = "\n".join(
f" {name} = {value!r}" for name, value in self.__dict__.items()
)
return f"{self.__class__.__name__}(\n{fields}\n)"
def parse_file(filepath: Union[str, Path]) -> pd.DataFrame:
"""
Parse a Clang -ftime-trace JSON file into a Pandas DataFrame.
The -ftime-trace format is a JSON array of trace events. Each event contains
fields like name, phase (ph), timestamp (ts), duration (dur), process/thread IDs,
and optional arguments (args).
The beginningOfTime value from the JSON structure is automatically extracted
and stored in df.attrs['beginningOfTime']. Use get_metadata(df) to get
processed metadata with event-derived fields and computed values.
Args:
filepath: Path to the -ftime-trace JSON file
Returns:
DataFrame with columns for each event field. Nested 'args' are flattened
with an 'arg_' prefix. The beginningOfTime value is stored in
df.attrs['beginningOfTime'].
Raises:
FileNotFoundError: If the file doesn't exist
ValueError: If the JSON is invalid or empty
Examples:
>>> df = parse_file('build/trace.json')
>>> df[['name', 'dur']].head()
>>>
>>> # Access processed metadata
>>> metadata = get_metadata(df)
>>> print(f"Compiled: {metadata.source_file}")
>>> print(f"Duration: {metadata.total_wall_time_s:.2f}s")
>>>
>>> # Access beginningOfTime directly if needed
>>> beginning = df.attrs.get('beginningOfTime')
>>> print(f"Beginning of time: {beginning}")
"""
filepath = Path(filepath)
if not filepath.exists():
raise FileNotFoundError(f"Trace file not found: {filepath}")
# Read and parse JSON using orjson for speed
with open(filepath, "rb") as f:
data = orjson.loads(f.read())
if not data:
raise ValueError(f"Empty trace data in file: {filepath}")
# Handle both formats: direct array or {"traceEvents": [...]}
if isinstance(data, dict):
if "traceEvents" in data:
events = data["traceEvents"]
else:
raise ValueError(
f"Expected 'traceEvents' key in JSON object, got keys: {list(data.keys())}"
)
elif isinstance(data, list):
events = data
else:
raise ValueError(f"Expected JSON array or object, got {type(data).__name__}")
# Convert to DataFrame
df = pd.DataFrame(events)
if df.empty:
raise ValueError(f"No trace events found in file: {filepath}")
# Flatten 'args' column if it exists
if "args" in df.columns:
df = _flatten_args(df)
# Validate schema: check for missing columns
expected_columns = set(TRACE_EVENT_DTYPES.keys())
actual_columns = set(df.columns)
missing_columns = expected_columns - actual_columns
if missing_columns:
raise ValueError(
f"Missing expected columns in trace data: {sorted(missing_columns)}"
)
# Validate schema: check for unexpected columns
unexpected_columns = actual_columns - expected_columns
if unexpected_columns:
raise ValueError(
f"Unexpected columns found in trace data: {sorted(unexpected_columns)}"
)
# Apply optimized dtypes with strict type enforcement
for col, dtype in TRACE_EVENT_DTYPES.items():
if dtype in ("int64", "int32"):
# Fill missing values with 0 for integer columns, then convert to specified int type
df[col] = df[col].fillna(0).astype(dtype)
elif dtype == "category":
# Convert to categorical for memory efficiency with repeated values
df[col] = df[col].astype("category")
elif dtype == "string":
# Convert to pandas string dtype for memory efficiency
df[col] = df[col].astype("string")
else:
raise ValueError(f"Unsupported dtype '{dtype}' for column '{col}'")
# Extract and store beginningOfTime in DataFrame attributes
df.attrs["beginningOfTime"] = (
data.get("beginningOfTime") if isinstance(data, dict) else None
)
# Store the source file path derived from the trace filename
# The trace filename format is: <source_file>.json
# Remove the .json extension to get the source file path
source_file_path = filepath.stem # Gets filename without .json extension
full_path = filepath.parent / source_file_path
df.attrs["sourceFile"] = _remove_cmake_artifacts(str(full_path))
df.attrs["traceFilePath"] = str(filepath)
return df
def _flatten_args(df: pd.DataFrame) -> pd.DataFrame:
"""
Flatten the 'args' column into separate columns with 'arg_' prefix.
The 'args' field in trace events contains additional metadata as a dictionary.
This function extracts those key-value pairs into separate columns.
Args:
df: DataFrame with an 'args' column containing dictionaries
Returns:
DataFrame with flattened args columns and original 'args' column removed
"""
args_list = df["args"].tolist()
args_data = [arg if isinstance(arg, dict) else {} for arg in args_list]
if args_data:
args_df = pd.DataFrame(args_data)
# Prefix all args columns with 'arg_'
args_df.columns = [f"arg_{col}" for col in args_df.columns]
# Drop original args column and concatenate flattened args
df = df.drop(columns=["args"])
df = pd.concat([df, args_df], axis=1)
return df
def _remove_cmake_artifacts(file_path: str) -> str:
"""
Remove CMake build artifacts from a file path.
CMake creates build directories with the pattern:
<build-dir>/<source-path>/CMakeFiles/<target>.dir/<source-file>
This function removes the CMakeFiles and .dir segments to reconstruct
the logical source file path while preserving the build directory prefix.
Args:
file_path: Path potentially containing CMake artifacts
Returns:
Path with CMakeFiles and .dir segments removed
Examples:
>>> _remove_cmake_artifacts('build/library/src/foo/CMakeFiles/target.dir/bar.cpp')
'build/library/src/foo/bar.cpp'
>>> _remove_cmake_artifacts('library/src/foo/bar.cpp')
'library/src/foo/bar.cpp'
"""
path = Path(file_path)
parts = path.parts
# Filter out CMakeFiles and any parts ending with .dir
filtered_parts = [
part for part in parts if part != "CMakeFiles" and not part.endswith(".dir")
]
# Reconstruct the path
if filtered_parts:
return str(Path(*filtered_parts))
return file_path
def _normalize_source_path(file_path: str) -> str:
"""
Normalize a source file path to be relative to composable_kernel if present.
If 'composable_kernel' appears in the path, returns the path starting from
'composable_kernel/'. Otherwise, returns the original path unchanged.
Args:
file_path: Full filesystem path to a source file
Returns:
Normalized path starting from composable_kernel, or original path if
composable_kernel is not found
Examples:
>>> _normalize_source_path('/home/user/composable_kernel/include/ck/tensor.hpp')
'composable_kernel/include/ck/tensor.hpp'
>>> _normalize_source_path('/usr/include/vector')
'/usr/include/vector'
"""
path = Path(file_path)
parts = path.parts
# Find the last occurrence of 'composable_kernel' in the path
for i in range(len(parts) - 1, -1, -1):
if parts[i] == "composable_kernel":
# Return path from composable_kernel onwards
return str(Path(*parts[i:]))
# If composable_kernel not found, return original path
return file_path
def get_metadata(df: pd.DataFrame) -> FileMetadata:
"""
Extract and process compilation metadata from a DataFrame.
This function processes events from the DataFrame to extract compilation
information, then computes derived fields like formatted timestamps and
converted time units.
Args:
df: DataFrame returned by parse_file() with beginningOfTime in its .attrs
Returns:
FileMetadata instance with both raw and computed fields:
- source_file: Main .cpp/.c file being compiled (from events)
- time_granularity: Time unit used in trace ("microseconds")
- beginning_of_time: Epoch timestamp in microseconds from JSON root
- execute_compiler_ts: Timestamp of ExecuteCompiler event (from events)
- execute_compiler_dur: Duration of ExecuteCompiler event (from events)
- total_wall_time_us: Total compilation time in microseconds
- total_wall_time_s: Total compilation time in seconds (computed)
- wall_start_time: Wall clock start time (computed)
- wall_end_time: Wall clock end time (computed)
- wall_start_datetime: Human-readable start time (formatted)
- wall_end_datetime: Human-readable end time (formatted)
Examples:
>>> df = parse_file('trace.json')
>>> metadata = get_metadata(df)
>>> print(f"Compiled: {metadata.source_file}")
>>> print(f"Duration: {metadata.total_wall_time_s:.2f}s")
>>> print(f"Started: {metadata.wall_start_datetime}")
"""
# Extract beginningOfTime and source_file from DataFrame attributes
beginning_of_time = None
source_file = None
if hasattr(df, "attrs"):
beginning_of_time = df.attrs.get("beginningOfTime")
source_file = df.attrs.get("source_file")
# Initialize metadata with values from DataFrame attributes
metadata = FileMetadata(
beginning_of_time=beginning_of_time, source_file=source_file
)
# Process events to extract ExecuteCompiler timing information
if "name" in df.columns:
execute_compiler = df[df["name"] == "ExecuteCompiler"]
if not execute_compiler.empty:
# Get the first ExecuteCompiler event
event = execute_compiler.iloc[0]
if "ts" in event:
metadata.execute_compiler_ts = event["ts"]
if "dur" in event:
metadata.execute_compiler_dur = event["dur"]
# Fallback: Try to find source file from ParseDeclarationOrFunctionDefinition events
# This is only used if source_file wasn't already set from the filename
if (
metadata.source_file is None
and "name" in df.columns
and "arg_detail" in df.columns
):
# Look for ParseDeclarationOrFunctionDefinition events with .cpp or .c files
source_extensions = (".cpp", ".cc", ".cxx", ".c")
parse_events = df[df["name"] == "ParseDeclarationOrFunctionDefinition"]
for _, event in parse_events.iterrows():
detail = event.get("arg_detail", "")
if detail:
# Extract file path (may include line:column info)
file_path = str(detail).split(":")[0]
# Check if it's a source file (not a header)
if any(file_path.endswith(ext) for ext in source_extensions):
metadata.source_file = _normalize_source_path(file_path)
break
# Compute derived fields
if metadata.execute_compiler_dur is not None:
metadata.total_wall_time_us = metadata.execute_compiler_dur
metadata.total_wall_time_s = metadata.execute_compiler_dur / 1_000_000.0
# Calculate wall clock times if we have the necessary data
if (
metadata.beginning_of_time is not None
and metadata.execute_compiler_ts is not None
and metadata.execute_compiler_dur is not None
):
metadata.wall_start_time = (
metadata.beginning_of_time + metadata.execute_compiler_ts
)
metadata.wall_end_time = (
metadata.wall_start_time + metadata.execute_compiler_dur
)
# Convert to human-readable datetime strings
try:
start_dt = datetime.fromtimestamp(metadata.wall_start_time / 1_000_000.0)
end_dt = datetime.fromtimestamp(metadata.wall_end_time / 1_000_000.0)
metadata.wall_start_datetime = start_dt.strftime("%Y-%m-%d %H:%M:%S.%f")[
:-3
]
metadata.wall_end_datetime = end_dt.strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
except (OSError, ValueError):
# Handle invalid timestamps gracefully
pass
return metadata

View File

@@ -0,0 +1,354 @@
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
"""
Phase breakdown analysis for Clang -ftime-trace data.
This module provides hierarchical breakdown of compilation phases using
the pre-aggregated "Total" events from Clang's -ftime-trace output.
The data is returned as a PhaseBreakdown object with rich display and
analysis capabilities optimized for Jupyter notebooks.
"""
import pandas as pd
from collections import namedtuple
from typing import Optional
# Lightweight namedtuple for iteration
Phase = namedtuple("Phase", ["name", "depth", "duration", "duration_ms", "percentage"])
class PhaseBreakdown:
"""
Wrapper for compilation phase breakdown with notebook-friendly API.
Provides hierarchical view of compilation phases from Clang -ftime-trace,
with rich display, filtering, and visualization capabilities.
Examples:
>>> breakdown = get_phase_breakdown(df)
>>>
>>> # Display in Jupyter
>>> breakdown
>>>
>>> # Access specific phases
>>> breakdown['InstantiateFunction']
>>> breakdown.frontend
>>> breakdown.backend
>>>
>>> # Get metrics
>>> print(f"Total: {breakdown.total_ms:.1f}ms")
>>>
>>> # Top N analysis
>>> breakdown.top(10)
>>> breakdown.frontend.top(5)
>>>
>>> # Visualization
>>> import plotly.express as px
>>> data = breakdown.to_plotly()
>>> fig = px.sunburst(**data)
>>> fig.show()
>>>
>>> # Iteration
>>> for phase in breakdown:
>>> print(f"{phase.name}: {phase.duration_ms:.1f}ms")
"""
def __init__(self, df: pd.DataFrame):
"""
Initialize from phase breakdown DataFrame.
Args:
df: DataFrame with columns name, parent, depth, duration
"""
if df.empty:
self._df = pd.DataFrame(columns=["name", "parent", "depth", "duration"])
self._total_time = 0
else:
self._df = df
self._total_time = self._get_total_time()
def __repr__(self) -> str:
"""Simple text representation for console."""
if self._df.empty:
return "PhaseBreakdown(empty)"
n_phases = len(self._df)
return f"PhaseBreakdown({n_phases} phases, {self._total_time:.1f}ms total)"
def _repr_html_(self) -> str:
"""Rich HTML representation for Jupyter notebooks."""
if self._df.empty:
return "<div><i>PhaseBreakdown(empty)</i></div>"
return self.to_dataframe()._repr_html_()
@property
def df(self) -> pd.DataFrame:
"""
Access underlying DataFrame.
Returns:
DataFrame with columns name, parent, depth, duration
"""
return self._df
def to_dataframe(self, show_percentages: bool = True) -> pd.DataFrame:
"""
Format as DataFrame for display.
Creates a nicely formatted DataFrame suitable for Jupyter notebook display.
Args:
show_percentages: Include percentage of total time
Returns:
DataFrame with formatted columns
"""
return self._format_dataframe(show_percentages)
def to_plotly(self) -> dict:
"""
Convert to plotly hierarchical visualization format.
Returns a dictionary with data_frame, values, and path that can be directly
used with plotly.express sunburst, treemap, or icicle charts.
Returns:
Dictionary with keys: data_frame, values, path, branchvalues
Example:
>>> data = breakdown.to_plotly()
>>> import plotly.express as px
>>>
>>> # Create sunburst chart
>>> fig = px.sunburst(**data)
>>> fig.show()
>>>
>>> # Create treemap chart
>>> fig = px.treemap(**data)
>>> fig.show()
>>>
>>> # Create icicle chart
>>> fig = px.icicle(**data)
>>> fig.show()
"""
return self._build_plotly_data()
# Internal helper methods
def _get_total_time(self) -> int:
"""Get total time from root ExecuteCompiler event."""
root = self._df[self._df["depth"] == 0]
if root.empty:
return 0
return int(root.iloc[0]["duration"])
def _format_dataframe(self, show_percentages: bool) -> pd.DataFrame:
"""Format phase breakdown as DataFrame."""
if self._df.empty:
return pd.DataFrame()
display_rows = []
for _, row in self._df.iterrows():
duration_ms = row["duration"] / 1000.0
display_row = {
"Name": row["name"],
"Parent": row["parent"] if row["parent"] else "(root)",
"Depth": row["depth"],
"Duration (ms)": duration_ms,
}
if show_percentages and self._total_time > 0:
pct = row["duration"] / self._total_time * 100
display_row["% of Total"] = pct
display_rows.append(display_row)
display_df = pd.DataFrame(display_rows)
if show_percentages:
display_df["% of Total"] = display_df["% of Total"].round(1)
return display_df
def _build_plotly_data(self) -> dict:
"""Convert to plotly hierarchical visualization format."""
return {
"data_frame": self._df,
"names": "name",
"parents": "parent",
"values": "duration",
"branchvalues": "total",
}
# Hierarchical phase specification
# There are over 100 totals in the JSON file, but a lot of them overlap.
# If the children total more than their parent, we will throw a ValueError.
#
# The hierarchy is specified as a nested dictionary where:
# - Keys are phase names (matching "Total <name>" events in the trace)
# - Values are dictionaries of child phases (or empty dict {} for leaf nodes)
# - Empty string "" as a key means "calculate Other as residual"
#
# This structure supports arbitrary nesting depth.
PHASE_HIERARCHY = {
"ExecuteCompiler": {
"Frontend": {
"InstantiateFunction": {},
},
"Backend": {
"Optimizer": {},
"CodeGenPasses": {},
},
}
}
def get_phase_breakdown(df: pd.DataFrame) -> PhaseBreakdown:
"""
Get hierarchical breakdown of compilation phases.
Returns a PhaseBreakdown object with rich display and analysis methods,
using the pre-aggregated "Total" events from Clang's -ftime-trace output
for accurate statistics.
All durations are in microseconds.
The hierarchy is defined by the PHASE_HIERARCHY constant and supports
arbitrary nesting depth. The tree is traversed recursively to build
the phase breakdown.
Args:
df: DataFrame from parse_file()
Returns:
PhaseBreakdown object with rich display and analysis methods
Raises:
ValueError: If required Total events are missing or if calculated
"Other" values are negative (indicating data inconsistency)
Examples:
>>> df = parse_file('trace.json')
>>> breakdown = get_phase_breakdown(df)
>>>
>>> # Display in Jupyter (automatic)
>>> breakdown
>>>
>>> # Get total compilation time
>>> print(f"Total: {breakdown.total_ms:.1f}ms")
>>>
>>> # Access specific phases
>>> breakdown['InstantiateFunction']
>>> breakdown.frontend
>>> breakdown.backend.top(5)
>>>
>>> # Visualize
>>> import plotly.express as px
>>> data = breakdown.to_plotly()
>>> fig = px.sunburst(**data)
>>> fig.show()
"""
if "name" not in df.columns or "dur" not in df.columns:
raise ValueError("DataFrame missing required 'name' or 'dur' columns")
# Pre-filter to Total events for efficient lookup
total_events = df[df["name"].str.startswith("Total ", na=False)].copy()
total_events["phase"] = total_events["name"].str.removeprefix("Total ")
def get_duration(phase_name: str) -> Optional[int]:
"""Get duration in microseconds from a Total event."""
matches = total_events[total_events["phase"] == phase_name]
if matches.empty:
return None
return int(matches.iloc[0]["dur"])
def process_node(
node_name: str,
parent_name: str,
depth: int,
children_spec: dict,
) -> list[dict]:
"""
Recursively process a node and its children in the phase hierarchy.
Args:
node_name: Name of the current phase node
parent_name: Name of the parent phase (empty string for root)
depth: Current depth in the tree (0 for root)
children_spec: Dictionary of child phases to process
Returns:
List of row dictionaries for this node and all descendants
Raises:
ValueError: If phase not found or children exceed parent duration
"""
# Get duration for this node
node_duration = get_duration(node_name)
if node_duration is None:
raise ValueError(f"No Total {node_name} event found in trace")
# Add current node
rows = [
{
"name": node_name,
"parent": parent_name,
"depth": depth,
"duration": node_duration,
}
]
if not children_spec:
return rows
# Process all children recursively
children_total = 0
for child_name, grandchildren_spec in children_spec.items():
if child_name == "":
# Empty string means "Other" - skip for now, calculate as residual
continue
# Recursively process this child and its descendants
child_rows = process_node(
child_name, node_name, depth + 1, grandchildren_spec
)
rows.extend(child_rows)
# Track total duration of direct children only (not grandchildren)
children_total += child_rows[0]["duration"]
# Calculate and add "Other" if there's unaccounted time
other_duration = node_duration - children_total
if other_duration < 0:
raise ValueError(
f"{node_name} children total ({children_total}) "
f"exceeds parent total ({node_duration})"
)
if other_duration > 0:
rows.append(
{
"name": f"{node_name}_Other",
"parent": node_name,
"depth": depth + 1,
"duration": other_duration,
}
)
return rows
# Start recursive traversal from root
root_name = "ExecuteCompiler"
if root_name not in PHASE_HIERARCHY:
raise ValueError(f"Root phase '{root_name}' not found in PHASE_HIERARCHY")
all_rows = process_node(
root_name,
"", # Root has no parent
0, # Root is at depth 0
PHASE_HIERARCHY[root_name],
)
breakdown_df = pd.DataFrame(all_rows)
return PhaseBreakdown(breakdown_df)

View File

@@ -0,0 +1,292 @@
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
"""
Functional pipeline for parallel processing of trace files.
This module provides a fluent API for building data processing pipelines with
support for parallel execution, progress tracking, and multiple output branches.
Example:
>>> from trace_analysis import Pipeline, find_trace_files
>>> from trace_analysis.parse_file import parse_file
>>>
>>> files = find_trace_files(Path("build"))
>>> dfs = Pipeline(files).map(parse_file, workers=8).collect()
"""
from typing import Any, Callable, List, Optional, Tuple, Union
from multiprocessing import Pool, cpu_count
from tqdm.auto import tqdm
class Pipeline:
"""
Functional pipeline for processing data with parallel execution support.
Provides a fluent API for chaining operations like map, filter, and reduce.
Supports parallel processing with multiprocessing and progress tracking with tqdm.
Features:
- Fluent API with method chaining
- Parallel processing with configurable worker count
- Progress bars in Jupyter notebooks (tqdm)
- Fail-fast error handling
- In-memory processing for speed
- Tee operation for branching into multiple outputs
Attributes:
_items: Current list of items in the pipeline
_is_reduced: Flag indicating if pipeline has been reduced to single value
Example:
Basic parallel processing:
>>> files = find_trace_files(Path("build"))
>>> dfs = Pipeline(files).map(parse_file, workers=8).collect()
Multi-stage pipeline:
>>> results = (
... Pipeline(files)
... .map(parse_file, workers=8)
... .filter(lambda df: len(df) > 1000)
... .collect()
... )
Multiple outputs with tee:
>>> pipeline = Pipeline(files).map(parse_file, workers=8)
>>> all_events, metadata, stats = pipeline.tee(
... lambda dfs: pd.concat(dfs, ignore_index=True),
... lambda dfs: [get_metadata(df) for df in dfs],
... lambda dfs: {"count": len(dfs)}
... )
"""
def __init__(self, items: List[Any]):
"""
Initialize a new pipeline with a list of items.
Args:
items: Initial list of items to process
"""
self._items = items
self._is_reduced = False
def map(
self,
func: Callable[[Any], Any],
workers: Optional[int] = None,
desc: Optional[str] = None,
) -> "Pipeline":
"""
Apply a function to each item in the pipeline.
Args:
func: Function to apply to each item. Should accept a single argument
and return a transformed value.
workers: Number of parallel workers to use:
- None: Sequential processing (single-threaded)
- -1: Use all available CPUs
- N > 0: Use N worker processes
desc: Description for the progress bar. If None, uses a default description.
Returns:
Self for method chaining
Raises:
ValueError: If pipeline has already been reduced
Exception: Any exception raised by func is re-raised with context
Example:
>>> # Sequential processing
>>> Pipeline(files).map(parse_file).collect()
>>>
>>> # Parallel processing with all CPUs
>>> Pipeline(files).map(parse_file, workers=-1).collect()
>>>
>>> # Parallel with custom worker count and description
>>> Pipeline(files).map(parse_file, workers=8, desc="Parsing").collect()
"""
if self._is_reduced:
raise ValueError("Cannot map after reduce operation")
if not self._items:
return self
# Determine worker count
if workers == -1:
workers = cpu_count()
# Set default description
if desc is None:
desc = "Processing items"
# Sequential processing
if workers is None or workers == 1:
results = []
for item in tqdm(self._items, desc=desc):
try:
results.append(func(item))
except Exception as e:
raise type(e)(f"Error processing item {item}: {e}") from e
self._items = results
return self
# Parallel processing
try:
with Pool(processes=workers) as pool:
# Use imap_unordered for better performance (results as they complete)
# Wrap with tqdm for progress tracking
results = list(
tqdm(
pool.imap_unordered(func, self._items),
total=len(self._items),
desc=desc,
)
)
self._items = results
return self
except Exception as e:
# Re-raise with context
raise type(e)(f"Error in parallel map operation: {e}") from e
def filter(self, predicate: Callable[[Any], bool]) -> "Pipeline":
"""
Filter items based on a predicate function.
Args:
predicate: Function that returns True for items to keep, False to discard.
Should accept a single argument and return a boolean.
Returns:
Self for method chaining
Raises:
ValueError: If pipeline has already been reduced
Example:
>>> # Keep only large DataFrames
>>> Pipeline(dfs).filter(lambda df: len(df) > 1000).collect()
>>>
>>> # Keep only successful builds
>>> Pipeline(dfs).filter(
... lambda df: 'ExecuteCompiler' in df['name'].values
... ).collect()
"""
if self._is_reduced:
raise ValueError("Cannot filter after reduce operation")
self._items = [item for item in self._items if predicate(item)]
return self
def reduce(self, func: Callable[[List[Any]], Any]) -> "Pipeline":
"""
Reduce all items to a single value using an aggregation function.
After reduction, the pipeline contains a single value and no further
map or filter operations are allowed.
Args:
func: Aggregation function that accepts a list of all items and
returns a single aggregated value.
Returns:
Self for method chaining
Raises:
ValueError: If pipeline has already been reduced
Example:
>>> # Concatenate all DataFrames
>>> Pipeline(dfs).reduce(
... lambda dfs: pd.concat(dfs, ignore_index=True)
... ).collect()
>>>
>>> # Sum all values
>>> Pipeline(numbers).reduce(sum).collect()
>>>
>>> # Custom aggregation
>>> Pipeline(dfs).reduce(
... lambda dfs: {
... "total_files": len(dfs),
... "total_events": sum(len(df) for df in dfs)
... }
... ).collect()
"""
if self._is_reduced:
raise ValueError("Cannot reduce twice")
try:
self._items = [func(self._items)]
self._is_reduced = True
return self
except Exception as e:
raise type(e)(f"Error in reduce operation: {e}") from e
def tee(self, *funcs: Callable[[List[Any]], Any]) -> Tuple[Any, ...]:
"""
Branch the pipeline into multiple outputs.
Each function receives the full list of current items and produces
an independent output. This is useful for generating multiple
aggregations or analyses from the same data.
This operation automatically collects the pipeline results.
Args:
*funcs: Variable number of functions, each accepting the full list
of items and returning a result. Each function is applied
independently to the same input data.
Returns:
Tuple of results, one per function, in the same order as the functions
Raises:
ValueError: If no functions are provided
Exception: Any exception raised by a function is re-raised with context
Example:
>>> pipeline = Pipeline(files).map(parse_file, workers=8)
>>>
>>> # Create three different outputs from the same data
>>> all_events, metadata_df, stats = pipeline.tee(
... # Output 1: Concatenated DataFrame
... lambda dfs: pd.concat(dfs, ignore_index=True),
... # Output 2: Metadata summary
... lambda dfs: pd.DataFrame([get_metadata(df).__dict__ for df in dfs]),
... # Output 3: Statistics dictionary
... lambda dfs: {
... "total_files": len(dfs),
... "total_events": sum(len(df) for df in dfs)
... }
... )
"""
if not funcs:
raise ValueError("At least one function must be provided to tee")
results = []
for i, func in enumerate(funcs):
try:
results.append(func(self._items))
except Exception as e:
raise type(e)(f"Error in tee function {i}: {e}") from e
return tuple(results)
def collect(self) -> Union[List[Any], Any]:
"""
Execute the pipeline and return the results.
Returns:
If the pipeline has been reduced, returns the single reduced value.
Otherwise, returns the list of items.
Example:
>>> # Returns list of DataFrames
>>> dfs = Pipeline(files).map(parse_file, workers=8).collect()
>>>
>>> # Returns single concatenated DataFrame
>>> df = Pipeline(files).map(parse_file, workers=8).reduce(pd.concat).collect()
"""
if self._is_reduced:
return self._items[0]
return self._items

View File

@@ -0,0 +1,80 @@
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
"""
Template instantiation analysis for Clang -ftime-trace data.
This module provides specialized functions for analyzing C++ template
instantiation costs from Clang's -ftime-trace output.
"""
import pandas as pd
from .template_parser import parse_template_detail
def get_template_instantiation_events(df: pd.DataFrame) -> pd.DataFrame:
"""
Filter to template instantiation events and parse arg_detail into structured columns.
Returns events for:
- InstantiateFunction: Function template instantiations
- InstantiateClass: Class template instantiations
The returned DataFrame includes parsed columns from arg_detail:
- namespace: Top-level namespace (e.g., 'std', 'ck')
- template_name: Template name without parameters
- full_qualified_name: Full namespace::template_name
- param_count: Number of template parameters
- is_ck_type: Boolean indicating if this is a CK library type
- is_nested: Boolean indicating if contains nested templates
Args:
df: DataFrame from parse_file()
Returns:
Filtered DataFrame containing template instantiation events with parsed columns
Example:
>>> df = parse_file('trace.json')
>>> templates = get_template_instantiation_events(df)
>>> templates.sort_values('dur', ascending=False).head(10)
>>> # Filter to CK types only
>>> ck_templates = templates[templates['is_ck_type']]
>>> # Group by template name
>>> templates.groupby('template_name')['dur'].sum()
"""
# Filter to template instantiation events
filtered_df = (
df[
df["name"].isin(
[
"InstantiateClass",
"InstantiateFunction",
]
)
]
.drop(
columns=[
"arg_avg ms",
"arg_count",
"arg_name",
"cat",
"id",
"ph",
"pid",
"tid",
]
)
.reset_index(drop=True)
)
# Parse arg_detail into structured columns
parsed_data = filtered_df["arg_detail"].apply(parse_template_detail)
# Convert list of dicts to DataFrame and join with original
parsed_df = pd.DataFrame(parsed_data.tolist())
# Combine with original data
result_df = pd.concat([filtered_df, parsed_df], axis=1)
return result_df

View File

@@ -0,0 +1,301 @@
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
"""
Template detail string parser for C++ template instantiations.
This module provides functions to parse the arg_detail strings from
Clang's -ftime-trace output into structured components.
"""
import re
from typing import Dict
def parse_template_detail(detail_str: str) -> Dict[str, any]:
"""
Parse a template detail string into structured components.
Args:
detail_str: The arg_detail string from -ftime-trace
Returns:
Dictionary with parsed fields:
- namespace: Top-level namespace (e.g., 'std', 'ck')
- template_name: Template name without parameters
- full_qualified_name: Full namespace::template_name
- param_count: Number of template parameters
- is_ck_type: Boolean indicating if this is a CK library type
- is_nested: Boolean indicating if contains nested templates
Example:
>>> parse_template_detail('std::basic_string<char>')
{
'namespace': 'std',
'template_name': 'basic_string',
'full_qualified_name': 'std::basic_string',
'param_count': 1,
'is_ck_type': False,
'is_nested': False
}
"""
# Handle empty or invalid strings
if not detail_str or not isinstance(detail_str, str):
return _empty_result()
# Remove surrounding quotes if present
detail_str = detail_str.strip('"')
# Extract components
namespace = extract_namespace(detail_str)
template_name = extract_template_name(detail_str)
full_qualified_name = extract_full_qualified_name(detail_str)
param_count = count_template_params(detail_str)
is_ck = is_ck_template(detail_str)
is_nested = is_nested_template(detail_str)
return {
"namespace": namespace,
"template_name": template_name,
"full_qualified_name": full_qualified_name,
"param_count": param_count,
"is_ck_type": is_ck,
"is_nested": is_nested,
}
def extract_namespace(detail_str: str) -> str:
"""
Extract the top-level namespace from a template detail string.
Args:
detail_str: The template detail string
Returns:
The top-level namespace, or empty string if none found
Example:
>>> extract_namespace('std::basic_string<char>')
'std'
>>> extract_namespace('ck::tensor_operation::device::DeviceConv2d<...>')
'ck'
"""
if not detail_str:
return ""
# Remove quotes
detail_str = detail_str.strip('"')
# Find first :: separator
match = re.match(r"^([a-zA-Z_][a-zA-Z0-9_]*)::", detail_str)
if match:
return match.group(1)
# No namespace found - check if it's a simple type
match = re.match(r"^([a-zA-Z_][a-zA-Z0-9_]*)", detail_str)
if match:
return match.group(1)
return ""
def extract_template_name(detail_str: str) -> str:
"""
Extract the template name without namespace or parameters.
Args:
detail_str: The template detail string
Returns:
The template name without namespace or parameters
Example:
>>> extract_template_name('std::basic_string<char>')
'basic_string'
>>> extract_template_name('ck::GridwiseGemm_k0mk1_k0nk1_mn_xdlops_v2r3<...>')
'GridwiseGemm_k0mk1_k0nk1_mn_xdlops_v2r3'
"""
if not detail_str:
return ""
# Remove quotes
detail_str = detail_str.strip('"')
# Find the last component before < or end of string
# This handles nested namespaces like ck::tensor_operation::device::DeviceConv2d
match = re.search(r"::([a-zA-Z_][a-zA-Z0-9_]*)\s*(?:<|$)", detail_str)
if match:
return match.group(1)
# No :: found, try to get name before <
match = re.match(r"^([a-zA-Z_][a-zA-Z0-9_]*)\s*(?:<|$)", detail_str)
if match:
return match.group(1)
return ""
def extract_full_qualified_name(detail_str: str) -> str:
"""
Extract the full qualified name (namespace::...::template_name).
Args:
detail_str: The template detail string
Returns:
The full qualified name without template parameters
Example:
>>> extract_full_qualified_name('std::basic_string<char>')
'std::basic_string'
>>> extract_full_qualified_name('ck::tensor_operation::device::DeviceConv2d<...>')
'ck::tensor_operation::device::DeviceConv2d'
"""
if not detail_str:
return ""
# Remove quotes
detail_str = detail_str.strip('"')
# Match everything up to the first < or end of string
match = re.match(r"^([a-zA-Z_:][a-zA-Z0-9_:]*)\s*(?:<|$)", detail_str)
if match:
return match.group(1)
return ""
def count_template_params(detail_str: str) -> int:
"""
Count the number of top-level template parameters.
This counts commas at the top level of template brackets,
not commas inside nested templates.
Args:
detail_str: The template detail string
Returns:
Number of template parameters, or 0 if not a template
Example:
>>> count_template_params('std::basic_string<char>')
1
>>> count_template_params('std::tuple<int, float, double>')
3
"""
if not detail_str or "<" not in detail_str:
return 0
# Remove quotes
detail_str = detail_str.strip('"')
# Find the template parameter section
start = detail_str.find("<")
if start == -1:
return 0
# Track bracket depth to only count top-level commas
depth = 0
param_count = 1 # Start with 1 (if there's a <, there's at least one param)
in_template = False
for i in range(start, len(detail_str)):
char = detail_str[i]
if char == "<":
depth += 1
in_template = True
elif char == ">":
depth -= 1
if depth == 0:
# We've closed the outermost template
break
elif char == "," and depth == 1:
# Top-level comma
param_count += 1
return param_count if in_template else 0
def is_ck_template(detail_str: str) -> bool:
"""
Check if this is a CK library template.
Args:
detail_str: The template detail string
Returns:
True if this is a CK library type, False otherwise
Example:
>>> is_ck_template('ck::tensor_operation::device::DeviceConv2d<...>')
True
>>> is_ck_template('std::basic_string<char>')
False
"""
if not detail_str:
return False
# Remove quotes
detail_str = detail_str.strip('"')
# Check if it starts with ck:: or contains ::ck::
return detail_str.startswith("ck::") or "::ck::" in detail_str
def is_nested_template(detail_str: str) -> bool:
"""
Check if this template contains nested template instantiations.
Args:
detail_str: The template detail string
Returns:
True if contains nested templates, False otherwise
Example:
>>> is_nested_template('std::vector<int>')
False
>>> is_nested_template('std::vector<std::string>')
True
"""
if not detail_str or "<" not in detail_str:
return False
# Remove quotes
detail_str = detail_str.strip('"')
# Find the template parameter section
start = detail_str.find("<")
if start == -1:
return False
# Look for nested < after the first one
depth = 0
for i in range(start, len(detail_str)):
char = detail_str[i]
if char == "<":
depth += 1
if depth > 1:
# Found a nested template
return True
elif char == ">":
depth -= 1
if depth == 0:
break
return False
def _empty_result() -> Dict[str, any]:
"""Return an empty result dictionary with default values."""
return {
"namespace": "",
"template_name": "",
"full_qualified_name": "",
"param_count": 0,
"is_ck_type": False,
"is_nested": False,
}

72
script/check_copyright_year.sh Executable file
View File

@@ -0,0 +1,72 @@
#!/usr/bin/env bash
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
# This script checks if files have the correct copyright header template.
# It supports .hpp, .cpp, .inc, .py, .sh, and .cmake files.
#
# Usage: ./check_copyright_year.sh <file1> <file2> ...
exit_code=0
# Expected copyright header lines (without comment characters)
COPYRIGHT_LINE="Copyright (c) Advanced Micro Devices, Inc., or its affiliates."
SPDX_LINE="SPDX-License-Identifier: MIT"
check_file() {
local file=$1
local basename="${file##*/}"
local ext="${file##*.}"
local comment_char
# Determine comment character based on filename or extension
if [[ "$basename" == "CMakeLists.txt" ]]; then
comment_char="#"
else
case "$ext" in
cpp|hpp|inc)
comment_char="//"
;;
py|sh|cmake)
comment_char="#"
;;
*)
# Skip files with unsupported extensions
return 0
;;
esac
fi
# Build expected header patterns
expected_copyright="$comment_char $COPYRIGHT_LINE"
expected_spdx="$comment_char $SPDX_LINE"
# Check if file contains both required lines
if ! grep -qF "$expected_copyright" "$file"; then
echo "ERROR: File $file is missing the correct copyright header line."
echo " Expected: $expected_copyright"
return 1
fi
if ! grep -qF "$expected_spdx" "$file"; then
echo "ERROR: File $file is missing the correct SPDX license identifier line."
echo " Expected: $expected_spdx"
return 1
fi
return 0
}
# Process each file provided as argument
for file in "$@"; do
# Skip if file doesn't exist or is a directory
if [[ ! -f "$file" ]]; then
continue
fi
if ! check_file "$file"; then
exit_code=1
fi
done
exit $exit_code

View File

@@ -0,0 +1,5 @@
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
find . -name deps -prune -o -name build -prune -o -iname '*.h' -o -iname '*.hpp' -o -iname '*.cpp' -o -iname '*.h.in' -o -iname '*.hpp.in' -o -iname '*.cpp.in' -o -iname '*.cl' -o -iname '*.cuh' -o -iname '*.cu' -o -iname '*.inc' | grep -v 'build/' | grep -v 'include/rapidjson'| xargs -n 1 -P 16 -I{} -t sh -c 'clang-format-18 -i -style=file {}'
git status --porcelain | awk '$1 != "D" && (match($2, "\\.cpp|.hpp|.inc|include/rapidjson/")) {print $2}' | xargs -n 1 -P 16 -I{} -t sh -c 'clang-format-18 -i -style=file {}'

41
script/cmake-ck-dev.sh Executable file
View File

@@ -0,0 +1,41 @@
#!/bin/bash
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
# exit when a command exits with non-zero status; also when an unbound variable is referenced
set -eu
# pipefail is supported by many shells, not supported by sh and dash
set -o pipefail 2>/dev/null | true
# when treating a string as a sequence, do not split on spaces
IFS=$(printf '\n\t')
# clean the build system files
find . -name CMakeFiles -type d -exec rm -rfv {} +
find . -name CMakeCache.txt -type f -exec rm -rv {} +
if [ $# -ge 1 ]; then
MY_PROJECT_SOURCE="$1"
shift 1
else
MY_PROJECT_SOURCE=".."
fi
GPU_TARGETS="gfx908;gfx90a;gfx942"
if [ $# -ge 1 ]; then
case "$1" in
gfx*)
GPU_TARGETS="$1"
shift 1
echo "GPU targets provided: $GPU_TARGETS"
REST_ARGS=("$@")
;;
*)
REST_ARGS=("$@")
;;
esac
else
REST_ARGS=("$@")
fi
cmake "${MY_PROJECT_SOURCE}" --preset dev -DGPU_TARGETS="$GPU_TARGETS" "${REST_ARGS[@]}"

View File

@@ -0,0 +1,506 @@
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
# Convert miopen driver command to ck Profiler
# Example: python3 ../script/convert_miopen_driver_to_profiler.py
# /opt/rocm/bin/MIOpenDriver conv -n 32 -c 64 -H 28 -W 28 -k 64 -y 3 -x 3
# -p 1 -q 1 -u 2 -v 2 -l 1 -j 1 -m conv -g 32 -F 1 -t 1
import argparse
import subprocess
def init_const_args(args):
args.ck_profiler_cmd = "../build/bin/ckProfiler"
# use decimal values
args.init_method = 2
# don't print tensor values
args.log_value = 0
def run_ck_profiler_cmd(cmd):
print("ckProfiler command:")
cmd_concatenated_str = ""
for arg in cmd:
cmd_concatenated_str += arg + " "
print(cmd_concatenated_str)
subprocess.run(cmd)
def parse_layouts(args):
if args.in_layout == "NCW" or args.in_layout == "NCHW" or args.in_layout == "NCDHW":
if args.ck_profier_op == "grouped_conv_bwd_weight":
args.layout = 4
elif (
args.ck_profier_op == "grouped_conv_fwd"
or args.ck_profier_op == "grouped_conv_bwd_data"
):
args.layout = 3
else:
print("Not supported layout for this op")
exit(1)
elif (
args.in_layout == "NWC" or args.in_layout == "NHWC" or args.in_layout == "NDHWC"
):
if args.ck_profier_op == "grouped_conv_bwd_weight":
args.layout = 2
elif (
args.ck_profier_op == "grouped_conv_bwd_data"
or args.ck_profier_op == "grouped_conv_fwd"
):
args.layout = 1
else:
print("Not supported layout for this op")
exit(1)
def parse_data_type(args):
if args.data_type == "fp32":
if (
args.ck_profier_op == "grouped_conv_bwd_weight"
or args.ck_profier_op == "grouped_conv_bwd_data"
or args.ck_profier_op == "grouped_conv_fwd"
):
args.data_type = 0
if args.data_type == "fp16":
if (
args.ck_profier_op == "grouped_conv_bwd_weight"
or args.ck_profier_op == "grouped_conv_bwd_data"
or args.ck_profier_op == "grouped_conv_fwd"
):
args.data_type = 1
if args.data_type == "int8":
if args.ck_profier_op == "grouped_conv_bwd_weight":
args.data_type = 4
if args.ck_profier_op == "grouped_conv_bwd_data":
print("Not supported data type for grouped_conv_bwd_data")
exit(1)
if args.ck_profier_op == "grouped_conv_fwd":
args.data_type = 3
if args.data_type == "bfp16":
if args.ck_profier_op == "grouped_conv_bwd_weight":
args.data_type = 5
if (
args.ck_profier_op == "grouped_conv_bwd_data"
or args.ck_profier_op == "grouped_conv_fwd"
):
args.data_type = 2
def add_conv_params_to_cmd(args, cmd):
if args.spatial_dim == 1:
cmd += [str(args.fil_w), str(args.in_w)]
cmd += [str(args.conv_stride_w), str(args.dilation_w)]
cmd += [str(args.pad_w), str(args.pad_w)]
elif args.spatial_dim == 2:
cmd += [str(args.fil_h), str(args.fil_w)]
cmd += [str(args.in_h), str(args.in_w)]
cmd += [str(args.conv_stride_h), str(args.conv_stride_w)]
cmd += [str(args.dilation_h), str(args.dilation_w)]
cmd += [str(args.pad_h), str(args.pad_w)]
cmd += [str(args.pad_h), str(args.pad_w)]
elif args.spatial_dim == 3:
cmd += [str(args.fil_d), str(args.fil_h), str(args.fil_w)]
cmd += [str(args.in_d), str(args.in_h), str(args.in_w)]
cmd += [str(args.conv_stride_d), str(args.conv_stride_h)]
cmd += [str(args.conv_stride_w)]
cmd += [str(args.dilation_d), str(args.dilation_h), str(args.dilation_w)]
cmd += [str(args.pad_d), str(args.pad_h), str(args.pad_w)]
cmd += [str(args.pad_d), str(args.pad_h), str(args.pad_w)]
else:
print("Not supported spatial dim (supported: 1, 2, 3)")
exit(1)
def run_ck_grouped_conv_fwd(args):
args.ck_profier_op = "grouped_conv_fwd"
parse_data_type(args)
parse_layouts(args)
# use int32 by default
args.index_type = 0
cmd = [str(args.ck_profiler_cmd), str(args.ck_profier_op)]
cmd += [str(args.data_type), str(args.layout), str(args.index_type)]
cmd += [str(args.verify), str(args.init_method)]
cmd += [str(args.log_value), str(args.time)]
cmd += [str(args.spatial_dim), str(args.group_count)]
cmd += [str(args.batchsize), str(args.out_channels)]
cmd += [str(args.in_channels)]
add_conv_params_to_cmd(args, cmd)
# Add optional named arguments
if args.instance != -1:
cmd += ["--instance", str(args.instance)]
if args.list_instances:
cmd += ["--list-instances"]
run_ck_profiler_cmd(cmd)
def run_ck_grouped_conv_bwd_data(args):
args.ck_profier_op = "grouped_conv_bwd_data"
parse_data_type(args)
parse_layouts(args)
# Test all split K value from the list {1, 2, 4, 8, 32, 64, 128}
args.split_k_value = -1
cmd = [str(args.ck_profiler_cmd), str(args.ck_profier_op)]
cmd += [str(args.data_type), str(args.layout)]
cmd += [str(args.verify), str(args.init_method)]
cmd += [str(args.log_value), str(args.time)]
cmd += [str(args.spatial_dim), str(args.group_count)]
cmd += [str(args.batchsize), str(args.out_channels)]
cmd += [str(args.in_channels)]
add_conv_params_to_cmd(args, cmd)
cmd += [str(args.split_k_value)]
# Add optional named arguments
if args.instance != -1:
cmd += ["--instance", str(args.instance)]
if args.list_instances:
cmd += ["--list-instances"]
run_ck_profiler_cmd(cmd)
def run_ck_grouped_conv_bwd_weight(args):
args.ck_profier_op = "grouped_conv_bwd_weight"
parse_data_type(args)
parse_layouts(args)
# Test all split K value from the list {1, 2, 4, 8, 32, 64, 128}
args.split_k_value = "all"
cmd = [str(args.ck_profiler_cmd), str(args.ck_profier_op)]
cmd += [str(args.data_type), str(args.layout)]
cmd += [str(args.verify), str(args.init_method)]
cmd += [str(args.log_value), str(args.time)]
cmd += [str(args.spatial_dim), str(args.group_count)]
cmd += [str(args.batchsize), str(args.out_channels)]
cmd += [str(args.in_channels)]
add_conv_params_to_cmd(args, cmd)
cmd += [str(args.split_k_value)]
# Add optional named arguments
if args.instance != -1:
cmd += ["--instance", str(args.instance)]
if args.list_instances:
cmd += ["--list-instances"]
run_ck_profiler_cmd(cmd)
# Get name of miopen driver, remove it from unknown
def process_miopen_driver_name(args, unknown):
if "convint8" in unknown:
args.data_type = "int8"
unknown.remove("convint8")
elif "convbfp16" in unknown:
args.data_type = "bfp16"
unknown.remove("convbfp16")
elif "convfp16" in unknown:
args.data_type = "fp16"
unknown.remove("convfp16")
elif "conv" in unknown:
args.data_type = "fp32"
unknown.remove("conv")
else:
print("Not supported driver (supported: conv, convfp16, convint8, convbfp16).")
exit(1)
def run_ck_profiler(args):
# MIOpen get number of channel per all groups, CK profiler get number of
# channel per group
args.in_channels = int(args.in_channels / args.group_count)
args.out_channels = int(args.out_channels / args.group_count)
if args.forw == 0 or args.forw == 1 or args.forw == 3 or args.forw == 5:
run_ck_grouped_conv_fwd(args)
if args.forw == 0 or args.forw == 2 or args.forw == 3 or args.forw == 6:
run_ck_grouped_conv_bwd_data(args)
if args.forw == 0 or args.forw == 4 or args.forw == 5 or args.forw == 6:
run_ck_grouped_conv_bwd_weight(args)
if __name__ == "__main__":
parser = argparse.ArgumentParser(
prog="converter",
description="Convert miopen driver command to ck Profiler"
"\nExample: python3 "
"../script/convert_miopen_driver_to_profiler.py "
"/opt/rocm/bin/MIOpenDriver conv -n 32 -c 64 -H 28 -W 28 "
"-k 64 -y 3 -x 3 -p 1 -q 1 -u 1 -v 1 -l 1 -j 1 -m conv -g "
"32 -F 1 -t 1",
)
parser.add_argument(
"-in_layout",
"-I",
"--in_layout",
"--I",
default="NCHW",
type=str,
required=False,
help="Input Layout (Default=NCHW for 2d conv, NCDHW for 3d conv)",
)
parser.add_argument(
"-forw",
"-F",
"--forw",
"--F",
default=0,
type=int,
required=False,
help="Flag enables fwd, bwd, wrw convolutions"
"\n0 fwd+bwd+wrw (default)"
"\n1 fwd only"
"\n2 bwd only"
"\n4 wrw only"
"\n3 fwd+bwd"
"\n5 fwd+wrw"
"\n6 bwd+wrw",
)
parser.add_argument(
"-spatial_dim",
"-_",
"--spatial_dim",
"--_",
default=2,
type=int,
required=False,
help="convolution spatial dimension (Default-2)",
)
parser.add_argument(
"-batchsize",
"-n",
"--batchsize",
"--n",
default=100,
type=int,
required=False,
help="Mini-batch size (Default=100)",
)
parser.add_argument(
"-in_channels",
"-c",
"--in_channels",
"--c",
default=3,
type=int,
required=False,
help="Number of Input Channels (Default=3)",
)
parser.add_argument(
"-in_d",
"-!",
"--in_d",
"--!",
default=32,
type=int,
required=False,
help="Input Depth (Default=32)",
)
parser.add_argument(
"-in_h",
"-H",
"--in_h",
"--H",
default=32,
type=int,
required=False,
help="Input Height (Default=32)",
)
parser.add_argument(
"-in_w",
"-W",
"--in_w",
"--W",
default=32,
type=int,
required=False,
help="Input Width (Default=32)",
)
parser.add_argument(
"-out_channels",
"-k",
"--out_channels",
"--k",
default=32,
type=int,
required=False,
help="Number of Output Channels (Default=32)",
)
parser.add_argument(
"-fil_d",
"-@",
"--fil_d",
"--@",
default=3,
type=int,
required=False,
help="Filter Depth (Default=3)",
)
parser.add_argument(
"-fil_h",
"-y",
"--fil_h",
"--y",
default=3,
type=int,
required=False,
help="Filter Height (Default=3)",
)
parser.add_argument(
"-fil_w",
"-x",
"--fil_w",
"--x",
default=3,
type=int,
required=False,
help="Filter Width (Default=3)",
)
parser.add_argument(
"-conv_stride_d",
"-#",
"--conv_stride_d",
"--#",
default=1,
type=int,
required=False,
help="Convolution Stride for Depth (Default=1)",
)
parser.add_argument(
"-conv_stride_h",
"-u",
"--conv_stride_h",
"--u",
default=1,
type=int,
required=False,
help="Convolution Stride for Height (Default=1)",
)
parser.add_argument(
"-conv_stride_w",
"-v",
"--conv_stride_w",
"--v",
default=1,
type=int,
required=False,
help="Convolution Stride for Width (Default=1)",
)
parser.add_argument(
"-pad_d",
"-$",
"--pad_d",
"--$",
default=1,
type=int,
required=False,
help="Zero Padding for Depth (Default=0)",
)
parser.add_argument(
"-pad_h",
"-p",
"--pad_h",
"--p",
default=1,
type=int,
required=False,
help="Zero Padding for Height (Default=0)",
)
parser.add_argument(
"-pad_w",
"-q",
"--pad_w",
"--q",
default=1,
type=int,
required=False,
help="Zero Padding for Width (Default=0)",
)
parser.add_argument(
"-verify",
"-V",
"--verify",
"--V",
default=1,
type=int,
required=False,
help="Verify Each Layer (Default=1)",
)
parser.add_argument(
"-time",
"-t",
"--time",
"--t",
default=0,
type=int,
required=False,
help="Time Each Layer (Default=0)",
)
parser.add_argument(
"-dilation_d",
"-^",
"--dilation_d",
"--^",
default=1,
type=int,
required=False,
help="Dilation of Filter Depth (Default=1)",
)
parser.add_argument(
"-dilation_h",
"-l",
"--dilation_h",
"--l",
default=1,
type=int,
required=False,
help="Dilation of Filter Height (Default=1)",
)
parser.add_argument(
"-dilation_w",
"-j",
"--dilation_w",
"--j",
default=1,
type=int,
required=False,
help="Dilation of Filter Width (Default=1)",
)
parser.add_argument(
"-group_count",
"-g",
"--group_count",
"--g",
type=int,
default=1,
required=False,
help="Number of Groups (Default=1)",
)
parser.add_argument(
"-instance",
"--instance",
type=int,
default=-1,
required=False,
help="Instance index (Default=-1)",
)
parser.add_argument(
"-list-instances",
"--list-instances",
action="store_true",
default=False,
required=False,
help="List valid instances without running",
)
args, unknown = parser.parse_known_args()
init_const_args(args)
process_miopen_driver_name(args, unknown)
print("Ignored args:")
print(unknown)
run_ck_profiler(args)

23
script/count_vgpr.sh Executable file
View File

@@ -0,0 +1,23 @@
#!/bin/bash
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
FILE=$1
for num in {0..255}
do
base_pattern="(\[?${num}\b|\[\d*:${num}\])"
spattern="s${base_pattern}"
vpattern="v${base_pattern}"
apattern="a${base_pattern}"
scount=$(grep -P $spattern $FILE | wc -l)
vcount=$(grep -P $vpattern $FILE | wc -l)
acount=$(grep -P $apattern $FILE | wc -l)
bash -c "echo -n v${num} $vcount && \
echo -n , s${num} $scount && \
echo -n , a${num} $acount"
if [[ $scount -ne 0 || $vcount -ne 0 || $acount -ne 0 ]]; then
echo -n " *"
fi
echo ""
done

View File

@@ -0,0 +1,207 @@
# Dependency-based Selective Test Filtering using Static Analysis of Ninja Builds for C++ Projects
## Overview
This tool provides advanced dependency-based selective test filtering and build optimization for large C++ monorepos using static parsing of Ninja build files. By analyzing both source and header dependencies, it enables precise identification of which tests and executables are affected by code changes, allowing for efficient CI/CD workflows and faster incremental builds.
The parser:
- Identifies all executables in the Ninja build.
- Maps object files to their source and header dependencies using `ninja -t deps`.
- Constructs a reverse mapping from each file to all dependent executables.
- Automatically detects monorepo structure (`projects/<name>/`) and scopes analysis accordingly.
- Exports results in CSV and JSON formats for integration with other tools.
## Features
- **Comprehensive Dependency Tracking**: Captures direct source file dependencies and, critically, all included header files via `ninja -t deps`.
- **Executable to Object Mapping**: Parses the `build.ninja` file to understand how executables are linked from object files.
- **Batch Dependency Extraction**: Runs a single `ninja -t deps` call (no arguments) to dump all dependency information at once, then filters in-memory. This avoids the massive overhead of per-object subprocess calls on large build files (e.g., a 246MB `build.ninja` with 29K+ objects completes in ~2 seconds instead of ~54 minutes).
- **Monorepo Awareness**: Automatically detects `projects/<project>/` paths, strips them to project-relative paths, and scopes `git diff` to only the relevant subtree.
- **File to Executable Inversion**: Inverts the dependency graph to map each file to the set of executables that depend on it.
- **Filtering**: Filters out system files (`/usr/`, `/opt/rocm/`, etc.) and focuses on project-specific dependencies.
- **Multiple Output Formats**:
- **CSV**: `enhanced_file_executable_mapping.csv` - Each row lists a file and a semicolon-separated list of dependent executables.
- **JSON**: `enhanced_dependency_mapping.json` - Includes file-to-executable mapping, executable-to-file mapping, repo metadata, and statistics.
- **Robust Error Handling**: Includes error handling for missing files and failed subprocess commands.
## Prerequisites
- **Python 3.7+**
- **Ninja build system**: The `ninja` executable must be in the system's PATH or its path provided as an argument.
- A **Ninja build directory** containing a `build.ninja` file. The project should have been built at least once (even partially) so that `ninja -t deps` has dependency data.
## Quick Start with launch_tests.sh
The easiest way to use this tool is via the `launch_tests.sh` wrapper script:
```bash
# From the monorepo root (or anywhere):
script/launch_tests.sh /path/to/build-dir
# Uses default build dir (<CK_ROOT>/build) if no argument given:
script/launch_tests.sh
```
This script:
1. Discovers the git root (monorepo root) automatically.
2. Runs the dependency parser against `build.ninja`.
3. Runs `git diff` between `origin/develop` and the current branch (scoped to CK files only).
4. Maps changed files to affected tests/examples.
5. Runs the affected tests via `ctest` in chunks.
Environment variables:
- `CTEST_CHUNK_SIZE`: Number of tests per ctest invocation (default: 10).
- `CTEST_FAIL_FAST`: Set to `true` to stop on first failure (default: `false`).
## Using CMake with Ninja
To use this tool effectively, your C++ project should be configured with CMake to generate Ninja build files:
1. **Configure CMake to use Ninja:**
```bash
cmake -G Ninja \
-DCMAKE_PREFIX_PATH=/opt/rocm \
-DCMAKE_CXX_COMPILER=/opt/rocm/bin/hipcc \
-DCMAKE_BUILD_TYPE=Release \
-DGPU_TARGETS="gfx942" \
/path/to/composablekernel
```
2. **Build your project (full or partial):**
```bash
# Full build
ninja
# Or build specific targets
ninja example_gemm_xdl_fp16 example_gemm_xdl_fp16_v3
```
The parser only extracts dependencies for objects that were actually built.
3. **Run the dependency parser:**
```bash
python main.py parse /path/to/build/build.ninja --workspace-root /path/to/monorepo-root
```
**Note:** `--workspace-root` should point to the **git root** (monorepo root) for correct monorepo detection. If omitted, it defaults to `..` relative to the build directory.
## Usage
All features are available via the unified `main.py` CLI:
```bash
# Dependency parsing
python main.py parse /path/to/build.ninja --workspace-root /path/to/monorepo-root
# Selective test filtering (between git refs)
python main.py select enhanced_dependency_mapping.json <ref1> <ref2> [--all | --test-prefix] [--output <output_json>]
# Code auditing (list all files and their dependent executables)
python main.py audit enhanced_dependency_mapping.json
# Build optimization (list affected executables for specific changed files)
python main.py optimize enhanced_dependency_mapping.json <changed_file1> [<changed_file2> ...]
```
### Parse arguments
| Argument | Required | Description |
|----------|----------|-------------|
| `build_ninja` | Yes | Path to the `build.ninja` file |
| `--workspace-root` | No | Root of the workspace/monorepo (default: `..`) |
| `--ninja` | No | Path to the ninja executable (default: `ninja`) |
### Select arguments
| Argument | Required | Description |
|----------|----------|-------------|
| `depmap_json` | Yes | Path to `enhanced_dependency_mapping.json` |
| `ref1` | Yes | Source git ref (branch or commit SHA) |
| `ref2` | Yes | Target git ref (branch or commit SHA) |
| `--all` | No | Include all affected executables (default) |
| `--test-prefix` | No | Only include executables starting with `test_` |
| `--output` | No | Output JSON file (default: `tests_to_run.json`) |
## How It Works
1. **Build File Parsing (`_parse_build_file`)**:
* Reads the `build.ninja` file (~246MB for the full CK monorepo build).
* Uses regular expressions to identify executable link rules and object compilation rules.
* Populates `executable_to_objects` and `object_to_source` mappings.
2. **Batch Dependency Extraction (`_extract_object_dependencies`)**:
* Runs a single `ninja -t deps` command (no arguments) which dumps all dependency information for every built object file.
* Parses the output and filters to only the objects found in `object_to_source`.
* Strips the workspace root prefix from absolute paths to produce project-relative paths.
3. **Monorepo Path Detection (`_build_file_to_executable_mapping`)**:
* Applies a regex to detect `projects/<project_name>/` in dependency paths.
* Strips the monorepo prefix so paths are relative to the CK project root (e.g., `include/ck/ck.hpp`).
* Records the detected project name for use by the selective test filter.
4. **File Filtering (`_is_project_file`)**:
* Excludes system files (`/usr/`, `/opt/rocm/`, etc.).
* Includes files in known CK directories (`include/`, `library/`, `test/`, `example/`, etc.).
* Also recognizes monorepo-prefixed paths (`projects/composablekernel/include/`, etc.).
5. **Selective Test Filtering (`selective_test_filter.py`)**:
* Loads the dependency mapping JSON.
* Runs `git diff --name-only` between two refs, scoped to `projects/<project>/` when in monorepo mode.
* Strips the monorepo prefix from changed file paths.
* Looks up each changed file in the dependency map to find affected executables.
* Exports the list of tests to run as JSON.
## Output Files
Running the parser generates two files in the build directory:
- **`enhanced_file_executable_mapping.csv`**:
```csv
source_file,executables
"include/ck/ck.hpp","bin/example_gemm_xdl_fp16;bin/example_gemm_xdl_fp16_v3"
"example/01_gemm/gemm_xdl_fp16.cpp","bin/example_gemm_xdl_fp16"
```
- **`enhanced_dependency_mapping.json`**:
```json
{
"repo": {
"type": "monorepo",
"project": "composablekernel"
},
"file_to_executables": {
"include/ck/ck.hpp": ["bin/example_gemm_xdl_fp16", "bin/example_gemm_xdl_fp16_v3"],
"example/01_gemm/gemm_xdl_fp16.cpp": ["bin/example_gemm_xdl_fp16"]
},
"executable_to_files": {
"bin/example_gemm_xdl_fp16": ["include/ck/ck.hpp", "example/01_gemm/gemm_xdl_fp16.cpp"]
},
"statistics": {
"total_files": 180,
"total_executables": 20403,
"total_object_files": 29530,
"files_with_multiple_executables": 140
}
}
```
## Use Cases
- **Selective CI/CD Testing**: Run only the tests affected by a PR's changes, cutting CI time dramatically.
- **Impact Analysis**: Determine which executables need to be rebuilt when a header changes.
- **Build Optimization**: Identify which targets are affected by a set of file changes.
- **Code Auditing**: Get a clear overview of how files are used across different executables.
## Limitations
- Relies on the accuracy of Ninja's dependency information (`ninja -t deps`). If the build system doesn't correctly generate `.d` (dependency) files, the header information might be incomplete.
- Only objects that have been **actually built** will have dependency data. A partial build means partial coverage of the dependency map.
- The definition of "project file" vs. "system file" is based on a path-based heuristic and might need adjustment for other project structures.
## Troubleshooting
- **"ninja: command not found"**: Ensure `ninja` is installed and in your PATH, or provide the full path via `--ninja`.
- **"build.ninja not found"**: Double-check the path to your `build.ninja` file.
- **Empty or Incomplete Output**:
* Make sure the project has been successfully built at least once. `ninja -t deps` relies on information generated during the build.
* Verify that your CMake is configured to generate dependency files for Ninja (`-G Ninja`).
- **JSON shows `"type": "component"` instead of `"monorepo"`**: Ensure `--workspace-root` points to the **git/monorepo root**, not the CK project root. The parser needs to see `projects/<name>/` in the dependency paths to detect monorepo mode.

View File

@@ -0,0 +1,91 @@
#!/usr/bin/env python3
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
# This script generate list of files that are not referenced from any test (list in JSON format)
# Script only looks at not referenced files from three directories: include, library and profiler
# CK needs to be built with ability to use dependency parser and generate dependencies
# Usage: python3 generate_list_of_files_not_referenced_in_tests -f /path/to/enhanced_dependency_mapping/json/file
import argparse
import os
import subprocess
import json
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument(
"-f",
required=True,
help="Path to enhanced_dependency_mapping.json file generated by dependency parser",
)
args = parser.parse_args()
return args
def main():
args = parse_args()
with open(args.f, "r") as file:
ref_files = json.load(file)
file_to_executables = ref_files["file_to_executables"]
# Determine the CK project root: go up two levels from this script's location
# (script/dependency-parser/ -> script/ -> CK root)
script_dir = os.path.dirname(os.path.abspath(__file__))
ck_root = os.path.normpath(os.path.join(script_dir, "..", ".."))
search_dirs = ["include", "library", "profiler"]
all_files = []
for d in search_dirs:
search_path = os.path.join(ck_root, d)
if not os.path.isdir(search_path):
print(f"Warning: directory not found: {search_path}")
continue
result = subprocess.check_output(
f'find "{search_path}" -type f -iname "*.cpp" -o -iname "*.hpp"',
shell=True,
).decode("utf-8").split("\n")
# Convert absolute paths to relative paths from CK root
ck_root_prefix = ck_root.rstrip("/") + "/"
for f in result:
f = f.strip()
if f:
if f.startswith(ck_root_prefix):
f = f[len(ck_root_prefix):]
all_files.append(f)
all_referenced_files = []
for v in file_to_executables:
# Match both standalone paths (include/, library/, profiler/) and
# monorepo paths (projects/composablekernel/include/, etc.)
if any(
d in v
for d in ["include/", "library/", "profiler/"]
):
exe_list = file_to_executables[v]
else:
continue
found = any("bin/test_" in el for el in exe_list)
if found:
all_referenced_files.append(v)
not_referenced_files = {"include": [], "library": [], "profiler": []}
for f in all_files:
# Check if this file appears in any referenced file path
# (handles both relative and monorepo-prefixed paths)
found = any(f in el or el.endswith(f) for el in all_referenced_files)
if not found:
pos = f.find("/")
if pos > 0 and f[:pos] in not_referenced_files:
not_referenced_files[f[:pos]].append(f)
print(json.dumps(not_referenced_files, indent="\t"))
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,110 @@
#!/usr/bin/env python3
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
"""
Unified CLI for Ninja Dependency Analysis and Selective Testing
Features:
- Dependency parsing (from build.ninja)
- Selective test filtering (between git refs)
- Code auditing (--audit)
- Build optimization (--optimize-build)
"""
import argparse
import sys
def run_dependency_parser(args):
from src.enhanced_ninja_parser import main as ninja_main
sys.argv = ["enhanced_ninja_parser.py"] + args
ninja_main()
def run_selective_test_filter(args):
from src.selective_test_filter import main as filter_main
sys.argv = ["selective_test_filter.py"] + args
filter_main()
def main():
parser = argparse.ArgumentParser(
description="Unified Ninja Dependency & Selective Testing Tool"
)
subparsers = parser.add_subparsers(dest="command", required=True)
# Dependency parsing
parser_parse = subparsers.add_parser(
"parse", help="Parse build.ninja and generate dependency mapping"
)
parser_parse.add_argument("build_ninja", help="Path to build.ninja")
parser_parse.add_argument(
"--ninja", help="Path to ninja executable", default="ninja"
)
parser_parse.add_argument(
"--workspace-root", help="Path to workspace root", default=None
)
# Selective testing
parser_test = subparsers.add_parser(
"select", help="Selective test filtering between git refs"
)
parser_test.add_argument("depmap_json", help="Path to dependency mapping JSON")
parser_test.add_argument("ref1", help="Source git ref")
parser_test.add_argument("ref2", help="Target git ref")
parser_test.add_argument(
"--all", action="store_true", help="Include all executables"
)
parser_test.add_argument(
"--test-prefix",
action="store_true",
help="Only include executables starting with 'test_'",
)
parser_test.add_argument(
"--output", help="Output JSON file", default="tests_to_run.json"
)
# Code auditing
parser_audit = subparsers.add_parser(
"audit", help="List all files and their dependent executables"
)
parser_audit.add_argument("depmap_json", help="Path to dependency mapping JSON")
# Build optimization
parser_opt = subparsers.add_parser(
"optimize", help="List affected executables for changed files"
)
parser_opt.add_argument("depmap_json", help="Path to dependency mapping JSON")
parser_opt.add_argument("changed_files", nargs="+", help="List of changed files")
args = parser.parse_args()
if args.command == "parse":
parse_args = [args.build_ninja, args.ninja]
if args.workspace_root:
parse_args.append(args.workspace_root)
run_dependency_parser(parse_args)
elif args.command == "select":
filter_args = [args.depmap_json, args.ref1, args.ref2]
if args.test_prefix:
filter_args.append("--test-prefix")
if args.all:
filter_args.append("--all")
if args.output:
filter_args += ["--output", args.output]
run_selective_test_filter(filter_args)
elif args.command == "audit":
run_selective_test_filter([args.depmap_json, "--audit"])
elif args.command == "optimize":
run_selective_test_filter(
[args.depmap_json, "--optimize-build"] + args.changed_files
)
else:
parser.print_help()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,395 @@
#!/usr/bin/env python3
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
"""
Enhanced Ninja Dependency Parser
This script combines ninja build file parsing with ninja -t deps to create a comprehensive
mapping that includes both source files AND header files, and properly handles files
used by multiple executables.
"""
import re
import os
import sys
import subprocess
from collections import defaultdict
import json
class EnhancedNinjaDependencyParser:
def __init__(self, build_file_path, ninja_executable="ninja"):
self.build_file_path = build_file_path
self.build_dir = os.path.dirname(build_file_path)
self.ninja_executable = ninja_executable
# Core data structures
self.executable_to_objects = {} # exe -> [object_files]
self.object_to_source = {} # object -> primary_source
self.object_to_all_deps = {} # object -> [all_dependencies]
self.file_to_executables = defaultdict(set) # file -> {executables}
def parse_dependencies(self):
"""Main method to parse all dependencies."""
print(f"Parsing ninja dependencies from: {self.build_file_path}")
# Step 1: Parse build file for executable -> object mappings
self._parse_build_file()
# Step 2: Get all object files and their dependencies
print(f"Found {len(self.object_to_source)} object files")
print("Extracting detailed dependencies for all object files...")
self._extract_object_dependencies()
# Step 3: Build the final file -> executables mapping
self._build_file_to_executable_mapping()
def _parse_build_file(self):
"""Parse the ninja build file to extract executable -> object mappings."""
print("Parsing ninja build file...")
with open(self.build_file_path, "r") as f:
content = f.read()
# Parse executable build rules
exe_pattern = r"^build (bin/[^:]+):\s+\S+\s+([^|]+)"
obj_pattern = r"^build ([^:]+\.(?:cpp|cu|hip)\.o):\s+\S+\s+([^\s|]+)"
lines = content.split("\n")
for line in lines:
# Match executable rules
exe_match = re.match(exe_pattern, line)
if exe_match and (
"EXECUTABLE" in line
or "test_" in exe_match.group(1)
or "example_" in exe_match.group(1)
):
exe = exe_match.group(1)
deps_part = exe_match.group(2).strip()
object_files = []
for dep in deps_part.split():
if dep.endswith(".o") and not dep.startswith("/"):
object_files.append(dep)
self.executable_to_objects[exe] = object_files
continue
# Match object compilation rules
obj_match = re.match(obj_pattern, line)
if obj_match:
object_file = obj_match.group(1)
source_file = obj_match.group(2)
self.object_to_source[object_file] = source_file
print(f"Found {len(self.executable_to_objects)} executables")
print(f"Found {len(self.object_to_source)} object-to-source mappings")
def _extract_object_dependencies(self):
"""Extract detailed dependencies for all object files using a single ninja -t deps call.
Previous implementation spawned ninja -t deps per object file (29K+ subprocesses),
each re-parsing the full build.ninja. For large monorepo builds (246MB+ build.ninja),
each call takes 2-14 seconds, making the total ~54 minutes.
This implementation calls ninja -t deps once (no args) to dump ALL deps in ~2 seconds,
then parses the output and filters to only the objects we care about.
"""
object_files = set(self.object_to_source.keys())
if not object_files:
print("No object files found - skipping dependency extraction")
return
print(f"Running single 'ninja -t deps' call for all built objects...")
try:
cmd = [self.ninja_executable, "-t", "deps"]
result = subprocess.run(
cmd, cwd=self.build_dir, capture_output=True, text=True, timeout=120
)
if result.returncode != 0 and not result.stdout:
print(f"Warning: ninja -t deps returned code {result.returncode}")
if result.stderr:
print(f" stderr: {result.stderr.strip()}")
return
# Parse the combined output: each block starts with an object name line,
# followed by indented dependency lines
ws_root = getattr(self, "workspace_root", "..")
ws_prefix = ws_root.rstrip("/") + "/"
current_obj = None
current_deps = []
matched = 0
for line in result.stdout.split("\n"):
if not line:
continue
if not line.startswith(" ") and not line.startswith("\t"):
# This is an object header line like:
# some/path/foo.cpp.o: #deps 42, deps mtime ... (VALID)
# Save the previous block if it was relevant
if current_obj and current_obj in object_files:
self.object_to_all_deps[current_obj] = current_deps
matched += 1
if matched % 100 == 0:
print(f" Matched {matched} objects so far...")
# Parse the new object name (everything before the colon)
colon_pos = line.find(":")
if colon_pos > 0:
current_obj = line[:colon_pos].strip()
current_deps = []
else:
current_obj = None
current_deps = []
else:
# Indented dependency line
if current_obj is not None:
dep_file = line.strip()
if dep_file and not dep_file.startswith("#"):
# Strip workspace root prefix from absolute paths
if dep_file.startswith(ws_prefix):
dep_file = dep_file[len(ws_prefix):]
current_deps.append(dep_file)
# Don't forget the last block
if current_obj and current_obj in object_files:
self.object_to_all_deps[current_obj] = current_deps
matched += 1
except subprocess.TimeoutExpired:
print("Error: ninja -t deps timed out after 120 seconds")
return
except Exception as e:
print(f"Error running ninja -t deps: {e}")
return
print(
f"Completed dependency extraction for {len(self.object_to_all_deps)} "
f"of {len(object_files)} object files"
)
def _build_file_to_executable_mapping(self):
"""Build the final mapping from files to executables."""
print("Building file-to-executable mapping...")
# For monorepo, truncate the path before and including projects/<project_name>
# This regex matches both absolute and relative monorepo paths
self.project = None
rl_regex = rf"(?:^|.*[\\/])projects[\\/]+([^\\/]+)[\\/]+(.*)"
for exe, object_files in self.executable_to_objects.items():
for obj_file in object_files:
# Add all dependencies of this object file
if obj_file in self.object_to_all_deps:
for dep_file in self.object_to_all_deps[obj_file]:
match = re.search(rl_regex, dep_file, re.IGNORECASE)
if match:
dep_file = match.group(2)
if not self.project:
print(f"Found rocm-libraries project: '{match.group(1)}'")
self.project = match.group(1)
# Filter out system files and focus on project files
if self._is_project_file(dep_file):
self.file_to_executables[dep_file].add(exe)
print(f"Built mapping for {len(self.file_to_executables)} files")
# Show statistics
multi_exe_files = {
f: exes for f, exes in self.file_to_executables.items() if len(exes) > 1
}
print(f"Files used by multiple executables: {len(multi_exe_files)}")
if multi_exe_files:
print("Sample files with multiple dependencies:")
for f, exes in sorted(multi_exe_files.items())[:5]:
print(f" {f}: {len(exes)} executables")
def _is_project_file(self, file_path):
"""Determine if a file is part of the project (not system files).
Handles both standalone-style paths (e.g., include/ck/...) and
monorepo-style paths (e.g., projects/composablekernel/include/ck/...).
"""
# Exclude system files first (absolute paths to system dirs)
if any(
file_path.startswith(prefix)
for prefix in ["/usr/", "/opt/rocm", "/lib/", "/system/", "/local/"]
):
return False
# Project directory prefixes (without monorepo prefix).
# These match paths relative to the CK project root.
project_dirs = [
"include/",
"library/",
"test/",
"example/",
"src/",
"profiler/",
"build/include/",
"build/_deps/gtest",
"client_example",
"codegen",
"tile_engine",
"dispatcher",
"experimental",
"tutorial",
]
# Check both stripped paths (relative to CK root) and
# monorepo-prefixed paths (relative to monorepo root)
if any(file_path.startswith(prefix) for prefix in project_dirs):
return True
# Also check monorepo-style paths (projects/composablekernel/...)
if any(
file_path.startswith(f"projects/composablekernel/{prefix}")
for prefix in project_dirs
):
return True
# Include files with common source/header extensions that weren't
# excluded as system files above
if file_path.endswith(
(".cpp", ".hpp", ".h", ".c", ".cc", ".cxx", ".cu", ".hip", ".inc")
):
return True
return False
def export_to_csv(self, output_file):
"""Export the file-to-executable mapping to CSV with proper comma separation."""
print(f"Exporting mapping to {output_file}")
with open(output_file, "w") as f:
f.write("source_file,executables\n")
for file_path in sorted(self.file_to_executables.keys()):
executables = sorted(self.file_to_executables[file_path])
# Use semicolon to separate multiple executables within the field
exe_list = ";".join(executables)
f.write(f'"{file_path}","{exe_list}"\n')
def export_to_json(self, output_file):
"""Export the complete mapping to JSON."""
print(f"Exporting complete mapping to {output_file}")
# Build reverse mapping (executable -> files)
exe_to_files = defaultdict(set)
for file_path, exes in self.file_to_executables.items():
for exe in exes:
exe_to_files[exe].add(file_path)
mapping_data = {
"repo": {
"type": "monorepo" if self.project else "component",
"project": self.project
},
"file_to_executables": {
file_path: list(exes)
for file_path, exes in self.file_to_executables.items()
},
"executable_to_files": {
exe: sorted(files) for exe, files in exe_to_files.items()
},
"statistics": {
"total_files": len(self.file_to_executables),
"total_executables": len(self.executable_to_objects),
"total_object_files": len(self.object_to_source),
"files_with_multiple_executables": len(
[f for f, exes in self.file_to_executables.items() if len(exes) > 1]
),
},
}
with open(output_file, "w") as f:
json.dump(mapping_data, f, indent=2)
def print_summary(self):
"""Print a summary of the parsed dependencies."""
print("\n=== Enhanced Dependency Mapping Summary ===")
print(f"Total executables: {len(self.executable_to_objects)}")
print(f"Total files mapped: {len(self.file_to_executables)}")
print(f"Total object files processed: {len(self.object_to_all_deps)}")
# Files by type
cpp_files = sum(
1 for f in self.file_to_executables.keys() if f.endswith(".cpp")
)
hpp_files = sum(
1 for f in self.file_to_executables.keys() if f.endswith(".hpp")
)
h_files = sum(1 for f in self.file_to_executables.keys() if f.endswith(".h"))
print("\nFile types:")
print(f" .cpp files: {cpp_files}")
print(f" .hpp files: {hpp_files}")
print(f" .h files: {h_files}")
# Multi-executable files
multi_exe_files = {
f: exes for f, exes in self.file_to_executables.items() if len(exes) > 1
}
print(f"\nFiles used by multiple executables: {len(multi_exe_files)}")
if multi_exe_files:
print("\nTop files with most dependencies:")
sorted_multi = sorted(
multi_exe_files.items(), key=lambda x: len(x[1]), reverse=True
)
for file_path, exes in sorted_multi[:10]:
print(f" {file_path}: {len(exes)} executables")
def main():
# Accept: build_file, ninja_path, workspace_root
default_workspace_root = ".."
if len(sys.argv) > 3:
build_file = sys.argv[1]
ninja_path = sys.argv[2]
workspace_root = sys.argv[3]
elif len(sys.argv) > 2:
build_file = sys.argv[1]
ninja_path = sys.argv[2]
workspace_root = default_workspace_root
elif len(sys.argv) > 1:
build_file = sys.argv[1]
ninja_path = "ninja"
workspace_root = default_workspace_root
else:
build_file = f"{default_workspace_root}/build/build.ninja"
ninja_path = "ninja"
workspace_root = default_workspace_root
if not os.path.exists(build_file):
print(f"Error: Build file not found: {build_file}")
sys.exit(1)
try:
subprocess.run([ninja_path, "--version"], capture_output=True, check=True)
except (subprocess.CalledProcessError, FileNotFoundError):
print(f"Error: ninja executable not found: {ninja_path}")
sys.exit(1)
parser = EnhancedNinjaDependencyParser(build_file, ninja_path)
parser.workspace_root = workspace_root # Attach for use in _extract_object_dependencies
parser.parse_dependencies()
parser.print_summary()
# Export results
output_dir = os.path.dirname(build_file)
csv_file = os.path.join(output_dir, "enhanced_file_executable_mapping.csv")
json_file = os.path.join(output_dir, "enhanced_dependency_mapping.json")
parser.export_to_csv(csv_file)
parser.export_to_json(json_file)
print("\nResults exported to:")
print(f" CSV: {csv_file}")
print(f" JSON: {json_file}")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,175 @@
#!/usr/bin/env python3
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
"""
Selective Test Filter Tool
Given two git refs (branches or commit IDs), this tool:
- Identifies changed files between the refs
- Loads the enhanced dependency mapping JSON (from enhanced_ninja_parser.py)
- Maps changed files to affected test executables (optionally filtering for "test_" prefix)
- Exports the list of tests to run to tests_to_run.json
Usage:
python selective_test_filter.py <depmap_json> <ref1> <ref2> [--all | --test-prefix] [--output <output_json>]
Arguments:
<depmap_json> Path to enhanced_dependency_mapping.json
<ref1> Source git ref (branch or commit)
<ref2> Target git ref (branch or commit)
Options:
--all Include all executables (default)
--test-prefix Only include executables starting with "test_"
--output Output JSON file (default: tests_to_run.json)
"""
import sys
import subprocess
import json
import os
def get_changed_files(ref1, ref2, project: str = None):
"""Return a set of files changed between two git refs."""
try:
cmd = ["git", "diff", "--name-only", ref1, ref2]
if project:
# Scope git diff to only this project's subtree for efficiency
cmd += ["--", f"projects/{project}/"]
result = subprocess.run(
cmd,
capture_output=True,
text=True,
check=True,
)
raw_files = set(line.strip() for line in result.stdout.splitlines() if line.strip())
if project is None:
files = raw_files
print(f"Identified {len(files)} modified files")
else:
root = f"projects/{project}/"
root_len = len(root)
files = set()
for f in raw_files:
if f.startswith(root):
files.add(f[root_len:])
print(f"Identified {len(files)} files modified in project '{project}'")
return files
except subprocess.CalledProcessError as e:
print(f"Command '{e.cmd}' returned non-zero exit status {e.returncode}.")
print(f"Error output: {e.stderr}")
print(f"Standard output: {e.stdout}")
sys.exit(1)
def load_depmap(depmap_json):
"""Load the dependency mapping JSON."""
with open(depmap_json, "r") as f:
data = json.load(f)
# Support both old and new formats
json_project = None
if "repo" in data and data["repo"]["type"] == "monorepo":
json_project = data["repo"]["project"]
if "file_to_executables" in data:
return data["file_to_executables"], json_project
return data, json_project
def select_tests(file_to_executables, changed_files, filter_mode):
"""Return a set of test executables affected by changed files."""
affected = set()
for f in changed_files:
if f in file_to_executables:
for exe in file_to_executables[f]:
if filter_mode == "all":
affected.add(exe)
elif filter_mode == "test_prefix" and exe.startswith("test_"):
affected.add(exe)
return sorted(affected)
def main():
if "--audit" in sys.argv:
if len(sys.argv) < 2:
print("Usage: python selective_test_filter.py <depmap_json> --audit")
sys.exit(1)
depmap_json = sys.argv[1]
if not os.path.exists(depmap_json):
print(f"Dependency map JSON not found: {depmap_json}")
sys.exit(1)
file_to_executables, _ = load_depmap(depmap_json)
for f, exes in file_to_executables.items():
print(f"{f}: {', '.join(exes)}")
print(f"Total files: {len(file_to_executables)}")
sys.exit(0)
if "--optimize-build" in sys.argv:
if len(sys.argv) < 3:
print(
"Usage: python selective_test_filter.py <depmap_json> --optimize-build <changed_file1> [<changed_file2> ...]"
)
sys.exit(1)
depmap_json = sys.argv[1]
changed_files = set(sys.argv[sys.argv.index("--optimize-build") + 1 :])
if not os.path.exists(depmap_json):
print(f"Dependency map JSON not found: {depmap_json}")
sys.exit(1)
file_to_executables, _ = load_depmap(depmap_json)
affected_executables = set()
for f in changed_files:
if f in file_to_executables:
affected_executables.update(file_to_executables[f])
print("Affected executables:")
for exe in sorted(affected_executables):
print(exe)
print(f"Total affected executables: {len(affected_executables)}")
sys.exit(0)
if len(sys.argv) < 4:
print(
"Usage: python selective_test_filter.py <depmap_json> <ref1> <ref2> [--all | --test-prefix] [--output <output_json>]"
)
sys.exit(1)
depmap_json = sys.argv[1]
ref1 = sys.argv[2]
ref2 = sys.argv[3]
filter_mode = "all"
output_json = "tests_to_run.json"
if "--test-prefix" in sys.argv:
filter_mode = "test_prefix"
if "--all" in sys.argv:
filter_mode = "all"
if "--output" in sys.argv:
idx = sys.argv.index("--output")
if idx + 1 < len(sys.argv):
output_json = sys.argv[idx + 1]
if not os.path.exists(depmap_json):
print(f"Dependency map JSON not found: {depmap_json}")
sys.exit(1)
file_to_executables, json_project = load_depmap(depmap_json)
changed_files = get_changed_files(ref1, ref2, json_project)
if not changed_files:
print("No changed files detected.")
tests = []
else:
tests = select_tests(file_to_executables, changed_files, filter_mode)
with open(output_json, "w") as f:
json.dump(
{"tests_to_run": tests, "changed_files": sorted(changed_files)}, f, indent=2
)
print(f"Exported {len(tests)} tests to run to {output_json}")
if __name__ == "__main__":
main()

115
script/gemm_profile.sh Executable file
View File

@@ -0,0 +1,115 @@
#!/bin/bash
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
BIN=./bin/tile_example_gemm_weight_preshuffle
PREC=fp8
VERBOSITY=2
# List of all (m, n, k) triplets
ARGS_LIST=(
"1 2048 5120"
"1 5120 1024"
"2 2048 5120"
"2 5120 1024"
"3 2048 5120"
"3 5120 1024"
"4 2048 5120"
"4 5120 1024"
"5 2048 5120"
"5 5120 1024"
"6 2048 5120"
"6 5120 1024"
"7 2048 5120"
"7 5120 1024"
"8 2048 5120"
"8 5120 1024"
"9 2048 5120"
"9 5120 1024"
"10 2048 5120"
"10 5120 1024"
"11 2048 5120"
"11 5120 1024"
"12 2048 5120"
"12 5120 1024"
"13 2048 5120"
"13 5120 1024"
"14 2048 5120"
"14 5120 1024"
"15 2048 5120"
"15 5120 1024"
"16 64 128"
"16 64 256"
"16 2048 5120"
"16 5120 1024"
"512 768 640"
"1024 1792 896"
"1536 2816 1152"
"2048 5120 1024"
"2048 5120 8192"
"2048 7168 8192"
"2048 8192 3584"
"16384 7168 8192"
"16384 8192 3584"
)
# Output file
OUTPUT_FILE="gemm_profile_results.csv"
# Output header
echo "m,n,k,Pipeline,Time_ms,TFlops,GBps,Verification" > "$OUTPUT_FILE"
# Loop over each argument set
for args in "${ARGS_LIST[@]}"; do
read -r m n k <<< "$args"
echo "Testing: m=$m, n=$n, k=$k"
OUTPUT=$($BIN -m=$m -n=$n -k=$k -prec=$PREC -v=$VERBOSITY 2>/dev/null)
# Extract pipeline information
# Format: "Launching kernel with args: gemm_fp8_pipeline_AGmemBGmemCRegV2_128x256x256x256_16x16x128_16x16_0x0x0"
PIPELINE=$(echo "$OUTPUT" | grep "Launching kernel with args:" | sed -n 's/.*Launching kernel with args: \(.*\)/\1/p')
# Extract TFlops and GB/s from the output
# Format: "Run Gemm kernel with M=3840 N=4096 K=2048 ... : 0.042338 ms, 1521.67 TFlops, 1126.89 GB/s,"
PERF_LINE=$(echo "$OUTPUT" | grep "TFlops")
# Extract verification result
# Format: "The GPU verification result is:correct" (note: no space after colon)
VERIFICATION=$(echo "$OUTPUT" | grep "The GPU verification result is:" | sed -n 's/.*The GPU verification result is:\(.*\)/\1/p')
if [ -n "$PERF_LINE" ]; then
# Extract execution time in ms
TIME_MS=$(echo "$PERF_LINE" | grep -o '[0-9]\+\.[0-9]\+ ms' | grep -o '[0-9]\+\.[0-9]\+')
# Extract TFlops value - more robust regex
TFLOPS=$(echo "$PERF_LINE" | grep -o '[0-9]\+\.[0-9]\+ TFlops' | grep -o '[0-9]\+\.[0-9]\+')
# Extract GB/s value - more robust regex
GBPS=$(echo "$PERF_LINE" | grep -o '[0-9]\+\.[0-9]\+ GB/s' | grep -o '[0-9]\+\.[0-9]\+')
# Use extracted pipeline or default if not found
if [ -z "$PIPELINE" ]; then
PIPELINE="gemm_basic"
fi
# Print to terminal
echo " Pipeline: $PIPELINE"
echo " Time: ${TIME_MS} ms"
echo " TFlops: ${TFLOPS}"
echo " GB/s: ${GBPS}"
echo " Verification: ${VERIFICATION:-N/A}"
# Save to CSV file
echo "$m,$n,$k,$PIPELINE,$TIME_MS,$TFLOPS,$GBPS,$VERIFICATION" >> "$OUTPUT_FILE"
else
echo " ERROR: Could not parse performance data"
echo ""
echo "$m,$n,$k,$PIPELINE,,,,$VERIFICATION" >> "$OUTPUT_FILE"
fi
done
echo "=========================================="
echo "Profile completed!"
echo "Results saved to: $OUTPUT_FILE"
echo "Total tests run: ${#ARGS_LIST[@]}"
echo "=========================================="

7
script/hip_fatbin_insert Normal file
View File

@@ -0,0 +1,7 @@
SECTIONS {
.hipFatBinSegment : { *(.hipFatBinSegment) }
} INSERT AFTER .bss
SECTIONS {
.hip_fatbin : { *(.hip_fatbin) }
} INSERT AFTER .hipFatBinSegment

28
script/hipclang_opt.sh Executable file
View File

@@ -0,0 +1,28 @@
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
rm *.ll *.s
BC_FILE=$1
/opt/rocm/llvm/bin/llvm-dis $BC_FILE -o original.ll
/opt/rocm/llvm/bin/opt -S -inline -inline-threshold=104857 original.ll > inline.ll
/opt/rocm/llvm/bin/opt -S -sroa inline.ll > sroa.ll
/opt/rocm/llvm/bin/opt -S -O3 sroa.ll > o3.ll
/opt/rocm/llvm/bin/llc -mcpu=gfx906 original.ll
/opt/rocm/llvm/bin/llc -mcpu=gfx906 inline.ll
/opt/rocm/llvm/bin/llc -mcpu=gfx906 sroa.ll
/opt/rocm/llvm/bin/llc -mcpu=gfx906 o3.ll
#/opt/rocm/llvm/bin/opt -S -O3 -sroa inline.ll > o3.ll
#/opt/rocm/llvm/bin/opt -S -O3 -sroa o3.ll > o3_2.ll
#/opt/rocm/llvm/bin/opt -S -O3 -sroa o3_2.ll > o3_3.ll
#/opt/rocm/llvm/bin/opt -S -O3 -sroa o3_3.ll > o3_4.ll
#/opt/rocm/llvm/bin/llc -mcpu=gfx908 opt.ll
#/opt/rocm/llvm/bin/llc -mcpu=gfx908 inline.ll
#/opt/rocm/llvm/bin/llc -mcpu=gfx908 o3.ll
#/opt/rocm/llvm/bin/llc -mcpu=gfx908 o3_2.ll
#/opt/rocm/llvm/bin/llc -mcpu=gfx908 o3_3.ll
#/opt/rocm/llvm/bin/llc -mcpu=gfx908 o3_4.ll

View File

@@ -0,0 +1,54 @@
const puppeteer = require('puppeteer');
const buildTraceFileName = process.env.BUILD_TRACE_FILE;
(async () => {
try {
// Launch the browser
const browser = await puppeteer.launch({
args: [
'--no-sandbox',
'--headless',
'--disable-gpu',
'--window-size=1920x1080'
]});
const page = await browser.newPage();
await page.setViewport({ width: 1920, height: 1080 });
await page.goto('https://ui.perfetto.dev');
// Wait for the home page to be visible
console.log('Waiting for page to load...');
await page.waitForSelector('.pf-home-page', { visible: true, timeout: 30000 });
// Locate and click the Open trace button
const elements = await page.$$('li');
let element = null;
for (const el of elements) {
const text = await el.evaluate(node => node.textContent);
if (text && text.includes('Open trace file')) {
element = el;
break;
}
}
if (element) {
const [fileChooser] = await Promise.all([
page.waitForFileChooser(),
element.click()
]);
await fileChooser.accept([`/workspace/${buildTraceFileName}`]);
} else {
throw new Error('Element not found');
}
console.log('Waiting for data to load...');
// Wait for the timeline element to be visible
await page.waitForSelector('.pf-track', { timeout: 30000 });
// Wait for the data to finish loading
await page.waitForFunction(() => {
return !document.body.textContent.includes('Loading...');
}, { timeout: 30000 });
console.log('Capturing screenshot...');
await page.screenshot({path: '/workspace/perfetto_snapshot_build.png'});
console.log('Done capturing screenshot...');
await browser.close();
} catch (err) {
console.error(err);
process.exit(1);
}
})();

23
script/install_precommit.sh Executable file
View File

@@ -0,0 +1,23 @@
#!/bin/bash
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
run_and_check() {
"$@"
status=$?
if [ $status -ne 0 ]; then
echo "Error with \"$@\": Exited with status $status"
exit $status
fi
return $status
}
echo "I: Creating and activating virtual environment for pre-commit..."
python3 -m venv "$(dirname "$0")/../.venv"
source "$(dirname "$0")/../.venv/bin/activate"
echo "I: Installing pre-commit in virtual environment..."
run_and_check pip install pre-commit
run_and_check pre-commit install
echo "I: Installation successful."

132
script/launch_tests.sh Executable file
View File

@@ -0,0 +1,132 @@
#!/bin/bash
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
# Usage: launch_tests.sh [BUILD_DIR]
# BUILD_DIR: Path to the Ninja build directory (default: <CK_ROOT>/build)
# Get the directory where the script is located
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Go one level up to PACKAGE_HOME (the CK project root)
PACKAGE_HOME="$(dirname "$SCRIPT_DIR")"
# Discover the monorepo root (git toplevel), falling back to PACKAGE_HOME
GIT_ROOT="$(git -C "$PACKAGE_HOME" rev-parse --show-toplevel 2>/dev/null)" || GIT_ROOT="$PACKAGE_HOME"
# Accept an optional build directory argument; default to $PACKAGE_HOME/build
BUILD_DIR="${1:-$PACKAGE_HOME/build}"
BUILD_NINJA_FILE="$BUILD_DIR/build.ninja"
if [ ! -f "$BUILD_NINJA_FILE" ]; then
echo "Error: build.ninja not found at $BUILD_NINJA_FILE"
echo "Usage: $0 [BUILD_DIR]"
echo "Please build the project first (e.g., cmake -G Ninja ... && ninja)"
exit 1
fi
python3 "$SCRIPT_DIR/dependency-parser/main.py" parse "$BUILD_NINJA_FILE" --workspace-root "$GIT_ROOT"
# Path to enhanced_dependency_mapping.json in the same directory
JSON_FILE="$BUILD_DIR/enhanced_dependency_mapping.json"
# Check if the JSON file exists
if [ ! -f "$JSON_FILE" ]; then
echo "Error: $JSON_FILE not found."
exit 1
fi
branch=$(git -C "$GIT_ROOT" rev-parse --abbrev-ref HEAD)
# Run the command from the git root so that git diff paths are correct
cd "$GIT_ROOT"
python3 "$SCRIPT_DIR/dependency-parser/main.py" select "$JSON_FILE" origin/develop "$branch"
# Path to tests_to_run.json in the same directory
TEST_FILE="tests_to_run.json"
# Configuration: Adjust these defaults as needed
# Number of tests per ctest command (can be overridden with CTEST_CHUNK_SIZE env var)
DEFAULT_CHUNK_SIZE=10
# Whether to stop on first failure (can be overridden with CTEST_FAIL_FAST env var)
DEFAULT_FAIL_FAST=false
# Split tests into chunks and run multiple ctest commands
# Export variables so Python subprocess can access them
export CHUNK_SIZE=${CTEST_CHUNK_SIZE:-$DEFAULT_CHUNK_SIZE}
export FAIL_FAST=${CTEST_FAIL_FAST:-$DEFAULT_FAIL_FAST}
python3 -c "
import json
import os
import sys
import subprocess
CHUNK_SIZE = int(os.environ.get('CHUNK_SIZE', '10'))
FAIL_FAST = os.environ.get('FAIL_FAST', 'false').lower() == 'true'
with open('$TEST_FILE', 'r') as f:
data = json.load(f)
tests = data.get('tests_to_run', [])
if not tests:
print('# No tests to run')
sys.exit(0)
# Extract just the filename after the last '/'
clean_tests = [os.path.basename(test) for test in tests]
total_tests = len(clean_tests)
total_chunks = (total_tests + CHUNK_SIZE - 1) // CHUNK_SIZE
print(f'# Total tests to run: {total_tests}')
print(f'# Running in {total_chunks} chunk(s) of up to {CHUNK_SIZE} tests each')
print(f'# Fail-fast mode: {FAIL_FAST}')
print()
failed_chunks = []
# Split into chunks
for i in range(0, total_tests, CHUNK_SIZE):
chunk = clean_tests[i:i+CHUNK_SIZE]
chunk_num = (i // CHUNK_SIZE) + 1
print(f'Running test chunk {chunk_num}/{total_chunks} ({len(chunk)} tests)...')
sys.stdout.flush()
# Run ctest command, don't raise exception on failure
cmd = ['ctest', '--output-on-failure', '-R', '|'.join(chunk)]
try:
result = subprocess.run(cmd, cwd='$BUILD_DIR', check=False)
if result.returncode != 0:
failed_chunks.append(chunk_num)
print(f'WARNING: Chunk {chunk_num} had test failures (exit code: {result.returncode})')
# If fail-fast is enabled, exit immediately
if FAIL_FAST:
print(f'FAIL-FAST: Stopping at chunk {chunk_num} due to failures')
sys.exit(1)
except Exception as e:
print(f'ERROR: Failed to run chunk {chunk_num}: {e}')
failed_chunks.append(chunk_num)
if FAIL_FAST:
sys.exit(1)
print()
sys.stdout.flush()
# Print summary
print('=' * 60)
if failed_chunks:
print(f'SUMMARY: {len(failed_chunks)} of {total_chunks} chunk(s) had failures: {failed_chunks}')
print('=' * 60)
sys.exit(1)
else:
print(f'SUMMARY: All {total_chunks} chunk(s) passed successfully!')
print('=' * 60)
sys.exit(0)
"
PYTHON_EXIT=$?
exit $PYTHON_EXIT

View File

@@ -0,0 +1,119 @@
#!/bin/bash
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
# Continuous monitoring script for sccache during builds
# Usage: ./monitor_sccache_during_build.sh [log_prefix] &
LOG_PREFIX=${1:-"sccache_monitor"}
# Include stage name in log filename if available
STAGE_SUFFIX=""
if [ -n "${STAGE_NAME}" ]; then
# Convert stage name to filename-safe format (replace spaces and special chars with underscores)
STAGE_SAFE=$(echo "${STAGE_NAME}" | sed 's/[^a-zA-Z0-9]/_/g' | sed 's/__*/_/g' | sed 's/^_\|_$//g')
STAGE_SUFFIX="_${STAGE_SAFE}"
fi
MONITOR_LOG="logs/${LOG_PREFIX}_$(date +%Y%m%d_%H%M%S)${STAGE_SUFFIX}.log"
MONITOR_INTERVAL=30 # seconds
echo "Starting sccache monitoring - logging to $MONITOR_LOG"
echo "Monitor interval: $MONITOR_INTERVAL seconds"
# Function to log with timestamp
log_with_timestamp() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$MONITOR_LOG"
}
# Function to get sccache stats safely
get_sccache_stats() {
if command -v sccache &> /dev/null; then
sccache --show-stats 2>/dev/null || echo "sccache stats unavailable"
else
echo "sccache command not found"
fi
}
# Function to check if sccache server is running
is_sccache_running() {
if command -v sccache &> /dev/null; then
sccache --show-stats &> /dev/null
return $?
else
return 1
fi
}
# Function to test Redis connectivity
test_redis_connectivity() {
# Use SCCACHE_REDIS if set, otherwise construct from CK_SCCACHE
local REDIS_URL=""
if [ -n "${SCCACHE_REDIS}" ]; then
REDIS_URL="${SCCACHE_REDIS}"
elif [ -n "${CK_SCCACHE}" ]; then
REDIS_URL="redis://${CK_SCCACHE}"
fi
if [ -n "${REDIS_URL}" ]; then
local start_time=$(date +%s%N)
local response=$(timeout 5 redis-cli -u "${REDIS_URL}" ping 2>&1) || response="TIMEOUT"
local end_time=$(date +%s%N)
local latency=$(( (end_time - start_time) / 1000000 ))
echo "Redis: $response (${latency}ms)"
else
echo "Redis: No Redis URL available"
fi
}
# Gets the last sccache stats before exiting
cleanup() {
log_with_timestamp "=== FINAL SCCACHE STATS EXIT ==="
log_with_timestamp "$(get_sccache_stats)"
echo "=== CONTINUOUS MONITORING STOPPED ==="
# List monitoring logs
echo "=== MONITORING LOGS ==="
ls -la logs/*monitor*.log 2>/dev/null || echo "No monitoring logs found"
}
trap cleanup EXIT
log_with_timestamp "=== SCCACHE MONITORING STARTED ==="
log_with_timestamp "PID: $$"
log_with_timestamp "Node: ${NODE_NAME:-$(hostname)}"
log_with_timestamp "Stage: ${STAGE_NAME:-unknown}"
log_with_timestamp "WORKSPACE_PATH: ${WORKSPACE:-not set}"
log_with_timestamp "SCCACHE_C_CUSTOM_CACHE_BUSTER: ${SCCACHE_C_CUSTOM_CACHE_BUSTER:-not set}"
log_with_timestamp "CK_SCCACHE: ${CK_SCCACHE:-not set}"
# Initial state
log_with_timestamp "=== INITIAL STATE ==="
# Reset sscache stats
sccache --zero-stats
log_with_timestamp "$(get_sccache_stats) $(test_redis_connectivity)"
# Monitor loop
while true; do
sleep $MONITOR_INTERVAL
# Check if sccache server is still running
if ! is_sccache_running; then
log_with_timestamp "WARNING: sccache server not running!"
fi
# Get current stats
current_stats=$(get_sccache_stats)
redis_status=$(test_redis_connectivity)
# Log current cache hit information
log_with_timestamp "$(get_sccache_stats) $(test_redis_connectivity)"
# Check for Redis latency issues
if echo "$redis_status" | grep -E "[0-9]{3,}" > /dev/null; then # >100ms latency
log_with_timestamp "HIGH REDIS LATENCY detected"
fi
# Check for Redis connection failures
if echo "$redis_status" | grep -E "(TIMEOUT|Connection refused|No route)" > /dev/null; then
log_with_timestamp "REDIS CONNECTION FAILURE detected"
fi
done

View File

@@ -0,0 +1,549 @@
#!/usr/bin/env python3
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
"""
Converts .ninja_log files into Chrome's about:tracing format.
"""
import argparse
import json
import os
import re
import sys
from pathlib import Path
from typing import Dict, List, Optional, Iterator
class BuildTarget:
"""Represents a single build target with timing information."""
def __init__(self, start_time: int, end_time: int, output_name: str, cmd_hash: str):
self.start_time = int(start_time)
self.end_time = int(end_time)
self.cmd_hash = cmd_hash
self.duration = self.end_time - self.start_time
self.targets = [output_name] # List of target names for this command hash
@property
def category(self) -> str:
"""Categorize the build target based on file extension."""
# Use the first target for categorization
primary_target = self.targets[0] if self.targets else ""
ext = Path(primary_target).suffix.lower()
if ext in [".o", ".obj"]:
return "compile"
elif ext in [".a", ".lib"]:
return "archive"
elif ext in [".so", ".dll", ".dylib"]:
return "link_shared"
elif ext in [".exe", ".out"]:
return "link_executable"
elif "test" in primary_target.lower():
return "test"
else:
return "other"
@property
def output_name(self) -> str:
"""Get the primary output name (for backward compatibility)."""
return self.targets[0] if self.targets else ""
class ThreadScheduler:
"""Simulates thread allocation for parallelism analysis."""
def __init__(self, legacy_mode: bool = False):
self.workers: List[int] = []
self.legacy_mode = legacy_mode
def allocate_thread(self, target: BuildTarget) -> int:
"""Allocate a thread for the given target."""
if self.legacy_mode:
# Legacy algorithm from old ninjatracer
for worker in range(len(self.workers)):
if self.workers[worker] >= target.end_time:
self.workers[worker] = target.start_time
return worker
self.workers.append(target.start_time)
return len(self.workers) - 1
else:
# New algorithm
for i, worker_end_time in enumerate(self.workers):
if worker_end_time <= target.start_time:
self.workers[i] = target.end_time
return i
# No available worker, create a new one
self.workers.append(target.end_time)
return len(self.workers) - 1
class NinjaLogParser:
"""Parser for ninja build log files."""
def __init__(self, show_all_builds: bool = False):
self.show_all_builds = show_all_builds
def parse_log_file(self, log_path: str) -> List[BuildTarget]:
"""Parse the ninja log file and return build targets."""
if not os.path.exists(log_path):
raise FileNotFoundError(f"Ninja log file not found: {log_path}")
with open(log_path, "r", encoding="utf-8") as file:
lines = file.readlines()
if not lines:
raise ValueError("Empty ninja log file")
# Parse and validate header
header = lines[0].strip()
version_match = re.match(r"^# ninja log v(\d+)$", header)
if not version_match:
raise ValueError(f"Invalid ninja log header: {header}")
version = int(version_match.group(1))
if version < 5:
raise ValueError(f"Unsupported ninja log version: {version}")
# Skip additional header line for version 6
start_line = 2 if version > 5 else 1
targets: Dict[str, BuildTarget] = {}
last_end_time = 0
for line_num, line in enumerate(lines[start_line:], start=start_line + 1):
line = line.strip()
# Skip empty lines and comments
if not line or line.startswith("#"):
continue
parts = line.split("\t")
if len(parts) < 5:
print(
f"Warning: Skipping malformed line {line_num}: {line}",
file=sys.stderr,
)
continue
try:
start_time, end_time, _, output_name, cmd_hash = parts[:5]
start_time, end_time = int(start_time), int(end_time)
# Handle incremental builds
if not self.show_all_builds and end_time < last_end_time:
targets.clear()
last_end_time = end_time
# Group targets by command hash
if cmd_hash not in targets:
targets[cmd_hash] = BuildTarget(
start_time, end_time, output_name, cmd_hash
)
else:
# Update with the latest timing and add output
existing = targets[cmd_hash]
existing.start_time = min(existing.start_time, start_time)
existing.end_time = max(existing.end_time, end_time)
existing.duration = existing.end_time - existing.start_time
existing.targets.append(output_name)
except (ValueError, IndexError) as e:
print(f"Warning: Error parsing line {line_num}: {e}", file=sys.stderr)
continue
return sorted(targets.values(), key=lambda t: t.end_time, reverse=True)
class FTimeTraceReader:
"""Reads and processes Clang -ftime-trace JSON files."""
def __init__(self, granularity_us: int = 50000):
self.granularity_us = granularity_us
def read_trace_file(self, trace_path: str) -> Optional[Dict]:
"""Read and parse a Clang time trace file."""
try:
with open(trace_path, "r", encoding="utf-8") as f:
return json.load(f)
except (FileNotFoundError, json.JSONDecodeError, IOError):
return None
def filter_events(self, trace_data: Dict) -> List[Dict]:
"""Filter trace events based on criteria."""
if "traceEvents" not in trace_data:
return []
filtered_events = []
for event in trace_data["traceEvents"]:
# Only include complete events (ph=X) that meet duration threshold
if (
event.get("ph") == "X"
and event.get("dur", 0) >= self.granularity_us
and not event.get("name", "").startswith("Total")
):
filtered_events.append(event)
return filtered_events
def adjust_event_timing(
self, event: Dict, target: BuildTarget, pid: int, tid: int
) -> Dict:
"""Adjust event timing to align with ninja build timing."""
ninja_duration_us = target.duration * 1000
# Validate event duration against ninja timing
if event.get("dur", 0) > ninja_duration_us:
print(
f"Warning: Clang trace event duration ({event['dur']}μs) exceeds "
f"ninja duration ({ninja_duration_us}μs) for {target.output_name}",
file=sys.stderr,
)
return None
# Adjust event timing
adjusted_event = event.copy()
adjusted_event["pid"] = pid
adjusted_event["tid"] = tid
adjusted_event["ts"] += target.start_time * 1000 # Offset by ninja start time
return adjusted_event
class ChromeTraceGenerator:
"""Generates Chrome tracing format from build targets."""
def __init__(
self,
process_id: int = 1,
embed_ftime_traces: bool = False,
granularity_us: int = 50000,
ninja_log_dir: Optional[str] = None,
legacy_format: bool = False,
):
self.process_id = process_id
self.scheduler = ThreadScheduler(legacy_mode=legacy_format)
self.embed_ftime_traces = embed_ftime_traces
self.ninja_log_dir = ninja_log_dir
self.ftime_reader = (
FTimeTraceReader(granularity_us) if embed_ftime_traces else None
)
self.legacy_format = legacy_format
def find_ftime_trace_files(self, target: BuildTarget) -> List[str]:
"""Find Clang -ftime-trace files for a build target."""
if not self.ninja_log_dir:
return []
trace_files = []
# Look for .json files adjacent to object files
obj_path = Path(self.ninja_log_dir) / target.output_name
json_path = obj_path.with_suffix(".json")
if json_path.exists():
trace_files.append(str(json_path))
return trace_files
def generate_ftime_events(self, target: BuildTarget, tid: int) -> Iterator[Dict]:
"""Generate Clang -ftime-trace events for a target."""
if not self.embed_ftime_traces or not self.ftime_reader:
return
trace_files = self.find_ftime_trace_files(target)
for trace_file in trace_files:
trace_data = self.ftime_reader.read_trace_file(trace_file)
if not trace_data:
continue
filtered_events = self.ftime_reader.filter_events(trace_data)
for event in filtered_events:
adjusted_event = self.ftime_reader.adjust_event_timing(
event, target, self.process_id, tid
)
if adjusted_event:
yield adjusted_event
def generate_trace_events(self, targets: List[BuildTarget]) -> List[Dict]:
"""Generate Chrome trace events from build targets."""
events = []
for target in targets:
thread_id = self.scheduler.allocate_thread(target)
# Add main ninja build event
if self.legacy_format:
# Legacy format: join multiple targets with commas, use "targets" category, empty args
target_name = (
", ".join(target.targets)
if len(target.targets) > 1
else target.output_name
)
ninja_event = {
"name": target_name,
"cat": "targets",
"ph": "X", # Complete event
"ts": target.start_time * 1000, # Convert to microseconds
"dur": target.duration * 1000, # Convert to microseconds
"pid": self.process_id,
"tid": thread_id,
"args": {},
}
else:
# New format: smart categorization, detailed args
ninja_event = {
"name": target.output_name,
"cat": target.category,
"ph": "X", # Complete event
"ts": target.start_time * 1000, # Convert to microseconds
"dur": target.duration * 1000, # Convert to microseconds
"pid": self.process_id,
"tid": thread_id,
"args": {
"output": target.output_name,
"duration_ms": target.duration,
"cmd_hash": target.cmd_hash,
},
}
events.append(ninja_event)
# Add embedded Clang -ftime-trace events
if self.embed_ftime_traces:
ftime_events = list(self.generate_ftime_events(target, thread_id))
events.extend(ftime_events)
if ftime_events:
print(
f"Embedded {len(ftime_events)} -ftime-trace events for {target.output_name}",
file=sys.stderr,
)
return events
class BuildAnalyzer:
"""Analyzes build performance and provides statistics."""
def __init__(self, targets: List[BuildTarget]):
self.targets = targets
def get_build_summary(self) -> Dict:
"""Generate build performance summary."""
if not self.targets:
return {}
total_duration = sum(t.duration for t in self.targets)
total_targets = len(self.targets)
# Category statistics
category_stats = {}
for target in self.targets:
cat = target.category
if cat not in category_stats:
category_stats[cat] = {"count": 0, "total_time": 0}
category_stats[cat]["count"] += 1
category_stats[cat]["total_time"] += target.duration
# Top slowest targets
slowest_targets = sorted(self.targets, key=lambda t: t.duration, reverse=True)[
:10
]
return {
"total_targets": total_targets,
"total_duration_ms": total_duration,
"total_duration_sec": total_duration / 1000,
"average_duration_ms": total_duration / total_targets
if total_targets > 0
else 0,
"category_stats": category_stats,
"slowest_targets": [
{
"name": t.output_name,
"duration_ms": t.duration,
"category": t.category,
}
for t in slowest_targets
],
}
def print_summary(self):
"""Print build summary to stderr."""
summary = self.get_build_summary()
if not summary:
print("No build data available", file=sys.stderr)
return
print("\n=== Build Summary ===", file=sys.stderr)
print(f"Total targets: {summary['total_targets']}", file=sys.stderr)
print(f"Total time: {summary['total_duration_sec']:.2f}s", file=sys.stderr)
print(
f"Average time per target: {summary['average_duration_ms']:.2f}ms",
file=sys.stderr,
)
print("\nBy category:", file=sys.stderr)
for category, stats in summary["category_stats"].items():
avg_time = stats["total_time"] / stats["count"] if stats["count"] > 0 else 0
print(
f" {category:15} {stats['count']:6} targets "
f"{stats['total_time'] / 1000:8.2f}s "
f"(avg: {avg_time / 1000:.3f}s)",
file=sys.stderr,
)
print("\nSlowest targets:", file=sys.stderr)
for i, target in enumerate(summary["slowest_targets"][:5], 1):
print(
f" {i:2}. {target['name']} ({target['duration_ms']}ms, {target['category']})",
file=sys.stderr,
)
def create_argument_parser() -> argparse.ArgumentParser:
"""Create command line argument parser."""
parser = argparse.ArgumentParser(
description="Convert ninja build logs to Chrome tracing format",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
%(prog)s build/.ninja_log # Basic usage
%(prog)s build/.ninja_log --output trace.json # Save to file
%(prog)s build/.ninja_log --summary # Show build summary
%(prog)s build/.ninja_log --show-all # Include all builds
%(prog)s build/.ninja_log --embed-ftime-trace # Include Clang timing data
%(prog)s build/.ninja_log --granularity 10000 # Custom granularity threshold
""",
)
parser.add_argument(
"ninja_logs",
nargs="+", # Accept one or more ninja log files
help="Path(s) to the .ninja_log file(s)",
)
parser.add_argument("-o", "--output", help="Output file (default: stdout)")
parser.add_argument(
"--show-all", action="store_true", help="Show all builds, not just the last one"
)
parser.add_argument(
"--summary", action="store_true", help="Print build summary to stderr"
)
parser.add_argument(
"--pretty", action="store_true", help="Pretty-print JSON output"
)
parser.add_argument(
"--embed-ftime-trace",
action="store_true",
help="Embed Clang -ftime-trace JSON files found adjacent to targets",
)
parser.add_argument(
"--granularity",
type=int,
default=50000,
help="Minimum duration for -ftime-trace events in microseconds (default: 50000)",
)
parser.add_argument(
"--legacy-format",
action="store_true",
help='Output in legacy format compatible with old ninjatracer (simple JSON array, all categories as "targets", empty args)',
)
return parser
def main():
"""Main entry point."""
parser = create_argument_parser()
args = parser.parse_args()
try:
# Process multiple ninja log files
all_events = []
for pid, ninja_log_path in enumerate(args.ninja_logs):
# Parse ninja log
log_parser = NinjaLogParser(show_all_builds=args.show_all)
targets = log_parser.parse_log_file(ninja_log_path)
if not targets:
print(
f"No build targets found in ninja log: {ninja_log_path}",
file=sys.stderr,
)
continue
# Determine ninja log directory for -ftime-trace files
ninja_log_dir = (
os.path.dirname(os.path.abspath(ninja_log_path))
if args.embed_ftime_trace
else None
)
# Generate trace events for this log file
trace_generator = ChromeTraceGenerator(
process_id=pid, # Use different PID for each log file
embed_ftime_traces=args.embed_ftime_trace,
granularity_us=args.granularity,
ninja_log_dir=ninja_log_dir,
legacy_format=args.legacy_format,
)
events = trace_generator.generate_trace_events(targets)
all_events.extend(events)
# Print summary if requested (for each log file)
if args.summary:
print(f"\n=== Summary for {ninja_log_path} ===", file=sys.stderr)
analyzer = BuildAnalyzer(targets)
analyzer.print_summary()
if not all_events:
print("No build targets found in any ninja log files", file=sys.stderr)
return 1
# Output format logic
if args.legacy_format:
# Legacy format: always output simple JSON array
json_kwargs = {"indent": 2} if args.pretty else {}
json_output = json.dumps(all_events, **json_kwargs)
elif args.output or args.pretty:
# Enhanced format with metadata (when saving to file or pretty printing)
trace_data = {
"traceEvents": all_events,
"displayTimeUnit": "ms",
"systemTraceEvents": "SystemTraceData",
"otherData": {"version": "1.0", "generator": "ninja_json_converter.py"},
}
json_kwargs = {"indent": 2} if args.pretty else {}
json_output = json.dumps(trace_data, **json_kwargs)
else:
# Original format (simple JSON array to stdout)
json_output = json.dumps(all_events)
if args.output:
with open(args.output, "w") as f:
f.write(json_output)
print(f"Trace written to {args.output}", file=sys.stderr)
else:
print(json_output)
return 0
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
return 1
if __name__ == "__main__":
sys.exit(main())

43
script/parse_ninja_trace.py Executable file
View File

@@ -0,0 +1,43 @@
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
import json
import os
import sys
def read_json_file(file_path):
if not os.path.isfile(file_path):
raise FileNotFoundError(f"File not found: {file_path}")
with open(file_path, "r", encoding="utf-8") as file:
try:
data = json.load(file)
except json.JSONDecodeError as e:
raise json.JSONDecodeError(f"Invalid JSON format: {e}", e.doc, e.pos)
return data
if __name__ == "__main__":
if len(sys.argv) != 2:
print("Usage: python parse_json.py <path_to_json_file>")
sys.exit(1)
json_file_path = sys.argv[1]
try:
parsed_data = read_json_file(json_file_path)
print("JSON parsed successfully!")
threshold = 15 # max number of minutes for compilation
for i in range(len(parsed_data)):
if parsed_data[i]["dur"] > threshold * 60000000:
print(
f"build duration of {parsed_data[i]['name']} exceeds {threshold} minutes! actual build time: {parsed_data[i]['dur'] / 60000000:.2f} minutes!"
)
except FileNotFoundError as fnf_err:
print(f"Error: {fnf_err}")
except json.JSONDecodeError as json_err:
print(f"Error: {json_err}")
except Exception as e:
print(f"Unexpected error: {e}")

488
script/process_perf_data.py Normal file
View File

@@ -0,0 +1,488 @@
#!/usr/bin/env python3
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
import os
import io
import argparse
import datetime
# import numpy as np
import sqlalchemy
from sqlalchemy import text
import pandas as pd
from sshtunnel import SSHTunnelForwarder
def print_to_string(*args, **kwargs):
output = io.StringIO()
print(*args, file=output, **kwargs)
contents = output.getvalue()
output.close()
return contents
def parse_args():
parser = argparse.ArgumentParser(description="Parse results from tf benchmark runs")
parser.add_argument(
"filename", type=str, help="Log file to prase or directory containing log files"
)
args = parser.parse_args()
files = []
if os.path.isdir(args.filename):
all_files = os.listdir(args.filename)
for name in all_files:
if "log" not in name:
continue
files.append(os.path.join(args.filename, name))
else:
files = [args.filename]
args.files = files
return args
def get_log_params(logfile):
print("logfile=", logfile)
branch_name = " "
node_id = " "
gpu_arch = " "
hip_vers = " "
compute_units = 0
environment = " "
rocm_vers = " "
for line in open(logfile):
if "Branch name" in line:
lst = line.split()
branch_name = lst[2]
if "On branch" in line:
lst = line.split()
branch_name = lst[2]
if "Node name" in line:
lst = line.split()
node_id = lst[2]
if "GPU_arch" in line:
lst = line.split()
gpu_arch = lst[2]
if "HIP version" in line:
lst = line.split()
hip_vers = lst[2]
if "Compute Unit" in line:
lst = line.split()
compute_units = lst[2]
if "Environment type" in line:
lst = line.split()
environment = lst[2]
if "InstalledDir" in line:
lst = line.split()
rocm_vers = lst[1][
lst[1].find("/opt/rocm-") + len("/opt/rocm-") : lst[1].rfind(
"/llvm/bin"
)
]
return (
branch_name,
node_id,
gpu_arch,
compute_units,
rocm_vers,
hip_vers,
environment,
)
def parse_logfile(logfile):
glue = ""
res = []
tests = []
kernels = []
tflops = []
dtype = []
alayout = []
blayout = []
M = []
N = []
K = []
StrideA = []
StrideB = []
StrideC = []
if "perf_gemm" in logfile and "gemm_bilinear" not in logfile:
for line in open(logfile):
if "Best Perf" in line:
lst = line.split()
if len(lst) >= 37: # the line is complete
tests.append(glue.join(lst[5:30]))
kernels.append(glue.join(lst[37:]))
tflops.append(lst[33])
dtype.append(lst[5])
alayout.append(lst[8])
blayout.append(lst[11])
M.append(lst[14])
N.append(lst[17])
K.append(lst[20])
StrideA.append(lst[23])
StrideB.append(lst[26])
StrideC.append(lst[29])
elif len(lst) < 37 and len(lst) >= 33: # the tflops are available
tests.append(glue.join(lst[5:30]))
kernels.append("N/A")
tflops.append(lst[33])
dtype.append(lst[5])
alayout.append(lst[8])
blayout.append(lst[11])
M.append(lst[14])
N.append(lst[17])
K.append(lst[20])
StrideA.append(lst[23])
StrideB.append(lst[26])
StrideC.append(lst[29])
print("warning: incomplete line:", lst)
elif len(lst) < 33: # even the tflops are not available
print("Error in ckProfiler output!")
print("warning: incomplete line=", lst)
# sort results
# sorted_tests = sorted(tests)
res = [x for _, x in sorted(zip(tests, tflops))]
# sorted_kernels = [x for _,x in sorted(zip(tests,kernels))]
# test_list = list(range(1, len(tests) + 1))
# parse conv_fwd and conv_bwd performance tests:
elif "conv_fwd" in logfile or "conv_bwd" in logfile:
for line in open(logfile):
if "tflops:" in line:
lst = line.split()
res.append(lst[1])
# parse all other performance tests:
elif (
"resnet50" in logfile
or "batched_gemm" in logfile
or "grouped_gemm" in logfile
or "gemm_bilinear" in logfile
or "reduction" in logfile
):
for line in open(logfile):
if "Best Perf" in line:
lst = line.split()
res.append(lst[4])
elif "onnx_gemm" in logfile:
for line in open(logfile):
if "Best Perf" in line:
lst = line.split()
res.append(lst[33])
elif "splitK_gemm" in logfile or "mixed_gemm" in logfile:
for line in open(logfile):
if "Best Perf" in line:
lst = line.split()
res.append(lst[36])
elif "perf_fmha" in logfile:
for line in open(logfile):
if "TFlops" in line:
lst = line.split()
line_dict = dict(zip(lst[1:], lst))
res.append(line_dict["TFlops,"])
elif "perf_tile_gemm_basic" in logfile or "perf_tile_gemm_mem_pipeline" in logfile:
for line in open(logfile):
if "TFlops" in line:
lst = line.split()
line_dict = dict(zip(lst[1:], lst))
res.append(line_dict["TFlops,"])
return res
def get_baseline(table, connection):
query = text(
"""SELECT * from """
+ table
+ """ WHERE Datetime = (SELECT MAX(Datetime) FROM """
+ table
+ """ where Branch_ID='develop' );"""
)
return pd.read_sql(query, connection)
def store_new_test_result(
table_name,
test_results,
testlist,
branch_name,
node_id,
gpu_arch,
compute_units,
rocm_vers,
hip_vers,
environment,
connection,
):
params = [
str(branch_name),
str(node_id),
str(gpu_arch),
compute_units,
str(rocm_vers),
str(hip_vers),
str(environment),
str(datetime.datetime.now()),
]
df = pd.DataFrame(
data=[params],
columns=[
"Branch_ID",
"Node_ID",
"GPU_arch",
"Compute Units",
"ROCM_version",
"HIP_version",
"Environment",
"Datetime",
],
)
df_add = pd.DataFrame(data=[test_results], columns=testlist)
df = pd.concat([df, df_add], axis=1)
# print("new test results dataframe:",df)
df.to_sql(table_name, connection, if_exists="append", index=False)
return 0
def compare_test_to_baseline(baseline, test, testlist):
regression = 0
if not baseline.empty:
base = baseline[testlist].to_numpy(dtype="float")
base_list = base[0]
ave_perf = 0
for i in range(len(base_list)):
# success criterion:
if base_list[i] > 1.01 * float(test[i]):
print(
"test # ",
i,
"shows regression by {:.3f}%".format(
(float(test[i]) - base_list[i]) / base_list[i] * 100
),
)
regression = 1
if base_list[i] > 0:
ave_perf = ave_perf + float(test[i]) / base_list[i]
if regression == 0:
print("no regressions found")
ave_perf = ave_perf / len(base_list)
print("average performance relative to baseline:", ave_perf)
else:
print("could not find a baseline")
return regression
"""
def post_test_params(tlist,connection):
sorted_dtypes = [x for _,x in sorted(zip(tests,dtype))]
sorted_alayout = [x for _,x in sorted(zip(tests,alayout))]
sorted_blayout = [x for _,x in sorted(zip(tests,blayout))]
sorted_M = [x for _,x in sorted(zip(tests,M))]
sorted_N = [x for _,x in sorted(zip(tests,N))]
sorted_K = [x for _,x in sorted(zip(tests,K))]
sorted_StrideA = [x for _,x in sorted(zip(tests,StrideA))]
sorted_StrideB = [x for _,x in sorted(zip(tests,StrideB))]
sorted_StrideC = [x for _,x in sorted(zip(tests,StrideC))]
ck_gemm_params=[tlist,sorted_dtypes,sorted_alayout,sorted_blayout,
sorted_M,sorted_N,sorted_K,sorted_StrideA,sorted_StrideB,
sorted_StrideC]
df=pd.DataFrame(np.transpose(ck_gemm_params),columns=['Test_number','Data_type',
'Alayout','BLayout','M','N','K', 'StrideA','StrideB','StrideC'])
print(df)
dtypes = {
'Test_number': Integer(),
'Data_type': NVARCHAR(length=5),
'Alayout': NVARCHAR(length=12),
'Blayout': NVARCHAR(length=12),
'M': Integer(),
'N': Integer(),
'K': Integer(),
'StrideA': Integer(),
'StrideB': Integer(),
'StrideC': Integer()
}
df.to_sql("ck_gemm_test_params",connection,if_exists='replace',index=False, dtype=dtypes)
"""
def main():
args = parse_args()
results = []
tflops_base = []
testlist = []
# parse the test parameters from the logfile
for filename in args.files:
(
branch_name,
node_id,
gpu_arch,
compute_units,
rocm_vers,
hip_vers,
environment,
) = get_log_params(filename)
print("Branch name:", branch_name)
print("Node name:", node_id)
print("GPU_arch:", gpu_arch)
print("Compute units:", compute_units)
print("ROCM_version:", rocm_vers)
print("HIP_version:", hip_vers)
print("Environment:", environment)
# parse results, get the Tflops value for "Best Perf" kernels
results = parse_logfile(filename)
print("Number of tests:", len(results))
sql_hostname = "127.0.0.1"
sql_username = os.environ["dbuser"]
sql_password = os.environ["dbpassword"]
sql_main_database = os.environ["ck_perf_db"]
sql_port = 3306
ssh_host = os.environ["dbsship"]
ssh_user = os.environ["dbsshuser"]
ssh_port = int(os.environ["dbsshport"])
ssh_pass = os.environ["dbsshpassword"]
with SSHTunnelForwarder(
(ssh_host, ssh_port),
ssh_username=ssh_user,
ssh_password=ssh_pass,
remote_bind_address=(sql_hostname, sql_port),
) as tunnel:
sqlEngine = sqlalchemy.create_engine(
"mysql+pymysql://{0}:{1}@{2}:{3}/{4}".format(
sql_username,
sql_password,
sql_hostname,
tunnel.local_bind_port,
sql_main_database,
)
)
conn = sqlEngine.connect()
# save gemm performance tests:
if "perf_gemm" in filename and "gemm_bilinear" not in filename:
# write the ck_gemm_test_params table only needed once the test set changes
# post_test_params(test_list,conn)
for i in range(1, len(results) + 1):
testlist.append("Test%i" % i)
table_name = "ck_gemm_tflops"
if "batched_gemm" in filename:
for i in range(1, len(results) + 1):
testlist.append("Test%i" % i)
table_name = "ck_batched_gemm_tflops"
if "grouped_gemm" in filename:
for i in range(1, len(results) + 1):
testlist.append("Test%i" % i)
table_name = "ck_grouped_gemm_tflops"
if "perf_conv_fwd" in filename:
for i in range(1, len(results) + 1):
testlist.append("Test%i" % i)
table_name = "ck_conv_fwd_tflops"
if "perf_conv_bwd_data" in filename:
for i in range(1, len(results) + 1):
testlist.append("Test%i" % i)
table_name = "ck_conv_bwd_data_tflops"
if "grouped_conv_fwd" in filename:
for i in range(1, len(results) + 1):
testlist.append("Test%i" % i)
table_name = "ck_grouped_conv_fwd_tflops"
if "grouped_conv_bwd_data" in filename:
for i in range(1, len(results) + 1):
testlist.append("Test%i" % i)
table_name = "ck_grouped_conv_bwd_data_tflops"
if "grouped_conv_bwd_weight" in filename:
for i in range(1, len(results) + 1):
testlist.append("Test%i" % i)
table_name = "ck_grouped_conv_bwd_weight_tflops"
if "gemm_bilinear" in filename:
for i in range(1, len(results) + 1):
testlist.append("Test%i" % i)
table_name = "ck_gemm_bilinear_tflops"
if "reduction" in filename:
for i in range(1, len(results) + 1):
testlist.append("Test%i" % i)
table_name = "ck_reduction_GBps"
if "resnet50_N4" in filename:
for i in range(1, 50):
testlist.append("Layer%i" % i)
table_name = "ck_resnet50_N4_tflops"
if "resnet50_N256" in filename:
for i in range(1, 50):
testlist.append("Layer%i" % i)
table_name = "ck_resnet50_N256_tflops"
if "onnx_gemm" in filename:
for i in range(1, len(results) + 1):
testlist.append("Test%i" % i)
table_name = "ck_onnx_gemm_tflops"
if "splitK_gemm" in filename:
for i in range(1, len(results) + 1):
testlist.append("Test%i" % i)
table_name = "ck_splitK_gemm_tflops"
if "mixed_gemm" in filename:
for i in range(1, len(results) + 1):
testlist.append("Test%i" % i)
table_name = "ck_mixed_gemm_tflops"
if "fmha_fwd" in filename:
for i in range(1, len(results) + 1):
testlist.append("Test%i" % i)
table_name = "ck_fmha_fwd_tflops"
if "fmha_bwd" in filename:
for i in range(1, len(results) + 1):
testlist.append("Test%i" % i)
table_name = "ck_fmha_bwd_tflops"
if "gemm_basic_fp16" in filename:
for i in range(1, len(results) + 1):
testlist.append("Test%i" % i)
table_name = "ck_tile_gemm_basic_fp16_tflops"
if "gemm_mem_pipeline_fp16" in filename:
for i in range(1, len(results) + 1):
testlist.append("Test%i" % i)
table_name = "ck_tile_gemm_mem_pipeline_fp16_tflops"
if "gemm_basic_bf16" in filename:
for i in range(1, len(results) + 1):
testlist.append("Test%i" % i)
table_name = "ck_tile_gemm_basic_bf16_tflops"
if "gemm_mem_pipeline_bf16" in filename:
for i in range(1, len(results) + 1):
testlist.append("Test%i" % i)
table_name = "ck_tile_gemm_mem_pipeline_bf16_tflops"
if "gemm_basic_fp8" in filename:
for i in range(1, len(results) + 1):
testlist.append("Test%i" % i)
table_name = "ck_tile_gemm_basic_fp8_tflops"
if "gemm_mem_pipeline_fp8" in filename:
for i in range(1, len(results) + 1):
testlist.append("Test%i" % i)
table_name = "ck_tile_gemm_mem_pipeline_fp8_tflops"
if "gemm_basic_bf8" in filename:
for i in range(1, len(results) + 1):
testlist.append("Test%i" % i)
table_name = "ck_tile_gemm_basic_bf8_tflops"
if "gemm_mem_pipeline_bf8" in filename:
for i in range(1, len(results) + 1):
testlist.append("Test%i" % i)
table_name = "ck_tile_gemm_mem_pipeline_bf8_tflops"
tflops_base = get_baseline(table_name, conn)
store_new_test_result(
table_name,
results,
testlist,
branch_name,
node_id,
gpu_arch,
compute_units,
rocm_vers,
hip_vers,
environment,
sqlEngine,
)
conn.close()
# compare the results to the baseline if baseline exists
regression = 0
regression = compare_test_to_baseline(tflops_base, results, testlist)
return regression
if __name__ == "__main__":
main()

86
script/process_perf_data.sh Executable file
View File

@@ -0,0 +1,86 @@
#!/bin/bash
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
#
# in order to run this script you'd need the following python packages:
#pip3 install --upgrade pip
#pip3 install sqlalchemy pymysql pandas sshtunnel
# you would also need to set up some environment variables in order to
# post your new test results to the database and compare them to the baseline
# please contact Illia.Silin@amd.com for more details
#process results
file=./perf_gemm_gfx90a.log
if [ -e "$file" ]; then
python3 process_perf_data.py perf_gemm_gfx90a.log
fi
file=./perf_onnx_gemm_gfx90a.log
if [ -e "$file" ]; then
python3 process_perf_data.py perf_onnx_gemm_gfx90a.log
fi
file=./perf_resnet50_N256_gfx90a.log
if [ -e "$file" ]; then
python3 process_perf_data.py perf_resnet50_N256_gfx90a.log
fi
file=./perf_resnet50_N4_gfx90a.log
if [ -e "$file" ]; then
python3 process_perf_data.py perf_resnet50_N4_gfx90a.log
fi
file=./perf_gemm_gfx942.log
if [ -e "$file" ]; then
python3 process_perf_data.py perf_gemm_gfx942.log
fi
file=./perf_onnx_gemm_gfx942.log
if [ -e "$file" ]; then
python3 process_perf_data.py perf_onnx_gemm_gfx942.log
fi
file=./perf_resnet50_N256_gfx942.log
if [ -e "$file" ]; then
python3 process_perf_data.py perf_resnet50_N256_gfx942.log
fi
file=./perf_resnet50_N4_gfx942.log
if [ -e "$file" ]; then
python3 process_perf_data.py perf_resnet50_N4_gfx942.log
fi
file=./perf_onnx_gemm_gfx10.log
if [ -e "$file" ]; then
python3 process_perf_data.py perf_onnx_gemm_gfx10.log
fi
file=./perf_onnx_gemm_gfx11.log
if [ -e "$file" ]; then
python3 process_perf_data.py perf_onnx_gemm_gfx11.log
fi
file=./perf_onnx_gemm_gfx12.log
if [ -e "$file" ]; then
python3 process_perf_data.py perf_onnx_gemm_gfx12.log
fi
file=./perf_fmha_fwd_gfx942.log
if [ -e "$file" ]; then
python3 process_perf_data.py perf_fmha_fwd_gfx942.log
fi
file=./perf_fmha_bwd_gfx942.log
if [ -e "$file" ]; then
python3 process_perf_data.py perf_fmha_bwd_gfx942.log
fi
file=./perf_fmha_fwd_gfx90a.log
if [ -e "$file" ]; then
python3 process_perf_data.py perf_fmha_fwd_gfx90a.log
fi
file=./perf_fmha_bwd_gfx90a.log
if [ -e "$file" ]; then
python3 process_perf_data.py perf_fmha_bwd_gfx90a.log
fi
for gpu in "gfx90a" "gfx942"; do
for dtype in "fp16" "bf16" "fp8" "bf8"; do
file=./perf_tile_gemm_mem_pipeline_${dtype}_${gpu}.log
if [ -e "$file" ]; then
python3 process_perf_data.py perf_tile_gemm_mem_pipeline_${dtype}_${gpu}.log
fi
done
done

66
script/process_qa_data.sh Executable file
View File

@@ -0,0 +1,66 @@
#!/bin/bash
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
#
# in order to run this script you'd need the following python packages:
#pip3 install --upgrade pip
#pip3 install sqlalchemy pymysql pandas sshtunnel
# you would also need to set up some environment variables in order to
# post your new test results to the database and compare them to the baseline
# please contact Illia.Silin@amd.com for more details
#process results
python3 process_perf_data.py perf_gemm.log
python3 process_perf_data.py perf_resnet50_N256.log
python3 process_perf_data.py perf_resnet50_N4.log
python3 process_perf_data.py perf_batched_gemm.log
python3 process_perf_data.py perf_grouped_gemm.log
python3 process_perf_data.py perf_grouped_conv_fwd.log
python3 process_perf_data.py perf_grouped_conv_bwd_data.log
python3 process_perf_data.py perf_grouped_conv_bwd_weight.log
python3 process_perf_data.py perf_gemm_bilinear.log
python3 process_perf_data.py perf_reduction.log
python3 process_perf_data.py perf_splitK_gemm.log
python3 process_perf_data.py perf_onnx_gemm.log
python3 process_perf_data.py perf_mixed_gemm.log
file=./perf_onnx_gemm_gfx10.log
if [ -e "$file" ]; then
python3 process_perf_data.py perf_onnx_gemm_gfx10.log
fi
file=./perf_onnx_gemm_gfx11.log
if [ -e "$file" ]; then
python3 process_perf_data.py perf_onnx_gemm_gfx11.log
fi
file=./perf_onnx_gemm_gfx12.log
if [ -e "$file" ]; then
python3 process_perf_data.py perf_onnx_gemm_gfx12.log
fi
file=./perf_fmha_fwd_gfx942.log
if [ -e "$file" ]; then
python3 process_perf_data.py perf_fmha_fwd_gfx942.log
fi
file=./perf_fmha_bwd_gfx942.log
if [ -e "$file" ]; then
python3 process_perf_data.py perf_fmha_bwd_gfx942.log
fi
file=./perf_fmha_fwd_gfx90a.log
if [ -e "$file" ]; then
python3 process_perf_data.py perf_fmha_fwd_gfx90a.log
fi
file=./perf_fmha_bwd_gfx90a.log
if [ -e "$file" ]; then
python3 process_perf_data.py perf_fmha_bwd_gfx90a.log
fi
for gpu in "gfx90a" "gfx942"; do
for dtype in "fp16" "bf16" "fp8" "bf8"; do
file=./perf_tile_gemm_mem_pipeline_${dtype}_${gpu}.log
if [ -e "$file" ]; then
python3 process_perf_data.py perf_tile_gemm_mem_pipeline_${dtype}_${gpu}.log
fi
done
done

40
script/profile_batched_gemm.sh Executable file
View File

@@ -0,0 +1,40 @@
#!/bin/bash
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
## GPU visibility
export HIP_VISIBLE_DEVICES=0
DRIVER="../build/bin/ckProfiler"
OP=$1
DATATYPE=$2
LAYOUT=$3
VERIFY=$4
INIT=$5
LOG=$6
TIME=$7
######## op datatype layout verify init log time M___ N___ K___ StrideA StrideB StrideC BatchStrideA BatchStrideB BatchStrideC BatchCount
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 960 1024 1024 -1 -1 -1 -1 -1 -1 8
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 1920 2048 2048 -1 -1 -1 -1 -1 -1 8
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 3840 4096 4096 -1 -1 -1 -1 -1 -1 4
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 7680 8192 8192 -1 -1 -1 -1 -1 -1 2
####### op datatype layout verify init log time M___ N___ K___ StrideA StrideB StrideC BatchStrideA BatchStrideB BatchStrideC BatchCount
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 1024 1024 1024 1024 1024 1024 -1 -1 -1 8
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2048 2048 2048 2048 2048 2048 -1 -1 -1 8
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 4096 4096 4096 4096 4096 4096 -1 -1 -1 4
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 8192 8192 8192 8192 8192 8192 -1 -1 -1 2
####### op datatype layout verify init log time M___ N___ K___ StrideA StrideB StrideC BatchStrideA BatchStrideB BatchStrideC BatchCount
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 1024 1024 1024 1056 1056 1056 -1 -1 -1 8
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2048 2048 2048 2080 2080 2080 -1 -1 -1 8
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 4096 4096 4096 4128 4128 4128 -1 -1 -1 4
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 8192 8192 8192 8224 8224 8224 -1 -1 -1 2
####### op datatype layout verify init log time M___ N___ K___ StrideA StrideB StrideC BatchStrideA BatchStrideB BatchStrideC BatchCount
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 1024 1024 1024 1088 1088 1088 -1 -1 -1 8
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2048 2048 2048 2112 2112 2112 -1 -1 -1 8
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 4096 4096 4096 4160 4160 4160 -1 -1 -1 4
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 8192 8192 8192 8256 8256 8256 -1 -1 -1 2

61
script/profile_gemm.sh Executable file
View File

@@ -0,0 +1,61 @@
#!/bin/bash
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
## GPU visibility
export HIP_VISIBLE_DEVICES=0
DRIVER="../build/bin/ckProfiler"
echo $DRIVER
OP=$1
DATATYPE=$2
LAYOUT=$3
VERIFY=$4
INIT=$5
LOG=$6
TIME=$7
# 120 CU
######## op datatype layout verify init log time M___ N___ K___ StrideA StrideB StrideC
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 960 1024 1024 -1 -1 -1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 960 2048 2048 -1 -1 -1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 1920 1024 2048 -1 -1 -1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 1920 2048 2048 -1 -1 -1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 3840 4096 4096 -1 -1 -1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 7680 8192 8192 -1 -1 -1
# 104 CU
######## op datatype layout verify init log time M___ N___ K___ StrideA StrideB StrideC
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 832 1024 1024 -1 -1 -1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 832 2048 2048 -1 -1 -1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 1664 1024 2048 -1 -1 -1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 1664 2048 2048 -1 -1 -1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 3328 4096 4096 -1 -1 -1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 6656 8192 8192 -1 -1 -1
# 110 CU
######## op datatype layout verify init log time M___ N___ K___ StrideA StrideB StrideC
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 1280 1408 1024 -1 -1 -1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 1280 2816 2048 -1 -1 -1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2560 1408 2048 -1 -1 -1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2560 2816 2048 -1 -1 -1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 5120 5632 4096 -1 -1 -1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 7040 8192 8192 -1 -1 -1
# testing different strides
######## op datatype layout verify init log time M___ N___ K___ StrideA StrideB StrideC
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 1024 1024 1024 1024 1024 1024
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2048 2048 2048 2048 2048 2048
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 4096 4096 4096 4096 4096 4096
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 8192 8192 8192 8192 8192 8192
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 1024 1024 1024 1056 1056 1056
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2048 2048 2048 2080 2080 2080
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 4096 4096 4096 4128 4128 4128
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 8192 8192 8192 8224 8224 8224
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 1024 1024 1024 1088 1088 1088
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2048 2048 2048 2112 2112 2112
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 4096 4096 4096 4160 4160 4160
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 8192 8192 8192 8256 8256 8256

44
script/profile_gemm_bilinear.sh Executable file
View File

@@ -0,0 +1,44 @@
#!/bin/bash
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
## GPU visibility
export HIP_VISIBLE_DEVICES=0
DRIVER="../build/bin/ckProfiler"
OP=$1
DATATYPE=$2
LAYOUT=$3
VERIFY=$4
INIT=$5
LOG=$6
TIME=$7
######## op datatype layout verify init log time M___ N___ K___ StrideA StrideB StrideD StrideE Alpha Beta
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 960 1024 1024 -1 -1 -1 -1 1 1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 1920 2048 2048 -1 -1 -1 -1 1 1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 3840 4096 4096 -1 -1 -1 -1 1 1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 7680 8192 8192 -1 -1 -1 -1 1 1
######## op datatype layout verify init log time M___ N___ K___ StrideA StrideB StrideD StrideE Alpha Beta
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 960 1024 1024 -1 -1 0 -1 1 1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 1920 2048 2048 -1 -1 0 -1 1 1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 3840 4096 4096 -1 -1 0 -1 1 1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 7680 8192 8192 -1 -1 0 -1 1 1
######## op datatype layout verify init log time M___ N___ K___ StrideA StrideB StrideD StrideE Alpha Beta
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 1000 1000 1000 -1 -1 0 -1 1 1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2000 2000 2000 -1 -1 0 -1 1 1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 4000 4000 4000 -1 -1 0 -1 1 1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 8000 8000 8000 -1 -1 0 -1 1 1
######## op datatype layout verify init log time M___ N___ K___ StrideA StrideB StrideD StrideE Alpha Beta
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 1024 1024 1024 1056 1056 1056 1056 1 1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2048 2048 2048 2080 2080 2080 2080 1 1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 4096 4096 4096 4128 4128 4128 4128 1 1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 8192 8192 8192 8224 8224 8224 8224 1 1
######## op datatype layout verify init log time M___ N___ K___ StrideA StrideB StrideD StrideE Alpha Beta
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 1024 1024 1024 1088 1088 1088 1088 1 1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2048 2048 2048 2112 2112 2112 2112 1 1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 4096 4096 4096 4160 4160 4160 4160 1 1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 8192 8192 8192 8256 8256 8256 8256 1 1

View File

@@ -0,0 +1,41 @@
#!/bin/bash
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
## GPU visibility
export HIP_VISIBLE_DEVICES=0
DRIVER="../build/bin/ckProfiler"
OP=$1
DATATYPE=$2
LAYOUT=$3
VERIFY=$4
INIT=$5
LOG=$6
TIME=$7
N=$8
# Resnet50
######## op datatype layout verify init log time conv_dim G__ N__ K___ C___ Y X Hi__ Wi__ Strides Dilations LeftPads RightPads
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 256 1024 1 1 14 14 1 1 1 1 0 0 0 0
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 512 1024 1 1 14 14 1 1 1 1 0 0 0 0
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 128 128 3 3 28 28 1 1 1 1 1 1 1 1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 512 128 1 1 28 28 1 1 1 1 0 0 0 0
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 128 128 3 3 56 56 2 2 1 1 1 1 1 1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 512 2048 1 1 7 7 1 1 1 1 0 0 0 0
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 1024 256 1 1 14 14 1 1 1 1 0 0 0 0
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 256 256 3 3 14 14 1 1 1 1 1 1 1 1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 256 256 3 3 28 28 2 2 1 1 1 1 1 1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 128 256 1 1 56 56 1 1 1 1 0 0 0 0
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 64 256 1 1 56 56 1 1 1 1 0 0 0 0
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 512 512 3 3 14 14 2 2 1 1 1 1 1 1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 128 512 1 1 28 28 1 1 1 1 0 0 0 0
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 256 512 1 1 28 28 1 1 1 1 0 0 0 0
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 2048 512 1 1 7 7 1 1 1 1 0 0 0 0
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 512 512 3 3 7 7 1 1 1 1 1 1 1 1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 256 64 1 1 56 56 1 1 1 1 0 0 0 0
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 64 64 1 1 56 56 1 1 1 1 0 0 0 0
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 64 64 3 3 56 56 1 1 1 1 1 1 1 1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 64 3 7 7 224 224 2 2 1 1 3 3 3 3

View File

@@ -0,0 +1,42 @@
#!/bin/bash
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
## GPU visibility
export HIP_VISIBLE_DEVICES=0
DRIVER="../build/bin/ckProfiler"
OP=$1
DATATYPE=$2
LAYOUT=$3
VERIFY=$4
INIT=$5
LOG=$6
TIME=$7
N=$8
SplitK=$9
# Resnet50
######## op datatype layout verify init log time conv_dim G__ N__ K___ C___ Y X Hi__ Wi__ Strides Dilations LeftPads RightPads
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 256 1024 1 1 14 14 1 1 1 1 0 0 0 0 $SplitK
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 512 1024 1 1 14 14 1 1 1 1 0 0 0 0 $SplitK
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 128 128 3 3 28 28 1 1 1 1 1 1 1 1 $SplitK
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 512 128 1 1 28 28 1 1 1 1 0 0 0 0 $SplitK
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 128 128 3 3 56 56 2 2 1 1 1 1 1 1 $SplitK
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 512 2048 1 1 7 7 1 1 1 1 0 0 0 0 $SplitK
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 1024 256 1 1 14 14 1 1 1 1 0 0 0 0 $SplitK
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 256 256 3 3 14 14 1 1 1 1 1 1 1 1 $SplitK
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 256 256 3 3 28 28 2 2 1 1 1 1 1 1 $SplitK
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 128 256 1 1 56 56 1 1 1 1 0 0 0 0 $SplitK
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 64 256 1 1 56 56 1 1 1 1 0 0 0 0 $SplitK
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 512 512 3 3 14 14 2 2 1 1 1 1 1 1 $SplitK
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 128 512 1 1 28 28 1 1 1 1 0 0 0 0 $SplitK
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 256 512 1 1 28 28 1 1 1 1 0 0 0 0 $SplitK
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 2048 512 1 1 7 7 1 1 1 1 0 0 0 0 $SplitK
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 512 512 3 3 7 7 1 1 1 1 1 1 1 1 $SplitK
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 256 64 1 1 56 56 1 1 1 1 0 0 0 0 $SplitK
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 64 64 1 1 56 56 1 1 1 1 0 0 0 0 $SplitK
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 64 64 3 3 56 56 1 1 1 1 1 1 1 1 $SplitK
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 64 3 7 7 224 224 2 2 1 1 3 3 3 3 $SplitK

View File

@@ -0,0 +1,42 @@
#!/bin/bash
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
## GPU visibility
export HIP_VISIBLE_DEVICES=0
DRIVER="../build/bin/ckProfiler"
OP=$1
DATATYPE=$2
LAYOUT=$3
INDEXTYPE=$4
VERIFY=$5
INIT=$6
LOG=$7
TIME=$8
N=$9
# Resnet50
######## op datatype indextype layout verify init log time conv_dim G__ N__ K___ C___ Y X Hi__ Wi__ Strides Dilations LeftPads RightPads
$DRIVER $OP $DATATYPE $INDEXTYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 256 1024 1 1 14 14 1 1 1 1 0 0 0 0
$DRIVER $OP $DATATYPE $INDEXTYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 512 1024 1 1 14 14 1 1 1 1 0 0 0 0
$DRIVER $OP $DATATYPE $INDEXTYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 128 128 3 3 28 28 1 1 1 1 1 1 1 1
$DRIVER $OP $DATATYPE $INDEXTYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 512 128 1 1 28 28 1 1 1 1 0 0 0 0
$DRIVER $OP $DATATYPE $INDEXTYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 128 128 3 3 56 56 2 2 1 1 1 1 1 1
$DRIVER $OP $DATATYPE $INDEXTYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 512 2048 1 1 7 7 1 1 1 1 0 0 0 0
$DRIVER $OP $DATATYPE $INDEXTYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 1024 256 1 1 14 14 1 1 1 1 0 0 0 0
$DRIVER $OP $DATATYPE $INDEXTYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 256 256 3 3 14 14 1 1 1 1 1 1 1 1
$DRIVER $OP $DATATYPE $INDEXTYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 256 256 3 3 28 28 2 2 1 1 1 1 1 1
$DRIVER $OP $DATATYPE $INDEXTYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 128 256 1 1 56 56 1 1 1 1 0 0 0 0
$DRIVER $OP $DATATYPE $INDEXTYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 64 256 1 1 56 56 1 1 1 1 0 0 0 0
$DRIVER $OP $DATATYPE $INDEXTYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 512 512 3 3 14 14 2 2 1 1 1 1 1 1
$DRIVER $OP $DATATYPE $INDEXTYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 128 512 1 1 28 28 1 1 1 1 0 0 0 0
$DRIVER $OP $DATATYPE $INDEXTYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 256 512 1 1 28 28 1 1 1 1 0 0 0 0
$DRIVER $OP $DATATYPE $INDEXTYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 2048 512 1 1 7 7 1 1 1 1 0 0 0 0
$DRIVER $OP $DATATYPE $INDEXTYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 512 512 3 3 7 7 1 1 1 1 1 1 1 1
$DRIVER $OP $DATATYPE $INDEXTYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 256 64 1 1 56 56 1 1 1 1 0 0 0 0
$DRIVER $OP $DATATYPE $INDEXTYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 64 64 1 1 56 56 1 1 1 1 0 0 0 0
$DRIVER $OP $DATATYPE $INDEXTYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 64 64 3 3 56 56 1 1 1 1 1 1 1 1
$DRIVER $OP $DATATYPE $INDEXTYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2 1 $N 64 3 7 7 224 224 2 2 1 1 3 3 3 3

View File

@@ -0,0 +1,23 @@
#!/bin/bash
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
## GPU visibility
export HIP_VISIBLE_DEVICES=0
DRIVER="../build/bin/ckProfiler"
OP=$1
DATATYPE=$2
OUTELEMENTOP=$3
LAYOUT=$4
VERIFY=$5
INIT=$6
LOG=$7
TIME=$8
N=$9
####### op datatype OUTELEMENTOP layout verify init log time Ndims G N K C Z Y X Di Hi Wi Sz Sy Sx Dz Dy Dx Left Pz LeftPy LeftPx RightPz RightPy RightPx
$DRIVER $OP $DATATYPE $OUTELEMENTOP $LAYOUT $VERIFY $INIT $LOG $TIME 3 32 $N 96 96 3 3 3 28 28 28 1 1 1 1 1 1 1 1 1 1 1 1
$DRIVER $OP $DATATYPE $OUTELEMENTOP $LAYOUT $VERIFY $INIT $LOG $TIME 3 32 $N 192 192 3 3 3 28 28 28 1 1 1 1 1 1 1 1 1 1 1 1

21
script/profile_grouped_gemm.sh Executable file
View File

@@ -0,0 +1,21 @@
#!/bin/bash
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
## GPU visibility
export HIP_VISIBLE_DEVICES=0
DRIVER="../build/bin/ckProfiler"
OP=$1
DATATYPE=$2
LAYOUT=$3
VERIFY=$4
INIT=$5
LOG=$6
TIME=$7
######## op datatype layout verify init log time Ms______________ Ns______________ Ks_____________ StrideAs___________ StrideBs__________ StrideCs___________
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 256,512,1024,768 128,256,384,1024 128,192,256,512 1024,1025,1044,1026 1024,1024,1024,1024 1025,1024,1028,1024
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 512,768,2048,128 128,256,384,1024 128,192,256,512 1024,1025,2053,1026 1024,1024,1024,1024 1025,1024,2054,1024
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 256,512,1024,768 512,256,768,1024 128,192,256,512 1024,1045,1034,1026 1024,1024,1024,1024 1025,1063,1028,1024
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 512,768,4096,768 128,768,512,2048 128,192,256,512 1024,1027,4096,2050 1024,1024,1024,2048 1025,1024,4099,2049

55
script/profile_mixed_gemm.sh Executable file
View File

@@ -0,0 +1,55 @@
#!/bin/bash
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
## GPU visibility
export HIP_VISIBLE_DEVICES=0
DRIVER="../build/bin/ckProfiler"
echo $DRIVER
OP=$1
DATATYPE=$2
LAYOUT=$3
VERIFY=$4
INIT=$5
LOG=$6
TIME=$7
KBatch=$8
######## op datatype layout verify init log time M___ N___ K___ StrideA StrideB StrideC KBatch_
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 16 16 1024 -1 -1 -1 $KBatch
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 16 16 8192 -1 -1 -1 $KBatch
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 16 16 65536 -1 -1 -1 $KBatch
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 16 2048 1024 -1 -1 -1 $KBatch
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 16 2048 8192 -1 -1 -1 $KBatch
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 16 2048 65536 -1 -1 -1 $KBatch
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 16 8192 1024 -1 -1 -1 $KBatch
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 16 8192 8192 -1 -1 -1 $KBatch
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 16 8192 65536 -1 -1 -1 $KBatch
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2048 16 1024 -1 -1 -1 $KBatch
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2048 16 8192 -1 -1 -1 $KBatch
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2048 16 65536 -1 -1 -1 $KBatch
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2048 2048 1024 -1 -1 -1 $KBatch
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2048 2048 8192 -1 -1 -1 $KBatch
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2048 2048 65536 -1 -1 -1 $KBatch
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2048 8192 1024 -1 -1 -1 $KBatch
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2048 8192 8192 -1 -1 -1 $KBatch
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2048 8192 65536 -1 -1 -1 $KBatch
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 8192 16 1024 -1 -1 -1 $KBatch
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 8192 16 8192 -1 -1 -1 $KBatch
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 8192 16 65536 -1 -1 -1 $KBatch
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 8192 2048 1024 -1 -1 -1 $KBatch
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 8192 2048 8192 -1 -1 -1 $KBatch
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 8192 2048 65536 -1 -1 -1 $KBatch
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 8192 8192 1024 -1 -1 -1 $KBatch
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 8192 8192 8192 -1 -1 -1 $KBatch
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 8192 8192 65536 -1 -1 -1 $KBatch

34
script/profile_onnx_gemm.sh Executable file
View File

@@ -0,0 +1,34 @@
#!/bin/bash
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
## GPU visibility
export HIP_VISIBLE_DEVICES=0
DRIVER="../build/bin/ckProfiler"
echo $DRIVER
OP=$1
DATATYPE=$2
LAYOUT=$3
VERIFY=$4
INIT=$5
LOG=$6
TIME=$7
# GEMM kernel benchmarks used by ONNX
######## op datatype layout verify init log time M___ N___ K___ StrideA StrideB StrideC
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 384 768 768 -1 -1 -1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 384 768 2304 -1 -1 -1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 384 768 3072 -1 -1 -1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 384 3072 768 -1 -1 -1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 384 1024 1024 -1 -1 -1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 384 1024 3072 -1 -1 -1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 384 1024 4096 -1 -1 -1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 384 4096 1024 -1 -1 -1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 24576 768 768 -1 -1 -1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 24576 768 2304 -1 -1 -1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 24576 768 3072 -1 -1 -1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 24576 3072 768 -1 -1 -1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 24576 1024 1024 -1 -1 -1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 24576 1024 3072 -1 -1 -1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 24576 1024 4096 -1 -1 -1
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 24576 4096 1024 -1 -1 -1

46
script/profile_permute_scale.sh Executable file
View File

@@ -0,0 +1,46 @@
#!/bin/bash
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
## GPU visibility
export HIP_VISIBLE_DEVICES=0
DRIVER="../build/bin/ckProfiler"
echo $DRIVER
OP=$1
DATATYPE=$2
VERIFY=$3
INIT=$4
LOG=$5
TIME=$6
# 1D
######## op datatype verify init log time dims in_strides_order out_strides_order
$DRIVER $OP $DATATYPE $VERIFY $INIT $LOG $TIME 67108864 0 0
# # 2D
# ######## op datatype verify init log time dims in_strides_order out_strides_order
$DRIVER $OP $DATATYPE $VERIFY $INIT $LOG $TIME 8192 8192 0 1 1 0
$DRIVER $OP $DATATYPE $VERIFY $INIT $LOG $TIME 8192 8192 1 0 0 1
# 3D
######## op datatype verify init log time dims in_strides_order out_strides_order
$DRIVER $OP $DATATYPE $VERIFY $INIT $LOG $TIME 8 1024 8192 0 1 2 2 1 0
$DRIVER $OP $DATATYPE $VERIFY $INIT $LOG $TIME 8 1024 8192 2 1 0 0 1 2
# 4D
######## op datatype verify init log time dims in_strides_order out_strides_order
$DRIVER $OP $DATATYPE $VERIFY $INIT $LOG $TIME 8 2 512 8192 0 1 2 3 3 2 1 0
$DRIVER $OP $DATATYPE $VERIFY $INIT $LOG $TIME 8 2 512 8192 3 2 1 0 0 1 2 3
# 5D
######## op datatype verify init log time dims in_strides_order out_strides_order
$DRIVER $OP $DATATYPE $VERIFY $INIT $LOG $TIME 8 2 2 256 8192 0 1 2 3 4 4 3 2 1 0
$DRIVER $OP $DATATYPE $VERIFY $INIT $LOG $TIME 8 2 2 256 8192 4 3 2 1 0 0 1 2 3 4
# 6D
######## op datatype verify init log time dims in_strides_order out_strides_order
$DRIVER $OP $DATATYPE $VERIFY $INIT $LOG $TIME 8 2 2 2 128 8192 0 1 2 3 4 5 5 4 3 2 1 0
$DRIVER $OP $DATATYPE $VERIFY $INIT $LOG $TIME 8 2 2 2 128 8192 5 4 3 2 1 0 0 1 2 3 4 5

View File

@@ -0,0 +1,81 @@
#!/bin/bash
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
DRIVER="../build/bin/ckProfiler"
VERIFY="-v $1"
INIT=$2
NREPEAT=$3
PRECISION=$4
##PRECISION=--half
##PRECISION=--double
##PRECISION=--int8
##PRECISION=--bf16
if [ -n $PRECISION ] && [ "$PRECISION" = "--half" -o "$PRECISION" = "--bf16" ]; then
ACCTYPE="-C 1"
elif [ -n $PRECISION ] && [ "$PRECISION" = "--int8" ]; then
ACCTYPE="-C 2"
fi
#### 0 - ADD, 5 - AVG, 7 - NORM2
Operations="0 5"
#### 0 - ADD, 5 - AVG, for int8, no NORM2 supported
if [ -n $PRECISION ] && [ "$PRECISION" = "--int8" -o "$PRECISION" = "--half" ]; then
Operations=5
fi
## for generic validation
for op in $Operations; do
set -x
####### datatype layout reduce dims op acctype verify init repeats
$DRIVER reduce $PRECISION -D 64,4,280,82 -R 0,1,2,3 -O $op $ACCTYPE $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 64,4,280,82 -R 0 -O $op $ACCTYPE $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 64,4,280,82 -R 1 -O $op $ACCTYPE $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 64,4,280,82 -R 2 -O $op $ACCTYPE $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 64,4,280,82 -R 3 -O $op $ACCTYPE $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 64,4,280,82 -R 0,1,2 -O $op $ACCTYPE $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 64,4,280,82 -R 1,2,3 -O $op $ACCTYPE $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 64,4,280,82 -R 0,2,3 -O $op $ACCTYPE $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 64,4,280,82 -R 0,1,3 -O $op $ACCTYPE $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 256,22960 -R 0 -O $op $ACCTYPE $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 256,22960 -R 1 -O $op $ACCTYPE $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 4,1469440 -R 0 -O $op $ACCTYPE $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 4,1469440 -R 1 -O $op $ACCTYPE $VERIFY $INIT $NREPEAT
set +x
done
#### 0 - ADD, 5 - AVG, 7 - NORM2
Operations=5
## for performance evaluation (resnet50 NHWC => C)
for op in $Operations; do
set -x
####### datatype layout reduce dims op acctype verify init repeats
$DRIVER reduce $PRECISION -D 256,14,14,1024 -R 0,1,2 -O $op $ACCTYPE $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 256,28,28,128 -R 0,1,2 -O $op $ACCTYPE $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 256,58,58,128 -R 0,1,2 -O $op $ACCTYPE $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 256,7,7,2048 -R 0,1,2 -O $op $ACCTYPE $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 256,14,14,256 -R 0,1,2 -O $op $ACCTYPE $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 256,30,30,256 -R 0,1,2 -O $op $ACCTYPE $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 256,56,56,256 -R 0,1,2 -O $op $ACCTYPE $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 256,16,16,512 -R 0,1,2 -O $op $ACCTYPE $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 256,28,28,512 -R 0,1,2 -O $op $ACCTYPE $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 256,7,7,512 -R 0,1,2 -O $op $ACCTYPE $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 256,56,56,64 -R 0,1,2 -O $op $ACCTYPE $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 256,230,230,3 -R 0,1,2 -O $op $ACCTYPE $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 128,14,14,1024 -R 0,1,2 -O $op $ACCTYPE $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 128,28,28,128 -R 0,1,2 -O $op $ACCTYPE $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 128,58,58,128 -R 0,1,2 -O $op $ACCTYPE $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 128,7,7,2048 -R 0,1,2 -O $op $ACCTYPE $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 128,14,14,256 -R 0,1,2 -O $op $ACCTYPE $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 128,30,30,256 -R 0,1,2 -O $op $ACCTYPE $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 128,56,56,256 -R 0,1,2 -O $op $ACCTYPE $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 128,16,16,512 -R 0,1,2 -O $op $ACCTYPE $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 128,28,28,512 -R 0,1,2 -O $op $ACCTYPE $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 128,7,7,512 -R 0,1,2 -O $op $ACCTYPE $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 128,56,56,64 -R 0,1,2 -O $op $ACCTYPE $VERIFY $INIT $NREPEAT
set +x
done

View File

@@ -0,0 +1,73 @@
#!/bin/bash
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
DRIVER="../build/bin/ckProfiler"
VERIFY="-v $1"
INIT=$2
NREPEAT=$3
PRECISION=$4
##PRECISION=--half
##PRECISION=--double
##PRECISION=--int8
##PRECISION=--bf16
#### 2 - MIN, 3 - MAX, 4 - AMAX
Operations="2 4"
## for generic validation
for op in $Operations; do
for use_idx in 0 1; do
set -x
####### datatype layout reduce dims op use index verify init repeats
$DRIVER reduce $PRECISION -D 64,4,280,82 -R 0,1,2,3 -O $op -I $use_idx $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 64,4,280,82 -R 0 -O $op -I $use_idx $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 64,4,280,82 -R 1 -O $op -I $use_idx $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 64,4,280,82 -R 2 -O $op -I $use_idx $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 64,4,280,82 -R 3 -O $op -I $use_idx $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 64,4,280,82 -R 0,1,2 -O $op -I $use_idx $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 64,4,280,82 -R 1,2,3 -O $op -I $use_idx $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 64,4,280,82 -R 0,2,3 -O $op -I $use_idx $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 64,4,280,82 -R 0,1,3 -O $op -I $use_idx $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 256,22960 -R 0 -O $op -I $use_idx $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 256,22960 -R 1 -O $op -I $use_idx $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 4,1469440 -R 0 -O $op -I $use_idx $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 4,1469440 -R 1 -O $op -I $use_idx $VERIFY $INIT $NREPEAT
set +x
done
done
Operations=2
## for performance evaluation (resnet50 NHWC => C)
for op in $Operations; do
for use_idx in 0 1; do
set -x
####### datatype layout reduce dims op use index verify init repeats
$DRIVER reduce $PRECISION -D 256,14,14,1024 -R 0,1,2 -O $op -I $use_idx $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 256,28,28,128 -R 0,1,2 -O $op -I $use_idx $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 256,58,58,128 -R 0,1,2 -O $op -I $use_idx $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 256,7,7,2048 -R 0,1,2 -O $op -I $use_idx $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 256,14,14,256 -R 0,1,2 -O $op -I $use_idx $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 256,30,30,256 -R 0,1,2 -O $op -I $use_idx $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 256,56,56,256 -R 0,1,2 -O $op -I $use_idx $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 256,16,16,512 -R 0,1,2 -O $op -I $use_idx $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 256,28,28,512 -R 0,1,2 -O $op -I $use_idx $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 256,7,7,512 -R 0,1,2 -O $op -I $use_idx $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 256,56,56,64 -R 0,1,2 -O $op -I $use_idx $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 256,230,230,3 -R 0,1,2 -O $op -I $use_idx $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 128,14,14,1024 -R 0,1,2 -O $op -I $use_idx $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 128,28,28,128 -R 0,1,2 -O $op -I $use_idx $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 128,58,58,128 -R 0,1,2 -O $op -I $use_idx $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 128,7,7,2048 -R 0,1,2 -O $op -I $use_idx $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 128,14,14,256 -R 0,1,2 -O $op -I $use_idx $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 128,30,30,256 -R 0,1,2 -O $op -I $use_idx $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 128,56,56,256 -R 0,1,2 -O $op -I $use_idx $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 128,16,16,512 -R 0,1,2 -O $op -I $use_idx $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 128,28,28,512 -R 0,1,2 -O $op -I $use_idx $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 128,7,7,512 -R 0,1,2 -O $op -I $use_idx $VERIFY $INIT $NREPEAT
$DRIVER reduce $PRECISION -D 128,56,56,64 -R 0,1,2 -O $op -I $use_idx $VERIFY $INIT $NREPEAT
set +x
done
done

72
script/profile_resnet50.sh Executable file
View File

@@ -0,0 +1,72 @@
#!/bin/bash
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
## GPU visibility
export HIP_VISIBLE_DEVICES=0
DRIVER="../build/bin/ckProfiler"
OP=$1
DATATYPE=$2
IN_LAYOUT=$3
WEI_LAYOUT=$4
OUT_LAYOUT=$5
VERIFY=$6
INIT=$7
LOG=$8
TIME=$9
N=${10}
# Resnet50
######## op____________________ datatype in_layout wei_layout out_layout verify init log time N__ K___ C___ Y X Hi__ Wi__ Strides Dilations LeftPads RightPads
$DRIVER conv_fwd_bias_relu $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 64 3 7 7 224 224 2 2 1 1 3 3 3 3
$DRIVER conv_fwd_bias_relu $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 64 64 1 1 56 56 1 1 1 1 0 0 0 0
$DRIVER conv_fwd_bias_relu $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 64 64 3 3 56 56 1 1 1 1 1 1 1 1
$DRIVER conv_fwd_bias_relu $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 256 64 1 1 56 56 1 1 1 1 0 0 0 0
$DRIVER conv_fwd_bias_relu $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 64 256 1 1 56 56 1 1 1 1 0 0 0 0
$DRIVER conv_fwd_bias_relu $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 64 64 3 3 56 56 1 1 1 1 1 1 1 1
$DRIVER conv_fwd_bias_relu_add $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 256 64 1 1 56 56 1 1 1 1 0 0 0 0
$DRIVER conv_fwd_bias_relu $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 64 256 1 1 56 56 1 1 1 1 0 0 0 0
$DRIVER conv_fwd_bias_relu $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 64 64 3 3 56 56 1 1 1 1 1 1 1 1
$DRIVER conv_fwd_bias_relu_add $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 256 64 1 1 56 56 1 1 1 1 0 0 0 0
$DRIVER conv_fwd_bias_relu $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 128 256 1 1 56 56 1 1 1 1 0 0 0 0
$DRIVER conv_fwd_bias_relu $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 128 128 3 3 56 56 2 2 1 1 1 1 1 1
$DRIVER conv_fwd_bias_relu $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 512 128 1 1 28 28 1 1 1 1 0 0 0 0
$DRIVER conv_fwd_bias_relu $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 128 512 1 1 28 28 1 1 1 1 0 0 0 0
$DRIVER conv_fwd_bias_relu $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 128 128 3 3 28 28 1 1 1 1 1 1 1 1
$DRIVER conv_fwd_bias_relu_add $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 512 128 1 1 28 28 1 1 1 1 0 0 0 0
$DRIVER conv_fwd_bias_relu $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 128 512 1 1 28 28 1 1 1 1 0 0 0 0
$DRIVER conv_fwd_bias_relu $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 128 128 3 3 28 28 1 1 1 1 1 1 1 1
$DRIVER conv_fwd_bias_relu_add $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 512 128 1 1 28 28 1 1 1 1 0 0 0 0
$DRIVER conv_fwd_bias_relu $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 128 512 1 1 28 28 1 1 1 1 0 0 0 0
$DRIVER conv_fwd_bias_relu $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 128 128 3 3 28 28 1 1 1 1 1 1 1 1
$DRIVER conv_fwd_bias_relu_add $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 512 128 1 1 28 28 1 1 1 1 0 0 0 0
$DRIVER conv_fwd_bias_relu $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 256 512 1 1 28 28 1 1 1 1 0 0 0 0
$DRIVER conv_fwd_bias_relu $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 256 256 3 3 28 28 2 2 1 1 1 1 1 1
$DRIVER conv_fwd_bias_relu $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 1024 256 1 1 14 14 1 1 1 1 0 0 0 0
$DRIVER conv_fwd_bias_relu $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 256 1024 1 1 14 14 1 1 1 1 0 0 0 0
$DRIVER conv_fwd_bias_relu $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 256 256 3 3 14 14 1 1 1 1 1 1 1 1
$DRIVER conv_fwd_bias_relu_add $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 1024 256 1 1 14 14 1 1 1 1 0 0 0 0
$DRIVER conv_fwd_bias_relu $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 256 1024 1 1 14 14 1 1 1 1 0 0 0 0
$DRIVER conv_fwd_bias_relu $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 256 256 3 3 14 14 1 1 1 1 1 1 1 1
$DRIVER conv_fwd_bias_relu_add $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 1024 256 1 1 14 14 1 1 1 1 0 0 0 0
$DRIVER conv_fwd_bias_relu $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 256 1024 1 1 14 14 1 1 1 1 0 0 0 0
$DRIVER conv_fwd_bias_relu $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 256 256 3 3 14 14 1 1 1 1 1 1 1 1
$DRIVER conv_fwd_bias_relu_add $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 1024 256 1 1 14 14 1 1 1 1 0 0 0 0
$DRIVER conv_fwd_bias_relu $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 256 1024 1 1 14 14 1 1 1 1 0 0 0 0
$DRIVER conv_fwd_bias_relu $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 256 256 3 3 14 14 1 1 1 1 1 1 1 1
$DRIVER conv_fwd_bias_relu_add $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 1024 256 1 1 14 14 1 1 1 1 0 0 0 0
$DRIVER conv_fwd_bias_relu $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 256 1024 1 1 14 14 1 1 1 1 0 0 0 0
$DRIVER conv_fwd_bias_relu $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 256 256 3 3 14 14 1 1 1 1 1 1 1 1
$DRIVER conv_fwd_bias_relu_add $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 1024 256 1 1 14 14 1 1 1 1 0 0 0 0
$DRIVER conv_fwd_bias_relu $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 512 1024 1 1 14 14 1 1 1 1 0 0 0 0
$DRIVER conv_fwd_bias_relu $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 512 512 3 3 14 14 2 2 1 1 1 1 1 1
$DRIVER conv_fwd_bias_relu $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 2048 512 1 1 7 7 1 1 1 1 0 0 0 0
$DRIVER conv_fwd_bias_relu $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 512 2048 1 1 7 7 1 1 1 1 0 0 0 0
$DRIVER conv_fwd_bias_relu $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 512 512 3 3 7 7 1 1 1 1 1 1 1 1
$DRIVER conv_fwd_bias_relu_add $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 2048 512 1 1 7 7 1 1 1 1 0 0 0 0
$DRIVER conv_fwd_bias_relu $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 512 2048 1 1 7 7 1 1 1 1 0 0 0 0
$DRIVER conv_fwd_bias_relu $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 512 512 3 3 7 7 1 1 1 1 1 1 1 1
$DRIVER conv_fwd_bias_relu_add $DATATYPE $IN_LAYOUT $WEI_LAYOUT $OUT_LAYOUT $VERIFY $INIT $LOG $TIME $N 2048 512 1 1 7 7 1 1 1 1 0 0 0 0

44
script/profile_splitK_gemm.sh Executable file
View File

@@ -0,0 +1,44 @@
#!/bin/bash
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
## GPU visibility
export HIP_VISIBLE_DEVICES=0
DRIVER="../build/bin/ckProfiler"
echo $DRIVER
OP=$1
DATATYPE=$2
LAYOUT=$3
VERIFY=$4
INIT=$5
LOG=$6
TIME=$7
KBatch=$8
# 120 CU
######## op datatype layout verify init log time M___ N___ K___ StrideA StrideB StrideC KBatch_
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 960 1024 1024 -1 -1 -1 $KBatch
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 960 2048 2048 -1 -1 -1 $KBatch
# 104 CU
######## op datatype layout verify init log time M___ N___ K___ StrideA StrideB StrideC KBatch_
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 832 1024 1024 -1 -1 -1 $KBatch
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 832 2048 2048 -1 -1 -1 $KBatch
# 110 CU
######## op datatype layout verify init log time M___ N___ K___ StrideA StrideB StrideC KBatch_
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 1280 1408 1024 -1 -1 -1 $KBatch
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 1280 2816 2048 -1 -1 -1 $KBatch
# testing different strides
######## op datatype layout verify init log time M___ N___ K___ StrideA StrideB StrideC KBatch_
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 1024 1024 1024 1024 1024 1024 $KBatch
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2048 2048 2048 2048 2048 2048 $KBatch
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 1024 1024 1024 1056 1056 1056 $KBatch
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2048 2048 2048 2080 2080 2080 $KBatch
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 1024 1024 1024 1088 1088 1088 $KBatch
$DRIVER $OP $DATATYPE $LAYOUT $VERIFY $INIT $LOG $TIME 2048 2048 2048 2112 2112 2112 $KBatch

10
script/redis-cli.conf Normal file
View File

@@ -0,0 +1,10 @@
fips = no
setuid = root
setgid = root
pid = /var/run/stunnel.pid
debug = 7
options = NO_SSLv2
options = NO_SSLv3
[redis-cli]
client = yes
accept = 127.0.0.1:6379

16
script/remod_for_ck_tile.py Executable file
View File

@@ -0,0 +1,16 @@
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
import os
root_dir = os.getcwd()
ck_tile_include = root_dir + "/projects/composablekernel/include/ck_tile"
ck_tile_example = root_dir + "/projects/composablekernel/example/ck_tile"
# Run for include
os.chdir(ck_tile_include)
_ = os.system("python remod.py")
# Run for example
os.chdir(ck_tile_example)
_ = os.system("python remod.py")

10
script/remove_exec_bit.sh Executable file
View File

@@ -0,0 +1,10 @@
#!/usr/bin/env bash
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
for file in $(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(cpp|hpp|txt|inc)$'); do
if [ -x "$file" ]; then
chmod -x "$file"
echo "[remove-exec-bit] Removed executable bit from $file" >&2
fi
done

254
script/run-tests.ps1 Normal file
View File

@@ -0,0 +1,254 @@
<#
.SYNOPSIS
Runs GTest executables and generates a markdown test report.
.DESCRIPTION
This script searches for GTest executables in a specified binary directory,
runs them, and generates a comprehensive markdown report with test results.
.PARAMETER BinaryDirectory
The directory containing the GTest executables.
.PARAMETER TestName
The name pattern of the GTest executable(s). Supports wildcards (e.g., "*test*.exe").
.PARAMETER OutputReport
Optional. The path to the output markdown report file.
Defaults to "test-report.md" in the current directory.
.PARAMETER FullTestOutput
Optional. If specified, includes the full test output for failed tests instead of just error lines.
Defaults to false (only error lines are included).
.PARAMETER ExcludeTests
Optional. Pattern to exclude specific test executables. Supports wildcards (e.g., "*large_cases*").
Test executables matching this pattern will be filtered out and not executed.
.EXAMPLE
.\run-tests.ps1 -BinaryDirectory "C:\build\bin" -TestName "test_*.exe"
.EXAMPLE
.\run-tests.ps1 -BinaryDirectory ".\build" -TestName "*test.exe" -OutputReport "test-results.md"
.EXAMPLE
.\run-tests.ps1 -BinaryDirectory ".\build" -TestName "*test.exe" -FullTestOutput
.EXAMPLE
.\run-tests.ps1 -BinaryDirectory ".\build" -TestName "*test.exe" -ExcludeTests "*large_cases*"
#>
param(
[Parameter(Mandatory=$true)]
[string]$BinaryDirectory,
[Parameter(Mandatory=$true)]
[string]$TestName,
[Parameter(Mandatory=$false)]
[string]$OutputReport = "test-report.md",
[Parameter(Mandatory=$false)]
[switch]$FullTestOutput,
[Parameter(Mandatory=$false)]
[string]$ExcludeTests = ""
)
# Validate binary directory exists
if (-not (Test-Path -Path $BinaryDirectory -PathType Container)) {
Write-Error "Binary directory does not exist: $BinaryDirectory"
exit 1
}
# Find all matching executables
$executables = Get-ChildItem -Path $BinaryDirectory -Filter $TestName -File -Recurse -ErrorAction SilentlyContinue
# Filter out excluded executables if ExcludeTests is specified
if ($ExcludeTests) {
$originalCount = $executables.Count
$executables = $executables | Where-Object { $_.Name -notlike $ExcludeTests }
$excludedCount = $originalCount - $executables.Count
if ($excludedCount -gt 0) {
Write-Host "Excluded $excludedCount executable(s) matching pattern '$ExcludeTests'"
}
}
if ($executables.Count -eq 0) {
Write-Error "No executables found matching pattern '$TestName' (after exclusions) in directory '$BinaryDirectory'"
exit 1
}
Write-Host "Found $($executables.Count) executable(s) to run"
# Initialize counters
$totalTests = 0
$totalPassed = 0
$totalFailed = 0
$failedTestDetails = @()
$executionResults = @()
# Process each executable
foreach ($exe in $executables) {
Write-Host "Running: $($exe.FullName)"
$exeResult = @{
Name = $exe.Name
Path = $exe.FullName
Tests = 0
Passed = 0
Failed = 0
Output = ""
FailedTests = @()
}
try {
# Run the GTest executable
$output = & $exe.FullName --gtest_color=no 2>&1 | Out-String
$exeResult.Output = $output
# Extract total tests run
if ($output -match '\[==========\] Running (\d+) test') {
$exeResult.Tests = [int]$matches[1]
$totalTests += $exeResult.Tests
}
# Extract passed tests
if ($output -match '\[ PASSED \] (\d+) test') {
$exeResult.Passed = [int]$matches[1]
$totalPassed += $exeResult.Passed
}
# Extract failed tests count
if ($output -match '\[ FAILED \] (\d+) test') {
$exeResult.Failed = [int]$matches[1]
$totalFailed += $exeResult.Failed
}
$failedTestPattern = '\[ FAILED \] ([^\r\n\(]+)'
$failedMatches = [regex]::Matches($output, $failedTestPattern)
foreach ($match in $failedMatches) {
if ($match.Groups[1].Value -notmatch '^\d+ test') {
$failedTestName = $match.Groups[1].Value.Trim()
$exeResult.FailedTests += $failedTestName
$parts = $failedTestName -split ", where "
$escapedName = $parts[0]
$runPattern = "\[\s+RUN\s+\]\s+$escapedName\s*[\r\n]+([\s\S]*?)\[\s+FAILED\s+\]\s+$escapedName.*"
$detailsText = ""
if ($output -match $runPattern) {
$testSection = $matches[1]
if ($FullTestOutput) {
$detailsText = $testSection.Trim()
} else {
# Extract only lines containing "error" (case-insensitive)
$errorLines = @()
$lines = $testSection -split "`r?`n"
foreach ($line in $lines) {
if ($line -match 'error') {
$errorLines += $line.Trim()
}
}
if ($errorLines.Count -gt 0) {
$detailsText = $errorLines -join "`n"
} else {
# If no error lines found, show the full section (might contain other useful info)
$detailsText = $testSection.Trim()
if ($detailsText.Length -lt 10) {
$detailsText = "Test failed without detailed error output."
}
}
}
} else {
# If pattern doesn't match, provide a helpful message
$detailsText = "Test failed without detailed error output."
}
$failedTestDetails += @{
Executable = $exe.Name
TestName = $failedTestName
Details = $detailsText
}
}
}
} catch {
Write-Warning "Error running $($exe.Name): $_"
$exeResult.Output = "Error: $_"
}
$executionResults += $exeResult
}
# Generate Markdown Report
$reportContent = @"
# GTest Execution Report
**Generated:** $(Get-Date -Format "yyyy-MM-dd HH:mm:ss")
## Summary
| Metric | Count |
|--------|-------|
| **Total Tests Executed** | $totalTests |
| **Tests Passed** | $totalPassed |
| **Tests Failed** | $totalFailed |
| **Success Rate** | $(if ($totalTests -gt 0) { [math]::Round(($totalPassed / $totalTests) * 100, 2) } else { 0 })% |
## Executable Results
"@
foreach ($result in $executionResults) {
# Use emoji/symbols via string concatenation to avoid encoding issues
$passSymbol = [char]0x2705 # ✅ white heavy check mark
$failSymbol = [char]0x274C # ❌ cross mark
$status = if ($result.Failed -eq 0) { "$passSymbol PASSED" } else { "$failSymbol FAILED" }
$reportContent += "`n`n### $($result.Name) $status`n`n"
$reportContent += "- **Tests Run:** $($result.Tests)`n"
$reportContent += "- **Passed:** $($result.Passed)`n"
$reportContent += "- **Failed:** $($result.Failed)`n"
$reportContent += "- **Path:** ``$($result.Path)```n"
}
# Add failed test details section if there are failures
if ($failedTestDetails.Count -gt 0) {
$failSymbol = [char]0x274C # ❌ cross mark
$reportContent += "`n`n---`n`n## Failed Test Details`n"
foreach ($failure in $failedTestDetails) {
$reportContent += "`n`n$failSymbol $($failure.TestName)`n`n"
$reportContent += "$($failure.Details)`n"
}
} else {
$celebrationSymbol = [char]0x1F389 # 🎉 party popper
$reportContent += "`n`n---`n`n$celebrationSymbol All Tests Passed!`n`n"
$reportContent += "No test failures detected.`n"
}
# Add footer
$reportContent += "`n"
# Write report to file
$reportContent | Out-File -FilePath $OutputReport -Encoding UTF8
Write-Host ""
Write-Host "========================================" -ForegroundColor Cyan
Write-Host "Test Execution Complete" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
Write-Host "Total Tests: $totalTests" -ForegroundColor White
Write-Host "Passed: $totalPassed" -ForegroundColor Green
Write-Host "Failed: $totalFailed" -ForegroundColor $(if ($totalFailed -gt 0) { "Red" } else { "Green" })
Write-Host "Report saved: $((Get-Item $OutputReport).FullName)" -ForegroundColor Yellow
Write-Host "========================================" -ForegroundColor Cyan
# Exit with appropriate code
if ($totalFailed -gt 0) {
exit 1
} else {
exit 0
}

View File

@@ -0,0 +1,313 @@
#!/usr/bin/env python3
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
# -*- coding: utf-8 -*-
from enum import Enum
def parse_args():
"""
Parse command-line arguments
- --shapes_csv : input csv file with M, N, K integer columns
- --best : if set, store only the result reported by the best instance.
if not set, store results from all instances
- -o : output csv file
- --build_dir : path to directory where CMake stores all the build artifacts.
The profiler binary is bin/ckProfiler relative to this directory.
- --op_name : operator name
- --layout : inputs and output layout
r ~ row-major
c ~ col-major
p ~ preshuffled for mfma
- --dtype : inputs and output dtype
"""
import argparse
parser = argparse.ArgumentParser()
parser.add_argument(
"--shapes_csv",
required=True,
help="Input csv file with M, N, K integer columns",
)
parser.add_argument(
"--best",
action="store_true",
help="If set, store only the result reported by the best instance. If not set, store results from all instances",
)
parser.add_argument("-o", default="out.csv", help="Output csv file")
parser.add_argument(
"--build_dir",
default=".",
help="Path to directory where CMake stores all the build artifacts. The profiler binary is bin/ckProfiler relative to this directory.",
)
parser.add_argument(
"--op_name",
default="gemm_multiply_multiply_weight_preshuffle",
help="Operator name",
)
parser.add_argument(
"--layout",
default="rpr",
help="Inputs and output layout. r ~ row-major, c ~ col-major, p ~ preshuffled for mfma.",
)
parser.add_argument("--dtype", default="f8f8bf16", help="Inputs and output dtype.")
return vars(parser.parse_args())
def tuples(filename):
"""
Parse M, N, K integers from the input csv file
"""
lines = []
with open(filename, "r", newline="") as f:
import csv
reader = csv.reader(f)
for line in reader:
try:
m, n, k = map(int, line)
lines.append((m, n, k))
except Exception:
pass
return lines
def parse_result(line):
"""
Parse the ckProfiler stdout line.
Result: a dict with the instance metadata and performance results
"""
words = line.split()
fields = dict()
if "Perf:" in words or "Perf" in words:
for key in ("ms", "TFlops", "GB/s"):
fields[key] = words[words.index(key + ",") - 1]
for key in (
"BlkSize:",
"BlkTile:",
"WaveTile:",
"WaveMap:",
"VmemReadVec:",
"BlkGemmPipelineScheduler:",
"BlkGemmPipelineVersion:",
"BlkGemmPipelinePrefetchStages:",
):
fields[key.strip(":")] = words[words.index(key) + 1].strip(",")
if "KBatch" in words:
key = "KBatch"
fields[key] = words[words.index(key) + 1]
return fields
class GemmMulMulWP:
"""
Wrapper for ckProfiler CLI parameters specific to gemm_multiply_multiply_weight_preshuffle
"""
dtype = Enum("dtype", [("f8f8f16", 0), ("f8f8bf16", 1)])
layout = Enum("layout", [("rpr", 0)])
class GemmMulMul:
"""
Wrapper for ckProfiler CLI parameters specific to gemm_multiply_multiply
"""
dtype = Enum(
"dtype",
[
("f32f32f32", 0),
("f16f16f16", 1),
("bf16bf16bf16", 2),
("i8i8i8", 3),
("f8f16f16", 4),
("f16f8f16", 5),
("f16f16f8", 6),
("f8f8bf16", 7),
("i8i8bf16", 8),
("i8i8f16", 9),
("f8f8f16", 10),
],
)
layout = Enum(
"layout",
[
("rrr", 0),
("rcr", 1),
("crr", 2),
("ccr", 3),
],
)
OPs = Enum(
"ops",
[
("gemm_multiply_multiply_weight_preshuffle", GemmMulMulWP),
("gemm_multiply_multiply", GemmMulMul),
],
)
def run_shape(shape, profiler_bin, op_name, dtype, layout):
"""
Launch ckProfiler in subprocess and collect its stdout
"""
import subprocess
m, n, k = shape
try:
op = OPs[op_name]
except KeyError:
raise AssertionError(f"Invalid operator {op_name}")
name_arg = op.name
op_wrapper = op.value()
try:
dtype_arg = str(op_wrapper.dtype[dtype].value)
except KeyError:
raise AssertionError(f"Invalid dtype for {op_name}: {dtype}")
try:
layout_wrapper = op_wrapper.layout[layout]
except KeyError:
raise AssertionError(f"Invalid layout for {op_name}: {layout}")
layout_arg = str(layout_wrapper.value)
# verification: no, initialization: decimal, print tensor: no, time kernel: yes
meta_args = map(str, [0, 2, 0, 1])
layout_a = layout_wrapper.name[0]
if layout_a == "r":
stride_a = k
elif layout_a == "c":
stride_a = n
else:
raise AssertionError(
f"Couldn't decide StrideA from layout {layout_wrapper.name}"
)
layout_b = layout_wrapper.name[1]
if layout_b == "r":
stride_b = n
elif layout_b in ("c", "p"):
stride_b = k
else:
raise AssertionError(
f"Couldn't decide StrideB from layout {layout_wrapper.name}"
)
# M, N, K, StrideA, StrideB, StrideD0, StrideD1, StrideE
shape_args = map(str, [m, n, k, stride_a, stride_b, 0, 0, n])
# kBatch, number of warm-up cycles, number of iterations, rotating buffer size in MB
control_args = map(str, [1, 50, 10, 4096])
cmd = [
profiler_bin,
name_arg,
dtype_arg,
layout_arg,
*meta_args,
*shape_args,
*control_args,
]
print(" ".join(cmd))
result = subprocess.run(
cmd,
capture_output=True,
text=True,
).stdout
return result.splitlines()
def filter_output_line(result_line, best_only):
"""
Filter out ckProfiler output lines which don't report performance results
"""
if "DeviceGemmXdlUniversal" in result_line:
if best_only:
if "Best Perf" in result_line:
return True
else:
if "Best Perf" not in result_line:
return True
return False
def write_results(filename, results):
"""
Write out the performance results to a csv file
"""
if not results:
return
with open(filename, "w", newline="") as f:
import csv
fields = list(results[0].keys())
writer = csv.DictWriter(f, dialect="unix", fieldnames=fields)
writer.writeheader()
for r in results:
writer.writerow(r)
def add_shape_to_metadata(shape, metadata):
"""
Adds M, N, K to the parsed profiler results
"""
m, n, k = shape
return metadata | {"M": m, "N": n, "K": k}
def main():
"""
Main driver:
- parses command line arguments
- parses input shapes to run ckProfiler with
- for each shape,
- runs ckProfiler
- parses the ckProfiler output
- writes out the results for all shapes
"""
args = parse_args()
filename = args["shapes_csv"]
shapes = tuples(filename)
all_results = []
from functools import partial
from os import path
profiler_bin = path.join(args["build_dir"], "bin", "ckProfiler")
try:
from tqdm import tqdm as iterate
except ImportError:
def iterate(x):
return x
for s in iterate(shapes):
run_shape_stdout_lines = run_shape(
s, profiler_bin, args["op_name"], args["dtype"], args["layout"]
)
results_single_shape = map(
lambda r: add_shape_to_metadata(s, r),
map(
parse_result,
filter(
partial(filter_output_line, best_only=args["best"]),
run_shape_stdout_lines,
),
),
)
all_results.extend(list(results_single_shape))
write_results(args["o"], all_results)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,157 @@
#!/bin/bash
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
#
# in order to run this script you'd first need to build the ckProfiler executable in ../build/bin/
# you would also need to set up some environment variables in order to
# post your new test results to the database and compare them to the baseline
# please contact Illia.Silin@amd.com for more details
#
# run the script as "./run_full_performance_tests.sh <verification> <tag for your test environment> <branch name> <node name>
# input arguments:
# verification = 0 : do not verify result correctness on CPU
# = 1 : verifuy correctness on CPU (may take a long time)
# environment tag : a string describing the specifics of your test environment
# branch name : name of the branch in git repo (git status | grep -e 'On branch')
# node name : $hostname
#get the command line arguments:
export verify=$1
echo 'Verification: ' $verify
export env_type=$2
echo 'Environment type: ' $env_type
export branch=$3
echo 'Branch name: ' $branch
export host_name=$4
echo 'Host name: ' $host_name
export arch=$5
echo 'GPU architecture: ' $arch
function print_log_header(){
rm -f $1;
echo 'On branch ' $3 &> $1;
echo 'Node name: ' $4 >> $1;
#get GPU_arch and number of compute units from rocminfo
echo -n "GPU_arch: " >> $1; rocminfo | grep "Name:" | grep "gfx" >> $1;
rocminfo | grep "Compute Unit:" >> $1;
hipcc --version | grep -e 'HIP version' >> $1;
echo 'Environment type: ' $2 >> $1;
/opt/rocm/bin/amdclang++ --version | grep -e 'InstalledDir' >> $1;
}
#run gemm tests
export gemm_log="perf_gemm_$arch.log"
print_log_header $gemm_log $env_type $branch $host_name
./profile_gemm.sh gemm 0 0 $verify 1 0 1 2>&1 | tee -a $gemm_log
./profile_gemm.sh gemm 1 0 $verify 1 0 1 2>&1 | tee -a $gemm_log
./profile_gemm.sh gemm 2 0 $verify 1 0 1 2>&1 | tee -a $gemm_log
./profile_gemm.sh gemm 3 0 $verify 1 0 1 2>&1 | tee -a $gemm_log
./profile_gemm.sh gemm 0 1 $verify 1 0 1 2>&1 | tee -a $gemm_log
./profile_gemm.sh gemm 1 1 $verify 1 0 1 2>&1 | tee -a $gemm_log
./profile_gemm.sh gemm 2 1 $verify 1 0 1 2>&1 | tee -a $gemm_log
./profile_gemm.sh gemm 3 1 $verify 1 0 1 2>&1 | tee -a $gemm_log
./profile_gemm.sh gemm 0 2 $verify 1 0 1 2>&1 | tee -a $gemm_log
./profile_gemm.sh gemm 1 2 $verify 1 0 1 2>&1 | tee -a $gemm_log
./profile_gemm.sh gemm 2 2 $verify 1 0 1 2>&1 | tee -a $gemm_log
./profile_gemm.sh gemm 3 2 $verify 1 0 1 2>&1 | tee -a $gemm_log
./profile_gemm.sh gemm 0 3 $verify 1 0 1 2>&1 | tee -a $gemm_log
./profile_gemm.sh gemm 1 3 $verify 1 0 1 2>&1 | tee -a $gemm_log
./profile_gemm.sh gemm 2 3 $verify 1 0 1 2>&1 | tee -a $gemm_log
./profile_gemm.sh gemm 3 3 $verify 1 0 1 2>&1 | tee -a $gemm_log
#run batched_gemm tests
export batched_gemm_log="perf_batched_gemm_$arch.log"
print_log_header $batched_gemm_log $env_type $branch $host_name
./profile_batched_gemm.sh batched_gemm 0 0 $verify 1 0 1 2>&1 | tee -a $batched_gemm_log
./profile_batched_gemm.sh batched_gemm 0 1 $verify 1 0 1 2>&1 | tee -a $batched_gemm_log
./profile_batched_gemm.sh batched_gemm 0 2 $verify 1 0 1 2>&1 | tee -a $batched_gemm_log
./profile_batched_gemm.sh batched_gemm 0 3 $verify 1 0 1 2>&1 | tee -a $batched_gemm_log
./profile_batched_gemm.sh batched_gemm 1 0 $verify 1 0 1 2>&1 | tee -a $batched_gemm_log
./profile_batched_gemm.sh batched_gemm 1 1 $verify 1 0 1 2>&1 | tee -a $batched_gemm_log
./profile_batched_gemm.sh batched_gemm 1 2 $verify 1 0 1 2>&1 | tee -a $batched_gemm_log
./profile_batched_gemm.sh batched_gemm 1 3 $verify 1 0 1 2>&1 | tee -a $batched_gemm_log
./profile_batched_gemm.sh batched_gemm 2 0 $verify 1 0 1 2>&1 | tee -a $batched_gemm_log
./profile_batched_gemm.sh batched_gemm 2 1 $verify 1 0 1 2>&1 | tee -a $batched_gemm_log
./profile_batched_gemm.sh batched_gemm 2 2 $verify 1 0 1 2>&1 | tee -a $batched_gemm_log
./profile_batched_gemm.sh batched_gemm 2 3 $verify 1 0 1 2>&1 | tee -a $batched_gemm_log
./profile_batched_gemm.sh batched_gemm 3 0 $verify 1 0 1 2>&1 | tee -a $batched_gemm_log
./profile_batched_gemm.sh batched_gemm 3 1 $verify 1 0 1 2>&1 | tee -a $batched_gemm_log
./profile_batched_gemm.sh batched_gemm 3 2 $verify 1 0 1 2>&1 | tee -a $batched_gemm_log
./profile_batched_gemm.sh batched_gemm 3 3 $verify 1 0 1 2>&1 | tee -a $batched_gemm_log
#run grouped_gemm tests
export grouped_gemm_log="perf_grouped_gemm_$arch.log"
print_log_header $grouped_gemm_log $env_type $branch $host_name
./profile_grouped_gemm.sh grouped_gemm 1 0 $verify 1 0 1 2>&1 | tee -a $grouped_gemm_log
./profile_grouped_gemm.sh grouped_gemm 1 1 $verify 1 0 1 2>&1 | tee -a $grouped_gemm_log
./profile_grouped_gemm.sh grouped_gemm 1 2 $verify 1 0 1 2>&1 | tee -a $grouped_gemm_log
./profile_grouped_gemm.sh grouped_gemm 1 3 $verify 1 0 1 2>&1 | tee -a $grouped_gemm_log
#run GEMM+Bilinear tests
export gemm_bilinear_log="perf_gemm_bilinear_$arch.log"
print_log_header $gemm_bilinear_log $env_type $branch $host_name
./profile_gemm_bilinear.sh gemm_bilinear 1 0 $verify 1 0 1 2>&1 | tee -a $gemm_bilinear_log
./profile_gemm_bilinear.sh gemm_bilinear 1 1 $verify 1 0 1 2>&1 | tee -a $gemm_bilinear_log
./profile_gemm_bilinear.sh gemm_bilinear 1 2 $verify 1 0 1 2>&1 | tee -a $gemm_bilinear_log
./profile_gemm_bilinear.sh gemm_bilinear 1 3 $verify 1 0 1 2>&1 | tee -a $gemm_bilinear_log
#run grouped_fwd tests
export grouped_conv_fwd_log="perf_grouped_conv_fwd_$arch.log"
print_log_header $grouped_conv_fwd_log $env_type $branch $host_name
./profile_grouped_conv_fwd.sh grouped_conv_fwd 0 1 0 $verify 1 0 1 256 2>&1 | tee -a $grouped_conv_fwd_log
./profile_grouped_conv_fwd.sh grouped_conv_fwd 1 1 0 $verify 1 0 1 256 2>&1 | tee -a $grouped_conv_fwd_log
./profile_grouped_conv_fwd.sh grouped_conv_fwd 2 1 0 $verify 1 0 1 256 2>&1 | tee -a $grouped_conv_fwd_log
#run grouped_bwd_data tests
export grouped_conv_bwd_data_log="perf_grouped_conv_bwd_data_$arch.log"
print_log_header $grouped_conv_bwd_data_log $env_type $branch $host_name
./profile_grouped_conv_bwd_data.sh grouped_conv_bwd_data 0 1 $verify 1 0 1 256 2>&1 | tee -a $grouped_conv_bwd_data_log
./profile_grouped_conv_bwd_data.sh grouped_conv_bwd_data 1 1 $verify 1 0 1 256 2>&1 | tee -a $grouped_conv_bwd_data_log
./profile_grouped_conv_bwd_data.sh grouped_conv_bwd_data 2 1 $verify 1 0 1 256 2>&1 | tee -a $grouped_conv_bwd_data_log
#run grouped_bwd_weight tests
export grouped_conv_bwd_weight_log="perf_grouped_conv_bwd_weight_$arch.log"
print_log_header $grouped_conv_bwd_weight_log $env_type $branch $host_name
./profile_grouped_conv_bwd_weight.sh grouped_conv_bwd_weight 0 2 $verify 1 0 1 256 1 2>&1 | tee -a $grouped_conv_bwd_weight_log
./profile_grouped_conv_bwd_weight.sh grouped_conv_bwd_weight 1 2 $verify 1 0 1 256 1 2>&1 | tee -a $grouped_conv_bwd_weight_log
./profile_grouped_conv_bwd_weight.sh grouped_conv_bwd_weight 2 2 $verify 1 0 1 256 1 2>&1 | tee -a $grouped_conv_bwd_weight_log
./profile_grouped_conv_bwd_weight.sh grouped_conv_bwd_weight 1 2 $verify 1 0 1 256 4 2>&1 | tee -a $grouped_conv_bwd_weight_log
#run resnet50 tests
export resnet256_log="perf_resnet50_N256_$arch.log"
print_log_header $resnet256_log $env_type $branch $host_name
./profile_resnet50.sh conv_fwd_bias_relu 1 1 1 1 $verify 1 0 1 256 2>&1 | tee -a $resnet256_log
export resnet4_log="perf_resnet50_N4_$arch.log"
print_log_header $resnet4_log $env_type $branch $host_name
./profile_resnet50.sh conv_fwd_bias_relu 1 1 1 1 $verify 1 0 1 4 2>&1 | tee -a $resnet4_log
#run reduction tests
export reduction_log="perf_reduction_$arch.log"
print_log_header $reduction_log $env_type $branch $host_name
./profile_reduce_with_index.sh $verify 2 10 --half 2>&1 | tee -a $reduction_log
./profile_reduce_no_index.sh $verify 2 10 --half 2>&1 | tee -a $reduction_log
#run splitK_gemm tests, first correctness verification, then performance
export splitK_gemm_log="perf_splitK_gemm_$arch.log"
print_log_header $splitK_gemm_log $env_type $branch $host_name
./profile_splitK_gemm.sh gemm_splitk 0 0 $verify 1 0 1 4 2>&1 | tee -a $splitK_gemm_log
./profile_splitK_gemm.sh gemm_splitk 0 1 $verify 1 0 1 4 2>&1 | tee -a $splitK_gemm_log
./profile_splitK_gemm.sh gemm_splitk 0 2 $verify 1 0 1 4 2>&1 | tee -a $splitK_gemm_log
./profile_splitK_gemm.sh gemm_splitk 0 3 $verify 1 0 1 4 2>&1 | tee -a $splitK_gemm_log
./profile_splitK_gemm.sh gemm_splitk 1 0 $verify 1 0 1 4 2>&1 | tee -a $splitK_gemm_log
./profile_splitK_gemm.sh gemm_splitk 1 1 $verify 1 0 1 4 2>&1 | tee -a $splitK_gemm_log
./profile_splitK_gemm.sh gemm_splitk 1 2 $verify 1 0 1 4 2>&1 | tee -a $splitK_gemm_log
./profile_splitK_gemm.sh gemm_splitk 1 3 $verify 1 0 1 4 2>&1 | tee -a $splitK_gemm_log
#run ONNX gemm tests
export onnx_log="perf_onnx_gemm_$arch.log"
print_log_header $onnx_log $env_type $branch $host_name
./profile_onnx_gemm.sh gemm 0 0 $verify 1 0 1 2>&1 | tee -a $onnx_log
./profile_onnx_gemm.sh gemm 1 0 $verify 1 0 1 2>&1 | tee -a $onnx_log
#run mixed fp16/fp8 and fp8/fp16 gemm tests
export mixed_gemm_log="perf_mixed_gemm_$arch.log"
print_log_header $mixed_gemm_log $env_type $branch $host_name
./profile_mixed_gemm.sh gemm_splitk 4 0 $verify 2 0 1 16 2>&1 | tee -a $mixed_gemm_log
./profile_mixed_gemm.sh gemm_splitk 5 0 $verify 2 0 1 16 2>&1 | tee -a $mixed_gemm_log

View File

@@ -0,0 +1,44 @@
#!/bin/bash
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
#
# in order to run this script you'd first need to build the ckProfiler executable in ../build/bin/
# run the script as "./run_gemm_performance_tests.sh <verification> <tag for your test environment> <branch name> <node name> <arch>
# input arguments:
# verification = 0 : do not verify result correctness on CPU
# = 1 : verify correctness on CPU (may take a long time)
# environment tag : a string describing the specifics of your test environment
# branch name : name of the branch in git repo (git status | grep -e 'On branch')
# node name : $hostname
# arch : GPU architecture, e.g. "gfx9" or "gfx1100"
#get the command line arguments:
export verify=$1
echo 'Verification: ' $verify
export env_type=$2
echo 'Environment type: ' $env_type
export branch=$3
echo 'Branch name: ' $branch
export host_name=$4
echo 'Host name: ' $host_name
export arch=$5
echo 'GPU architecture: ' $arch
function print_log_header(){
rm -f $1;
echo 'On branch ' $3 &> $1;
echo 'Node name: ' $4 >> $1;
#get GPU_arch and number of compute units from rocminfo
echo -n "GPU_arch: " >> $1; rocminfo | grep "Name:" | grep "gfx" >> $1;
rocminfo | grep "Compute Unit:" >> $1;
hipcc --version | grep -e 'HIP version' >> $1;
echo 'Environment type: ' $2 >> $1;
/opt/rocm/bin/amdclang++ --version | grep -e 'InstalledDir' >> $1;
}
#run ONNX gemm tests
export onnx_log="perf_onnx_gemm_$arch.log"
print_log_header $onnx_log $env_type $branch $host_name
./profile_onnx_gemm.sh gemm 0 0 $verify 1 0 1 2>&1 | tee -a $onnx_log
./profile_onnx_gemm.sh gemm 1 0 $verify 1 0 1 2>&1 | tee -a $onnx_log

71
script/run_performance_tests.sh Executable file
View File

@@ -0,0 +1,71 @@
#!/bin/bash
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
#
# in order to run this script you'd first need to build the ckProfiler executable in ../build/bin/
# run the script as "./run_performance_tests.sh <verification> <tag for your test environment> <branch name> <node name>
# input arguments:
# verification = 0 : do not verify result correctness on CPU
# = 1 : verify correctness on CPU (may take a long time)
# environment tag : a string describing the specifics of your test environment
# branch name : name of the branch in git repo (git status | grep -e 'On branch')
# node name : $hostname
#get the command line arguments:
export verify=$1
echo 'Verification: ' $verify
export env_type=$2
echo 'Environment type: ' $env_type
export branch=$3
echo 'Branch name: ' $branch
export host_name=$4
echo 'Host name: ' $host_name
export arch=$5
echo 'GPU architecture: ' $arch
function print_log_header(){
rm -f $1;
echo 'On branch ' $3 &> $1;
echo 'Node name: ' $4 >> $1;
#get GPU_arch and number of compute units from rocminfo
echo -n "GPU_arch: " >> $1; rocminfo | grep "Name:" | grep "gfx" >> $1;
rocminfo | grep "Compute Unit:" >> $1;
hipcc --version | grep -e 'HIP version' >> $1;
echo 'Environment type: ' $2 >> $1;
/opt/rocm/bin/amdclang++ --version | grep -e 'InstalledDir' >> $1;
}
#run gemm tests
export gemm_log="perf_gemm_$arch.log"
print_log_header $gemm_log $env_type $branch $host_name
./profile_gemm.sh gemm 0 0 $verify 1 0 1 | tee -a $gemm_log
./profile_gemm.sh gemm 1 0 $verify 1 0 1 | tee -a $gemm_log
./profile_gemm.sh gemm 2 0 $verify 1 0 1 | tee -a $gemm_log
./profile_gemm.sh gemm 3 0 $verify 1 0 1 | tee -a $gemm_log
./profile_gemm.sh gemm 0 1 $verify 1 0 1 | tee -a $gemm_log
./profile_gemm.sh gemm 1 1 $verify 1 0 1 | tee -a $gemm_log
./profile_gemm.sh gemm 2 1 $verify 1 0 1 | tee -a $gemm_log
./profile_gemm.sh gemm 3 1 $verify 1 0 1 | tee -a $gemm_log
./profile_gemm.sh gemm 0 2 $verify 1 0 1 | tee -a $gemm_log
./profile_gemm.sh gemm 1 2 $verify 1 0 1 | tee -a $gemm_log
./profile_gemm.sh gemm 2 2 $verify 1 0 1 | tee -a $gemm_log
./profile_gemm.sh gemm 3 2 $verify 1 0 1 | tee -a $gemm_log
./profile_gemm.sh gemm 0 3 $verify 1 0 1 | tee -a $gemm_log
./profile_gemm.sh gemm 1 3 $verify 1 0 1 | tee -a $gemm_log
./profile_gemm.sh gemm 2 3 $verify 1 0 1 | tee -a $gemm_log
./profile_gemm.sh gemm 3 3 $verify 1 0 1 | tee -a $gemm_log
#run ONNX gemm tests
export onnx_log="perf_onnx_gemm_$arch.log"
print_log_header $onnx_log $env_type $branch $host_name
./profile_onnx_gemm.sh gemm 0 0 $verify 1 0 1 2>&1 | tee -a $onnx_log
./profile_onnx_gemm.sh gemm 1 0 $verify 1 0 1 2>&1 | tee -a $onnx_log
#run resnet50 tests
export resnet256_log="perf_resnet50_N256_$arch.log"
print_log_header $resnet256_log $env_type $branch $host_name
./profile_resnet50.sh conv_fwd_bias_relu 1 1 1 1 $verify 1 0 1 256 | tee -a $resnet256_log
export resnet4_log="perf_resnet50_N4_$arch.log"
print_log_header $resnet4_log $env_type $branch $host_name
./profile_resnet50.sh conv_fwd_bias_relu 1 1 1 1 $verify 1 0 1 4 | tee -a $resnet4_log

76
script/sccache_wrapper.sh Executable file
View File

@@ -0,0 +1,76 @@
#!/bin/bash
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
set -e
COMPILERS_HASH_DIR=${COMPILERS_HASH_DIR:-"/tmp/.sccache"}
SCCACHE_EXTRAFILES=${SCCACHE_EXTRAFILES:-"${COMPILERS_HASH_DIR}/rocm_compilers_hash_file"}
SCCACHE_BIN=${SCCACHE_BIN:-"${SCCACHE_INSTALL_LOCATION}/sccache"}
ENFORCE_REDIS="false"
while [ "$1" != "" ];
do
case $1 in
--enforce_redis )
shift; ENFORCE_REDIS="true" ;;
--no-hipcc )
shift ;;
*)
break ;;
esac
done
setup_rocm_compilers_hash_file() {
mkdir -p "$COMPILERS_HASH_DIR"
HIPCC_MD5="$(md5sum "${ROCM_PATH}/bin/hipcc")"
pushd "${ROCM_PATH}/amdgcn/bitcode"
DEVICELIBS_BITCODES_MD5="$(find . -type f -exec md5sum {} \; | sort | md5sum)"
popd
HIPCC_HASH_VALUE="${HIPCC_MD5%% *}"
DEVICELIBS_BITCODES_HASH_VALUE="${DEVICELIBS_BITCODES_MD5%% *}"
# MD5 checksums of clang and clang-offload-bundler cannot be used since they will keep changing
# if the ROCM_PATH changes, ie; for every mainline build.
# This is because ROCM_PATH gets encoded into the clang/clang-offload-bundler binaries as part
# of RPATH.
# The versions themselves contain the commit hash of the compiler repo at the time of building.
# Hence, this should be a viable alternative to using the binary checksum itself.
CLANG_VERSION="$("${ROCM_PATH}/llvm/bin/clang" --version | head -n 1)"
CLANG_OFFLOAD_BUNDLER_VERSION="$("${ROCM_PATH}/llvm/bin/clang-offload-bundler" --version | head -n 1)"
printf '%s: %s\n' 'clang version' "${CLANG_VERSION}" | tee -a "$SCCACHE_EXTRAFILES"
printf '%s: %s\n' 'clang-offload-bundler version' "${CLANG_OFFLOAD_BUNDLER_VERSION}" | tee -a "$SCCACHE_EXTRAFILES"
printf '%s: %s\n' 'hipcc md5sum' "${HIPCC_HASH_VALUE}" | tee -a "$SCCACHE_EXTRAFILES"
printf '%s: %s\n' 'devicelibs bitcode md5sum' "${DEVICELIBS_BITCODES_HASH_VALUE}" | tee -a "$SCCACHE_EXTRAFILES"
echo "sccache-wrapper: compilers hash file set up at ${SCCACHE_EXTRAFILES}"
cat "$SCCACHE_EXTRAFILES"
}
if [ "${ENFORCE_REDIS}" == "true" ]; then
if [ -z "${SCCACHE_REDIS}" ]; then
echo "SCCACHE_REDIS not set. Not wrapping compilers with sccache."
exit 10
else
response=$(redis-cli -u ${SCCACHE_REDIS} ping) || true
if [ "${response}" != "PONG" ]; then
echo "Redis server unreachable. Not wrapping compilers with sccache."
exit 20
fi
fi
fi
setup_rocm_compilers_hash_file
$SCCACHE_BIN --version
echo "=== Starting sccache server at $(date) ==="
$SCCACHE_BIN --start-server
# Log initial sccache statistics
echo "=== Initial sccache statistics ==="
$SCCACHE_BIN --show-stats || echo "Could not get initial stats"
# Test Redis connectivity and performance
echo "=== Testing Redis connectivity ==="
start_time=$(date +%s%N)
redis-cli -u ${SCCACHE_REDIS} ping || echo "Redis ping failed"
end_time=$(date +%s%N)
latency=$(( (end_time - start_time) / 1000000 ))
echo "Redis ping latency: ${latency}ms"
# Check Redis memory status
echo "=== Redis memory status ==="
redis-cli -u ${SCCACHE_REDIS} info memory | grep -E "(used_memory|maxmemory|evicted_keys)" || echo "Could not get Redis memory info"

113
script/test_convnd_fwd.sh Normal file
View File

@@ -0,0 +1,113 @@
#!/usr/bin/env bash
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
# set -e
DIM1=False
DIM2=True
DIM3=False
DATE=220317
GIT_HASH=4e6dfda
LOG_DIR=${DATE}_${GIT_HASH}
SUFFIX=${GIT_HASH}
#--------------------------------------------------------------------------
# Commandline arguments parsing
# like: cmd -key[--key] value
#--------------------------------------------------------------------------
POSITIONAL=()
while [[ $# -gt 0 ]]
do
key="$1"
case $key in
-d1|--d1)
DIM1=True
echo DIM1: "${DIM1}"
shift # past argument
;;
-d2|--d2)
DIM2=True
echo DIM2: "${DIM2}"
shift # past argument
;;
-d3|--d3)
DIM3=True
echo DIM3: "${DIM3}"
shift # past argument
;;
-all|--all)
DIM1=True
DIM2=True
DIM3=True
echo DIM1: "${DIM1}"
echo DIM2: "${DIM2}"
echo DIM3: "${DIM3}"
shift # past argument
;;
-s|--suffix)
SUFFIX=${SUFFIX}_"$2"
echo SUFFIX: "${SUFFIX}"
shift # past argument
shift # past value
;;
*) # unknown option
POSITIONAL+=("$1") # save it in an array for later
shift # past argument
;;
esac
done
set -- "${POSITIONAL[@]}" # restore positional parameters
#--------------------------------------------------------------------------
# NUMACTL="numactl --cpunodebind=1 --membind=1"
NUMACTL=
# ENV_CONF=
GPU=gfx908
PROF_ITER_COUNT=10000
LOG_DIR_PATH=../log/${LOG_DIR}
set -x
#-------------------------------------------------------------------------------
# 1D
#-------------------------------------------------------------------------------
if [[ "${DIM1}" == "True" ]]; then
mkdir -p ${LOG_DIR_PATH}
echo ">>>>>>>> RUN test conv1d nwc <<<<<<<<<<"
CMD="./../build/bin/test_conv1d_fwd"
${NUMACTL} ${CMD} 2>&1 \
| tee ${LOG_DIR_PATH}/test_conv1d_fwd_nwc_${SUFFIX}_${GPU}.log
fi
#-------------------------------------------------------------------------------
# 2D
#-------------------------------------------------------------------------------
if [[ "${DIM2}" == "True" ]]; then
mkdir -p ${LOG_DIR_PATH}
echo ">>>>>>>> RUN test conv2d nhwc <<<<<<<<<<"
CMD="./../build/bin/test_conv2d_fwd"
${NUMACTL} ${CMD} 2>&1 \
| tee ${LOG_DIR_PATH}/test_conv2d_fwd_nhwc_${SUFFIX}_${GPU}.log
fi
#-------------------------------------------------------------------------------
# 3D
#-------------------------------------------------------------------------------
if [[ "${DIM3}" == "True" ]]; then
mkdir -p ${LOG_DIR_PATH}
echo ">>>>>>>> RUN test conv3d ndhwc <<<<<<<<<<"
CMD="./../build/bin/test_conv3d_fwd"
${NUMACTL} ${CMD} 2>&1 \
| tee ${LOG_DIR_PATH}/test_conv3d_fwd_ndhwc_${SUFFIX}_${GPU}.log
fi

66
script/test_reduce_no_index.sh Executable file
View File

@@ -0,0 +1,66 @@
#!/bin/bash
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
## The following will be used for CI
set -x
## for float
bin/test_reduce_no_index -D 64,4,280,82 -R 0,1,2,3 0 2
bin/test_reduce_no_index -D 64,4,280,82 -R 0,1,2 0 2
bin/test_reduce_no_index -D 64,4,280,82 -R 0,1,3 0 2
bin/test_reduce_no_index -D 64,4,280,82 -R 0,2,3 0 2
bin/test_reduce_no_index -D 64,4,280,82 -R 1,2,3 0 2
bin/test_reduce_no_index -D 64,4,280,82 -R 0 0 2
bin/test_reduce_no_index -D 64,4,280,82 -R 1 0 2
bin/test_reduce_no_index -D 64,4,280,82 -R 2 0 2
bin/test_reduce_no_index -D 64,4,280,82 -R 3 0 2
## for float64
bin/test_reduce_no_index -D 64,4,280,82 -R 0,1,2,3 6 2
bin/test_reduce_no_index -D 64,4,280,82 -R 0,1,2 6 2
bin/test_reduce_no_index -D 64,4,280,82 -R 0,1,3 6 2
bin/test_reduce_no_index -D 64,4,280,82 -R 0,2,3 6 2
bin/test_reduce_no_index -D 64,4,280,82 -R 1,2,3 6 2
bin/test_reduce_no_index -D 64,4,280,82 -R 0 6 2
bin/test_reduce_no_index -D 64,4,280,82 -R 1 6 2
bin/test_reduce_no_index -D 64,4,280,82 -R 2 6 2
bin/test_reduce_no_index -D 64,4,280,82 -R 3 6 2
## for float16
bin/test_reduce_no_index -D 64,4,280,82 -R 0,1,2,3 1 2
bin/test_reduce_no_index -D 64,4,280,82 -R 0,1,2 1 2
bin/test_reduce_no_index -D 64,4,280,82 -R 0,1,3 1 2
bin/test_reduce_no_index -D 64,4,280,82 -R 0,2,3 1 2
bin/test_reduce_no_index -D 64,4,280,82 -R 1,2,3 1 2
bin/test_reduce_no_index -D 64,4,280,82 -R 0 1 2
bin/test_reduce_no_index -D 64,4,280,82 -R 1 1 2
bin/test_reduce_no_index -D 64,4,280,82 -R 2 1 2
bin/test_reduce_no_index -D 64,4,280,82 -R 3 1 2
## for int8_t
bin/test_reduce_no_index -D 64,4,280,82 -R 0,1,2,3 3 2
bin/test_reduce_no_index -D 64,4,280,82 -R 0,1,2 3 2
bin/test_reduce_no_index -D 64,4,280,82 -R 0,1,3 3 2
bin/test_reduce_no_index -D 64,4,280,82 -R 0,2,3 3 2
bin/test_reduce_no_index -D 64,4,280,82 -R 1,2,3 3 2
bin/test_reduce_no_index -D 64,4,280,82 -R 0 3 2
bin/test_reduce_no_index -D 64,4,280,82 -R 1 3 2
bin/test_reduce_no_index -D 64,4,280,82 -R 2 3 2
bin/test_reduce_no_index -D 64,4,280,82 -R 3 3 2
## for bfloat16
bin/test_reduce_no_index -D 64,4,280,82 -R 0,1,2,3 5 2
bin/test_reduce_no_index -D 64,4,280,82 -R 0,1,2 5 2
bin/test_reduce_no_index -D 64,4,280,82 -R 0,1,3 5 2
bin/test_reduce_no_index -D 64,4,280,82 -R 0,2,3 5 2
bin/test_reduce_no_index -D 64,4,280,82 -R 1,2,3 5 2
bin/test_reduce_no_index -D 64,4,280,82 -R 0 5 2
bin/test_reduce_no_index -D 64,4,280,82 -R 1 5 2
bin/test_reduce_no_index -D 64,4,280,82 -R 2 5 2
bin/test_reduce_no_index -D 64,4,280,82 -R 3 5 2
set +x

78
script/tools/README.md Normal file
View File

@@ -0,0 +1,78 @@
# Composable Kernel Tools
This directory contains utility tools for building, testing, and analyzing Composable Kernel.
These tools are designed to be LLM-agnostic and can be used with any AI assistant or directly from the command line.
## Available Tools
### ck-docker
Build and test composable_kernel in Docker with ROCm support.
See [README_ck-docker.md](README_ck-docker.md) for details.
**Quick start:**
```bash
# Add to PATH
export PATH="$PATH:$PWD/script/tools"
# Start container and build
ck-docker start
ck-docker build test_amdgcn_mma
ck-docker test test_amdgcn_mma
```
### ck-build-analysis
Analyze Composable Kernel build times using Clang's -ftime-trace profiler.
See [README_ck-build-analysis.md](README_ck-build-analysis.md) for details.
**Quick start:**
```bash
# Add to PATH
export PATH="$PATH:$PWD/script/tools"
# Analyze build time
ck-build-analysis example_convnd_fwd_xdl_fp8
```
## LLM Assistant Integration
These tools can be used as-is with any LLM assistant by providing the tool documentation to the assistant. The assistant can then invoke these tools on your behalf.
For example, you can ask:
- "Start the docker container"
- "Build and test test_amdgcn_mma"
- "Analyze build time for example_convnd_fwd_xdl_fp8"
The assistant will translate your natural language request into the appropriate tool invocation.
## Dependencies
- **ck-docker**: Requires Docker and ROCm-capable GPU (for running tests)
- **ck-build-analysis**: Requires Docker, automatically installs Python dependencies (jinja2) via `uv`
## Directory Structure
```
script/tools/
├── README.md # This file
├── README_ck-docker.md # Documentation for ck-docker
├── README_ck-build-analysis.md # Documentation for ck-build-analysis
├── ck-docker # Docker container management tool
├── ck-build-analysis # Build time analysis tool
├── common.sh # Shared utilities for bash scripts
├── analyze_build_trace.py # Python script for trace analysis (PEP 723 compliant)
└── templates/
└── build_analysis_report.md.jinja # Jinja2 template for analysis reports
```
## Contributing
When adding new tools to this directory:
1. Keep them LLM-agnostic (avoid hardcoding references to specific AI assistants)
2. Provide clear command-line usage documentation
3. Include examples for both CLI and LLM assistant usage
4. Follow the existing naming convention and structure

View File

@@ -0,0 +1,168 @@
# ck-build-analysis
Analyze Composable Kernel build times using Clang's -ftime-trace profiler.
## Terminal Usage
Direct command-line usage:
```bash
# From composable_kernel directory
script/tools/ck-build-analysis example_convnd_fwd_xdl_fp8
script/tools/ck-build-analysis example_convnd_fwd_xdl_fp8 --granularity=1
script/tools/ck-build-analysis example_convnd_fwd_xdl_fp8 --granularity=1 --output=my_report.md
# Or add to PATH
export PATH="$PATH:$PWD/script/tools"
ck-build-analysis example_convnd_fwd_xdl_fp8
```
## LLM Assistant Integration
If using an LLM assistant, you can ask in natural language:
- "Analyze build time for example_convnd_fwd_xdl_fp8"
- "Profile the compilation of test_amdgcn_mma with 1us granularity"
- "Generate a build time report for example_gemm_xdl"
## Commands
```
ck-build-analysis <target> [options]
Options:
--granularity=N Time trace granularity in microseconds (default: 1)
--output=FILE Output report filename (default: build_time_analysis_report.md)
--name=NAME Docker container name (default: from CK_CONTAINER_NAME or auto-generated)
--no-reconfigure Skip CMake reconfiguration if build exists
--help Show this help message
```
## What It Does
1. **Configures CMake** with `-ftime-trace` and custom granularity
2. **Builds the target** using Ninja in Docker
3. **Analyzes the trace** JSON file for template instantiation patterns
4. **Generates a report** with:
- Compilation phase breakdown
- Top expensive individual instantiations
- Template families ranked by total time and count
- Key insights and optimization recommendations
- Complete statistics
## Configuration
- **Container**: Uses ck-docker container (auto-starts if needed)
- **Granularity**: Default 1us (100% template coverage, best balance)
- **Output**: Markdown report in project root
## Environment
```bash
export CK_CONTAINER_NAME=my_build # Override container name
export CK_BUILD_ANALYSIS_GRANULARITY=1 # Default granularity in microseconds
```
## Examples
```bash
# Complete template analysis with default granularity (1us - recommended)
ck-build-analysis example_convnd_fwd_xdl_fp8
# Quick daily check (10us granularity, captures most expensive templates)
ck-build-analysis example_convnd_fwd_xdl_fp8 --granularity=10
# Maximum detail (0us granularity, includes LLVM internals)
ck-build-analysis example_convnd_fwd_xdl_fp8 --granularity=0
# High-level overview (500us granularity, major bottlenecks only)
ck-build-analysis example_convnd_fwd_xdl_fp8 --granularity=500
# Custom output filename
ck-build-analysis example_convnd_fwd_xdl_fp8 --output=fp8_conv_analysis.md
# Analyze test target
ck-build-analysis test_amdgcn_mma
# Use existing build (skip reconfigure)
ck-build-analysis example_convnd_fwd_xdl_fp8 --no-reconfigure
```
## Output
The report includes:
- **Executive Summary**: Total time, events, instantiations, unique templates
- **Compilation Phases**: InstantiateFunction, Frontend, Backend, Optimizer, etc.
- **Top 30 Individual Instantiations**: Most expensive single templates
- **Template Families**: Grouped by total time and instantiation count
- **Key Insights**: What's slow and why
- **Optimization Recommendations**: Short, medium, and long-term strategies
- **Detailed Statistics**: Averages, medians, distributions
## Granularity Trade-offs
| Granularity | Template Coverage | Use Case |
|-------------|-------------------|----------|
| **0us** | All templates + sub-us compiler internals | LLVM internals debugging, very large files, higher overhead |
| **1us (default)** | **All templates** | **Default: Complete template analysis with low overhead** |
| **10us** | Most expensive templates | Daily quick checks, smaller files, minimal overhead |
| **50-100us** | Top bottlenecks | Balanced detail/size, suitable for CI/CD |
| **500us** | High-level phases only | Not recommended for template analysis |
**Recommended default**: 1us captures all template instantiations with minimal overhead
## Notes
- **0us and 1us capture all templates** - 0us adds sub-microsecond compiler internals
- **1us is the sweet spot**: complete template coverage, filters noise, low overhead
- **10us is practical** for daily use: captures most expensive templates, smaller files
- **500us loses most template instantiation data** - only use for high-level phase breakdown
- Finer granularity = more events = larger files + higher build time overhead
- For template-heavy C++ codebases like CK: **use 1us for analysis, 10us for daily checks**
## Implementation Details
### PEP 723 Compliance with Automatic Dependency Management
The analysis script (`analyze_build_trace.py`) is PEP 723 compliant with inline dependency metadata:
```python
# /// script
# requires-python = ">=3.8"
# dependencies = [
# "jinja2>=3.0.0",
# ]
# ///
```
**The tool automatically installs and uses `uv`**, which provides:
- ✅ Zero-configuration dependency management
- ✅ Automatic installation of jinja2 from PEP 723 metadata
- ✅ Isolated dependency environment (no system pollution)
- ✅ Fast caching for subsequent runs
**No manual setup required!** The first time you run the tool, it will:
1. Detect if `uv` is installed in the container
2. If not, automatically install it via Ubuntu packages (pipx install uv)
3. Use `uv run` to execute the analysis with auto-managed dependencies
On subsequent runs, `uv` will already be available and dependencies will be cached.
Installation is done through Ubuntu's package manager for security and reliability.
### Components
- **ck-build-analysis** - Main bash script that orchestrates Docker, CMake, and analysis
- **analyze_build_trace.py** - PEP 723 compliant Python script for trace analysis
- **templates/build_analysis_report.md.jinja** - Jinja2 template for report generation
### Standalone Usage
The Python script can also be run independently:
```bash
# With uv (recommended - auto-installs dependencies from PEP 723 metadata)
uv run script/tools/analyze_build_trace.py trace.json report.md target 100 22 templates/
# With pipx (alternative - also auto-installs dependencies)
pipx run script/tools/analyze_build_trace.py trace.json report.md target 100 22 templates/
```

View File

@@ -0,0 +1,80 @@
# ck-docker
Build and test composable_kernel in Docker with ROCm support.
## Terminal Usage
Direct command-line usage:
```bash
# From composable_kernel directory
script/tools/ck-docker start
script/tools/ck-docker build test_amdgcn_mma
script/tools/ck-docker test test_amdgcn_mma --gtest_filter=*Fp16*
script/tools/ck-docker status
script/tools/ck-docker shell
# Or add to PATH
export PATH="$PATH:$PWD/script/tools"
ck-docker start
```
## LLM Assistant Integration
If using an LLM assistant, you can ask in natural language:
- "Start the docker container"
- "Build test_amdgcn_mma"
- "Run test_amdgcn_mma with filter *Fp16*"
- "Check container status"
- "Open a shell in the container"
## Commands
```
ck-docker start [name] Start Docker container
ck-docker build [target] [--reconfigure] Build target (optionally reconfigure CMake)
ck-docker test <name> [options] Run test
ck-docker shell [name] Interactive shell
ck-docker status [name] Check status
ck-docker stop [name] Stop container
```
## Configuration
- **Image**: rocm/composable_kernel:ck_ub24.04_rocm7.0.1
- **GPU**: Auto-detected via rocminfo (fallback: gfx950)
- **Compiler**: /opt/rocm/llvm/bin/clang++
- **Build**: Ninja + CMake (Release)
- **Mount**: Current directory → /workspace
- **Container Name**: Auto-generated as `ck_<username>_<branch>` to avoid clashes
## Environment
```bash
export CK_CONTAINER_NAME=my_build # Override default container name
export CK_DOCKER_IMAGE=rocm/composable_kernel:ck_ub24.04_rocm7.0.1 # Override Docker image
export GPU_TARGET=gfx942 # Override GPU target detection
```
## Examples
```bash
# Start container
ck-docker start
# Build and run test
ck-docker build test_amdgcn_mma
ck-docker test test_amdgcn_mma
# Force clean CMake reconfiguration and build
ck-docker build --reconfigure test_amdgcn_mma
# Custom container
ck-docker start my_build
ck-docker build test_amdgcn_mma --name my_build
ck-docker test test_amdgcn_mma --name my_build
# Debug
ck-docker shell
ck-docker status
```

View File

@@ -0,0 +1,347 @@
#!/usr/bin/env python3
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
# /// script
# requires-python = ">=3.8"
# dependencies = [
# "jinja2>=3.0.0",
# ]
# ///
"""
Build Time Analysis Tool for Composable Kernel
Analyzes Clang -ftime-trace output to identify template instantiation
bottlenecks and generate comprehensive build time reports.
"""
import json
import os
import re
import sys
from collections import defaultdict
from datetime import datetime
try:
from jinja2 import Environment, FileSystemLoader
except ImportError:
print("Error: jinja2 is required but not installed.", file=sys.stderr)
print("Install with: apt-get install python3-jinja2", file=sys.stderr)
print("Or with pip: pip install jinja2", file=sys.stderr)
sys.exit(1)
def parse_arguments():
"""Parse command-line arguments."""
if len(sys.argv) < 7:
print(
"Usage: analyze_build_trace.py <trace_files_or_dir> <output_file> <target> <granularity> <build_time> <template_dir>"
)
print(
" trace_files_or_dir: Comma-separated list of trace files OR directory containing .json files"
)
sys.exit(1)
return {
"trace_input": sys.argv[1],
"output_file": sys.argv[2],
"target": sys.argv[3],
"granularity": sys.argv[4],
"build_time": sys.argv[5],
"template_dir": sys.argv[6],
}
def find_trace_files(trace_input):
"""Find all trace files from input (file list, single file, or directory)."""
trace_files = []
# Check if it's a directory
if os.path.isdir(trace_input):
print(f"Scanning directory: {trace_input}")
for root, dirs, files in os.walk(trace_input):
for file in files:
# Include .cpp.json and .hip.json, exclude compile_commands.json and CMake files
if file.endswith((".cpp.json", ".hip.json")) and "CMakeFiles" in root:
trace_files.append(os.path.join(root, file))
trace_files.sort()
# Check if it's a comma-separated list
elif "," in trace_input:
trace_files = [f.strip() for f in trace_input.split(",")]
# Single file
else:
trace_files = [trace_input]
# Filter out non-existent files
valid_files = [f for f in trace_files if os.path.isfile(f)]
if not valid_files:
print(f"Error: No valid trace files found in: {trace_input}", file=sys.stderr)
sys.exit(1)
print(f"Found {len(valid_files)} trace file(s)")
return valid_files
def load_trace_data(trace_files):
"""Load and parse multiple trace JSON files."""
all_data = []
for trace_file in trace_files:
print(f" Loading: {trace_file}")
try:
with open(trace_file, "r") as f:
data = json.load(f)
# Get file basename for tracking
file_name = os.path.basename(trace_file)
all_data.append({"file": file_name, "path": trace_file, "data": data})
except Exception as e:
print(f" Warning: Failed to load {trace_file}: {e}", file=sys.stderr)
return all_data
def process_events(all_trace_data):
"""Process trace events from multiple files and extract statistics."""
print("Processing events from all files...")
template_stats = defaultdict(lambda: {"count": 0, "total_dur": 0})
phase_stats = defaultdict(int)
top_individual = []
file_stats = []
total_events = 0
for trace_info in all_trace_data:
file_name = trace_info["file"]
data = trace_info["data"]
events = data.get("traceEvents", [])
file_template_time = 0
file_event_count = len(events)
total_events += file_event_count
print(f" Processing {file_name}: {file_event_count:,} events")
for event in events:
name = event.get("name", "")
dur = int(event.get("dur", 0)) # Keep as integer microseconds
if name and dur > 0:
phase_stats[name] += dur
if name in ["InstantiateFunction", "InstantiateClass"]:
detail = event.get("args", {}).get("detail", "")
top_individual.append(
{"detail": detail, "dur": dur, "type": name, "file": file_name}
)
file_template_time += dur
# Extract template name (everything before '<' or '(')
match = re.match(r"^([^<(]+)", detail)
if match:
template_name = match.group(1).strip()
# Normalize template names
template_name = re.sub(r"^ck::", "", template_name)
template_name = re.sub(r"^std::", "std::", template_name)
template_stats[template_name]["count"] += 1
template_stats[template_name]["total_dur"] += dur
file_stats.append(
{
"name": file_name,
"events": file_event_count,
"template_time": file_template_time,
}
)
return template_stats, phase_stats, top_individual, file_stats, total_events
def prepare_template_data(template_stats, phase_stats, top_individual, file_stats):
"""Prepare and calculate derived statistics for template rendering."""
print("Sorting data...")
# Sort data
sorted_phases = sorted(phase_stats.items(), key=lambda x: x[1], reverse=True)
top_individual.sort(key=lambda x: x["dur"], reverse=True)
file_stats.sort(key=lambda x: x["template_time"], reverse=True)
# Calculate totals
total_template_time = sum(s["total_dur"] for s in template_stats.values())
total_trace_time = sum(phase_stats.values())
total_inst = sum(s["count"] for s in template_stats.values())
# Prepare templates by time with calculated fields
templates_by_time = []
for name, stats in sorted(
template_stats.items(), key=lambda x: x[1]["total_dur"], reverse=True
):
templates_by_time.append(
(
name,
{
"count": stats["count"],
"total_dur": stats["total_dur"],
"avg": stats["total_dur"] // stats["count"]
if stats["count"] > 0
else 0,
"pct": 100 * stats["total_dur"] / total_template_time
if total_template_time > 0
else 0,
},
)
)
# Prepare templates by count
templates_by_count = []
for name, stats in sorted(
template_stats.items(), key=lambda x: x[1]["count"], reverse=True
):
templates_by_count.append(
(
name,
{
"count": stats["count"],
"total_dur": stats["total_dur"],
"avg": stats["total_dur"] // stats["count"]
if stats["count"] > 0
else 0,
},
)
)
# Add friendly type names to individual instantiations
for inst in top_individual:
inst["inst_type"] = "Func" if inst["type"] == "InstantiateFunction" else "Class"
# Calculate additional metrics
median_count = 0
if len(template_stats) > 0:
median_count = sorted([s["count"] for s in template_stats.values()])[
len(template_stats) // 2
]
top10_pct = 0
if len(templates_by_time) >= 10:
top10_pct = (
100
* sum(s[1]["total_dur"] for s in templates_by_time[:10])
/ total_template_time
)
return {
"sorted_phases": sorted_phases,
"top_individual": top_individual,
"templates_by_time": templates_by_time,
"templates_by_count": templates_by_count,
"total_template_time": total_template_time,
"total_trace_time": total_trace_time,
"total_inst": total_inst,
"median_count": median_count,
"top10_pct": top10_pct,
"unique_families": len(template_stats),
"file_stats": file_stats,
}
def setup_jinja_environment(template_dir):
"""Set up Jinja2 environment with custom filters."""
env = Environment(loader=FileSystemLoader(template_dir))
def format_number(value):
"""Format number with thousand separators."""
return f"{value:,}"
def truncate(value, length):
"""Truncate string to length with ellipsis."""
if len(value) > length:
return value[: length - 3] + "..."
return value
def pad(value, length):
"""Pad string to specified length."""
return f"{value:<{length}}"
def us_to_ms(value):
"""Convert microseconds to milliseconds."""
return value / 1000.0
def us_to_s(value):
"""Convert microseconds to seconds."""
return value / 1000000.0
env.filters["format_number"] = format_number
env.filters["truncate"] = truncate
env.filters["pad"] = pad
env.filters["us_to_ms"] = us_to_ms
env.filters["us_to_s"] = us_to_s
return env
def generate_report(env, data, args, total_events, num_files):
"""Generate the final report using Jinja2 template."""
print("Rendering report with Jinja2...")
template = env.get_template("build_analysis_report.md.jinja")
report_content = template.render(
timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
target=args["target"],
granularity=args["granularity"],
build_time=args["build_time"],
total_events=total_events,
num_files=num_files,
total_instantiations=data["total_inst"],
unique_families=data["unique_families"],
total_trace_time=data["total_trace_time"],
total_template_time=data["total_template_time"],
phases=data["sorted_phases"],
top_individual=data["top_individual"],
templates_by_time=data["templates_by_time"],
templates_by_count=data["templates_by_count"],
median_count=data["median_count"],
top10_pct=data["top10_pct"],
file_stats=data["file_stats"],
)
return report_content
def main():
"""Main entry point for the analysis tool."""
args = parse_arguments()
# Find and load trace files
trace_files = find_trace_files(args["trace_input"])
all_trace_data = load_trace_data(trace_files)
# Process events from all files
template_stats, phase_stats, top_individual, file_stats, total_events = (
process_events(all_trace_data)
)
# Prepare template data
data = prepare_template_data(
template_stats, phase_stats, top_individual, file_stats
)
# Setup Jinja2 environment
env = setup_jinja_environment(args["template_dir"])
# Generate report
report_content = generate_report(env, data, args, total_events, len(all_trace_data))
# Write output
with open(args["output_file"], "w") as f:
f.write(report_content)
print(f"Report generated: {args['output_file']}")
print(f"Report size: {len(report_content):,} bytes")
print(f"Analyzed {len(all_trace_data)} file(s) with {total_events:,} total events")
if __name__ == "__main__":
main()

163
script/tools/ck-build Executable file
View File

@@ -0,0 +1,163 @@
#!/bin/bash
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
# CK Build - Build Composable Kernel targets
# Environment-agnostic: works natively on ROCm hosts or inside containers
set -e
set -o pipefail
# Find script directory and load common utilities
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/common.sh"
# Initialize configuration
PROJECT_ROOT=$(find_project_root "${SCRIPT_DIR}" || get_project_root "${SCRIPT_DIR}")
BUILD_DIR=$(get_build_dir "${PROJECT_ROOT}")
# Help message
show_help() {
cat << EOF
CK Build - Build Composable Kernel targets
Usage: ck-build [options] [target...]
Options:
-h, --help Show this help message
-j <N> Parallel jobs (passed to ninja)
-v, --verbose Verbose output
--build-dir <dir> Build directory (default: ./build)
--clean Clean before building
--configure Auto-configure if build.ninja missing
--list List available targets
Arguments:
target Target(s) to build (default: all)
Environment:
CK_BUILD_DIR - Override build directory
CK_GPU_TARGET - Override GPU target for auto-configure
Examples:
ck-build # Build all targets
ck-build test_amdgcn_mma # Build specific target
ck-build test_amdgcn_mma test_gemm # Build multiple targets
ck-build --configure # Auto-configure and build all
ck-build --clean test_amdgcn_mma # Clean and build target
ck-build -j 8 test_amdgcn_mma # Build with 8 parallel jobs
ck-build --list # List available targets
EOF
}
# Parse arguments
targets=()
parallel_jobs=""
verbose=false
clean=false
auto_configure=false
list_targets=false
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_help
exit 0
;;
-j)
require_arg "$1" "${2:-}"
parallel_jobs="$2"
shift 2
;;
-j*)
parallel_jobs="${1#-j}"
shift
;;
-v|--verbose)
verbose=true
shift
;;
--build-dir)
require_arg "$1" "${2:-}"
BUILD_DIR="$2"
shift 2
;;
--clean)
clean=true
shift
;;
--configure)
auto_configure=true
shift
;;
--list)
list_targets=true
shift
;;
*)
targets+=("$1")
shift
;;
esac
done
# Handle --list
if [ "$list_targets" = true ]; then
if ! is_build_configured "${BUILD_DIR}"; then
error "Build not configured. Run 'ck-configure' first or use --configure"
exit 1
fi
info "Available targets:"
cd "${BUILD_DIR}"
ninja -t targets 2>/dev/null | grep -E '^[a-zA-Z_][a-zA-Z0-9_-]*:' | cut -d: -f1 | sort | head -100
echo ""
echo "(Showing first 100 targets. Use 'ninja -t targets' for full list)"
exit 0
fi
# Auto-configure if needed
if ! is_build_configured "${BUILD_DIR}"; then
if [ "$auto_configure" = true ]; then
info "Build not configured. Running ck-configure..."
"${SCRIPT_DIR}/ck-configure" --build-dir "${BUILD_DIR}"
echo ""
else
error "Build not configured. Run 'ck-configure' first or use --configure"
exit 1
fi
fi
# Clean if requested
if [ "$clean" = true ]; then
info "Cleaning build directory..."
cd "${BUILD_DIR}"
ninja clean
echo ""
fi
# Build ninja command
ninja_cmd=(ninja -C "${BUILD_DIR}")
if [ -n "$parallel_jobs" ]; then
ninja_cmd+=("-j" "$parallel_jobs")
fi
if [ "$verbose" = true ]; then
ninja_cmd+=(-v)
fi
# Add targets
ninja_cmd+=("${targets[@]}")
# Build targets
if [ ${#targets[@]} -eq 0 ]; then
info "Building all configured targets..."
else
info "Building targets: ${targets[*]}"
fi
"${ninja_cmd[@]}"
echo ""
info "Build complete"

237
script/tools/ck-build-analysis Executable file
View File

@@ -0,0 +1,237 @@
#!/bin/bash
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
# CK Build Analysis Tool - Analyze build times using -ftime-trace
set -e
set -o pipefail
# Find script directory and load common utilities
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/common.sh"
# Initialize configuration
PROJECT_ROOT=$(get_project_root "${SCRIPT_DIR}")
CONTAINER_NAME=$(get_container_name "${PROJECT_ROOT}")
# Default settings
GRANULARITY="${CK_BUILD_ANALYSIS_GRANULARITY:-1}"
OUTPUT_FILE="build_time_analysis_report.md"
RECONFIGURE=true
# Help message
show_help() {
cat << EOF
CK Build Analysis - Analyze build times using Clang -ftime-trace
Usage: ck-build-analysis <target> [options]
Arguments:
target Build target to analyze (e.g., example_convnd_fwd_xdl_fp8)
Options:
--granularity=N Time trace granularity in microseconds (default: 1)
--output=FILE Output report filename (default: build_time_analysis_report.md)
--name=NAME Docker container name (default: ${CONTAINER_NAME})
--no-reconfigure Skip CMake reconfiguration if build exists
--help Show this help message
Examples:
ck-build-analysis example_convnd_fwd_xdl_fp8
ck-build-analysis example_convnd_fwd_xdl_fp8 --granularity=10
ck-build-analysis test_amdgcn_mma --granularity=1 --output=mma_test_analysis.md
Granularity Guide:
0 - Everything: All compiler events including sub-microsecond operations
Use for LLVM internals debugging. Large files, higher overhead.
1 (default) - Complete template coverage: Captures all template instantiations
Best balance - filters sub-microsecond noise, low overhead
10 - Daily use: Captures most expensive templates, smaller files
Good for quick checks and routine analysis
50-100 - Intermediate: Balanced between detail and file size
Suitable for CI/CD tracking
500 - High-level only: Major compilation phases, minimal detail
Not recommended for template analysis (loses most instantiations)
Recommendation: Use 1us (default) for template analysis, 10us for quick checks.
EOF
}
# Parse arguments
TARGET=""
while [[ $# -gt 0 ]]; do
case $1 in
--granularity=*)
GRANULARITY="${1#*=}"
shift
;;
--output=*)
OUTPUT_FILE="${1#*=}"
shift
;;
--name=*)
CONTAINER_NAME="${1#*=}"
shift
;;
--no-reconfigure)
RECONFIGURE=false
shift
;;
--help|-h)
show_help
exit 0
;;
-*)
echo "Unknown option: $1"
show_help
exit 1
;;
*)
if [ -z "$TARGET" ]; then
TARGET="$1"
else
echo "Error: Multiple targets specified"
show_help
exit 1
fi
shift
;;
esac
done
if [ -z "$TARGET" ]; then
echo "Error: No target specified"
echo ""
show_help
exit 1
fi
# Validate OUTPUT_FILE to prevent path traversal
if [[ "$OUTPUT_FILE" =~ / ]] || [[ "$OUTPUT_FILE" =~ \.\. ]]; then
echo "Error: OUTPUT_FILE must be a simple filename (no path separators or .. allowed)"
echo "Invalid: $OUTPUT_FILE"
exit 1
fi
echo "═══════════════════════════════════════════════════════════════"
echo " CK Build Time Analysis"
echo "═══════════════════════════════════════════════════════════════"
echo "Target: $TARGET"
echo "Granularity: ${GRANULARITY}us"
echo "Container: $CONTAINER_NAME"
echo "Output: $OUTPUT_FILE"
echo "═══════════════════════════════════════════════════════════════"
echo ""
# Ensure container is running
ensure_container_running "${CONTAINER_NAME}" "${SCRIPT_DIR}"
# Configure CMake with -ftime-trace if needed
if [ "$RECONFIGURE" = true ] || ! docker exec "${CONTAINER_NAME}" test -f /workspace/build/build.ninja 2>/dev/null; then
echo ""
echo "Configuring CMake with -ftime-trace (granularity=${GRANULARITY}us)..."
GPU_TARGET=$(detect_gpu_target "${CONTAINER_NAME}")
docker exec -e GPU_TARGET="${GPU_TARGET}" -e GRANULARITY="${GRANULARITY}" "${CONTAINER_NAME}" bash -c '
cd /workspace || exit 1
rm -rf /workspace/build
mkdir /workspace/build
cd /workspace/build || exit 1
cmake .. -GNinja \
-DGPU_TARGETS="${GPU_TARGET}" \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_CXX_COMPILER=/opt/rocm/llvm/bin/clang++ \
-DCMAKE_CXX_FLAGS="-ftime-trace -ftime-trace-granularity=${GRANULARITY}" \
-DCMAKE_HIP_FLAGS="-ftime-trace -ftime-trace-granularity=${GRANULARITY}" \
-DBUILD_TESTING=ON 2>&1 | tail -20
'
echo "CMake configuration complete"
fi
# Build the target
echo ""
echo "Building target: $TARGET"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
BUILD_START=$(date +%s)
docker exec -e TARGET="${TARGET}" "${CONTAINER_NAME}" bash -c 'cd /workspace/build && time ninja "${TARGET}" 2>&1'
BUILD_END=$(date +%s)
BUILD_TIME=$((BUILD_END - BUILD_START))
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Build completed in ${BUILD_TIME} seconds"
# Find all trace JSON files for the target
echo ""
echo "Locating trace files..."
# Count trace files
TRACE_COUNT=$(docker exec -e TARGET="${TARGET}" "${CONTAINER_NAME}" bash -c '
find /workspace/build -type f \( -name "*.cpp.json" -o -name "*.hip.json" \) 2>/dev/null | \
grep -vF "compile_commands.json" | wc -l
')
if [ "$TRACE_COUNT" -eq 0 ]; then
echo "Error: Could not find any trace files in /workspace/build"
echo "Expected .cpp.json or .hip.json files from -ftime-trace compilation"
exit 1
fi
echo "Found ${TRACE_COUNT} trace file(s) in build directory"
# We'll pass the build directory to the Python script
BUILD_DIR="/workspace/build"
# Generate analysis report
echo ""
echo "Generating analysis report..."
# Copy analysis script and templates to container
docker cp "${SCRIPT_DIR}/analyze_build_trace.py" "${CONTAINER_NAME}:/tmp/analyze_build_trace.py"
docker cp "${SCRIPT_DIR}/templates" "${CONTAINER_NAME}:/tmp/ck_build_analysis_templates"
# Check if uv is available, install if needed, and use for PEP 723 dependency management
if ! docker exec "${CONTAINER_NAME}" bash -c "command -v uv >/dev/null 2>&1 || test -x \$HOME/.local/bin/uv"; then
echo "uv not found, installing via pipx..."
docker exec "${CONTAINER_NAME}" bash -c "
# Install pipx if not available
if ! command -v pipx >/dev/null 2>&1; then
apt-get update -qq && apt-get install -y -qq pipx >/dev/null 2>&1
fi
# Install uv via pipx
pipx install uv >/dev/null 2>&1
"
echo "uv installed successfully"
fi
echo "Using uv run for automatic dependency management..."
# Ensure uv is in PATH (handles ~/.local/bin installation)
# Pass build directory instead of single file
docker exec -e BUILD_DIR="${BUILD_DIR}" -e OUTPUT_FILE="${OUTPUT_FILE}" -e TARGET="${TARGET}" -e GRANULARITY="${GRANULARITY}" -e BUILD_TIME="${BUILD_TIME}" "${CONTAINER_NAME}" bash -c 'export PATH="$HOME/.local/bin:$PATH" && uv run --no-project /tmp/analyze_build_trace.py "${BUILD_DIR}" "/workspace/${OUTPUT_FILE}" "${TARGET}" "${GRANULARITY}" "${BUILD_TIME}" /tmp/ck_build_analysis_templates'
# Copy report back to host
docker cp "${CONTAINER_NAME}:/workspace/${OUTPUT_FILE}" "${PROJECT_ROOT}/${OUTPUT_FILE}"
# Cleanup
docker exec "${CONTAINER_NAME}" rm -f /tmp/analyze_build_trace.py
docker exec "${CONTAINER_NAME}" rm -rf /tmp/ck_build_analysis_templates
echo ""
echo "═══════════════════════════════════════════════════════════════"
echo " Analysis Complete!"
echo "═══════════════════════════════════════════════════════════════"
echo "Report: ${PROJECT_ROOT}/${OUTPUT_FILE}"
echo ""
echo "Summary:"
docker exec "${CONTAINER_NAME}" bash -c "head -20 /workspace/${OUTPUT_FILE} | tail -10"
echo ""
echo "View the full report:"
echo " cat ${OUTPUT_FILE}"
echo " or open it in your editor"
echo "═══════════════════════════════════════════════════════════════"

113
script/tools/ck-clean Executable file
View File

@@ -0,0 +1,113 @@
#!/bin/bash
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
# CK Clean - Clean build artifacts in Docker container
set -e
set -o pipefail
# Find script directory and load common utilities
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/common.sh"
# Initialize configuration
PROJECT_ROOT=$(get_project_root "${SCRIPT_DIR}")
CONTAINER_NAME=$(get_container_name "${PROJECT_ROOT}")
# Help message
show_help() {
cat << EOF
CK Clean - Clean build artifacts in Docker container
Usage: ck-clean [options]
Options:
-h, --help Show this help message
--name <name> Specify container name
--all Remove entire build directory
-f, --force Force without confirmation
Environment:
CK_CONTAINER_NAME - Override default container name
Examples:
ck-clean # Clean build artifacts (ninja clean)
ck-clean --all # Remove entire build directory
ck-clean --force --all # Remove build directory without confirmation
EOF
}
# Parse arguments
remove_all=false
force=false
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_help
exit 0
;;
--name)
CONTAINER_NAME="$2"
shift 2
;;
--all)
remove_all=true
shift
;;
-f|--force)
force=true
shift
;;
*)
echo "Unknown option: $1"
show_help
exit 1
;;
esac
done
# Check if container is running
if ! container_is_running "${CONTAINER_NAME}"; then
echo "Container '${CONTAINER_NAME}' not running"
echo "Start with: ck-start"
exit 1
fi
# Check if build directory exists
if ! docker exec "${CONTAINER_NAME}" test -d /workspace/build 2>/dev/null; then
echo "Build directory does not exist"
exit 0
fi
if [ "$remove_all" = true ]; then
# Remove entire build directory
if [ "$force" = false ]; then
read -p "Remove entire build directory? (y/N) " -n 1 -r
echo ""
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "Cancelled"
exit 0
fi
fi
echo "Removing build directory..."
docker exec "${CONTAINER_NAME}" bash -c "rm -rf /workspace/build"
echo "Build directory removed ✓"
else
# Clean with ninja
if ! docker exec "${CONTAINER_NAME}" test -f /workspace/build/build.ninja 2>/dev/null; then
echo "Build not configured (build.ninja not found)"
echo "Use --all to remove build directory"
exit 1
fi
echo "Cleaning build artifacts..."
docker exec "${CONTAINER_NAME}" bash -c "
cd /workspace/build || exit 1
ninja clean
"
echo "Build artifacts cleaned ✓"
fi

187
script/tools/ck-configure Executable file
View File

@@ -0,0 +1,187 @@
#!/bin/bash
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
# CK Configure - Configure CMake build for Composable Kernel
# Environment-agnostic: works natively on ROCm hosts or inside containers
set -e
set -o pipefail
# Find script directory and load common utilities
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/common.sh"
# Initialize configuration
PROJECT_ROOT=$(find_project_root "${SCRIPT_DIR}" || get_project_root "${SCRIPT_DIR}")
BUILD_DIR=$(get_build_dir "${PROJECT_ROOT}")
# Help message
show_help() {
cat << EOF
CK Configure - Configure CMake build for Composable Kernel
Usage: ck-configure [options]
Options:
-h, --help Show this help message
--preset <name> Use CMake preset (dev, dev-gfx908, dev-gfx90a, dev-gfx942, dev-gfx950)
--gpu <target> Override GPU_TARGETS (auto-detected if not specified)
--dtypes <types> Set DTYPES (e.g., fp16,fp32,bf16)
--build-type <type> CMAKE_BUILD_TYPE (default: Release)
--build-dir <dir> Build directory (default: ./build)
--clean Remove existing build directory before configuring
--list-presets List available CMake presets
-D <VAR>=<value> Pass additional CMake variable
Environment:
CK_GPU_TARGET - Override GPU target detection (e.g., gfx950, gfx942)
CK_BUILD_DIR - Override build directory
Examples:
ck-configure # Auto-detect GPU and configure
ck-configure --preset dev-gfx950 # Use CMake preset
ck-configure --gpu gfx942 # Configure for specific GPU
ck-configure --clean --preset dev # Clean and reconfigure
ck-configure -D BUILD_DEV=ON # Pass CMake variable
EOF
}
# Parse arguments
preset=""
gpu_target=""
dtypes=""
build_type="Release"
clean=false
list_presets=false
cmake_vars=()
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_help
exit 0
;;
--preset)
require_arg "$1" "${2:-}"
preset="$2"
shift 2
;;
--gpu)
require_arg "$1" "${2:-}"
gpu_target="$2"
shift 2
;;
--dtypes)
require_arg "$1" "${2:-}"
dtypes="$2"
shift 2
;;
--build-type)
require_arg "$1" "${2:-}"
build_type="$2"
shift 2
;;
--build-dir)
require_arg "$1" "${2:-}"
BUILD_DIR="$2"
shift 2
;;
--clean)
clean=true
shift
;;
--list-presets)
list_presets=true
shift
;;
-D)
require_arg "$1" "${2:-}"
cmake_vars+=("-D$2")
shift 2
;;
-D*)
cmake_vars+=("$1")
shift
;;
*)
error "Unknown option: $1"
echo ""
show_help
exit 1
;;
esac
done
# Handle --list-presets
if [ "$list_presets" = true ]; then
echo "Available CMake presets:"
presets=$(list_cmake_presets "${PROJECT_ROOT}" 2>/dev/null)
if [ -n "$presets" ]; then
echo "$presets" | sed 's/^/ /'
else
echo " (No CMakePresets.json found or jq not available)"
fi
exit 0
fi
# Clean build directory if requested
if [ "$clean" = true ]; then
if [ -d "${BUILD_DIR}" ]; then
info "Removing existing build directory: ${BUILD_DIR}"
rm -rf "${BUILD_DIR}"
fi
fi
# Create build directory
mkdir -p "${BUILD_DIR}"
# Change to project root for CMake
cd "${PROJECT_ROOT}"
# Build CMake command
cmake_cmd=(cmake -S . -B "${BUILD_DIR}" -GNinja)
# Use preset if specified
if [ -n "$preset" ]; then
cmake_cmd+=(--preset "${preset}")
info "Using CMake preset: ${preset}"
else
# Manual configuration
# Detect GPU target if not specified
if [ -z "$gpu_target" ]; then
gpu_target=$(detect_gpu_native)
info "Auto-detected GPU target: ${gpu_target}"
else
info "Using specified GPU target: ${gpu_target}"
fi
cmake_cmd+=(-DGPU_TARGETS="${gpu_target}")
cmake_cmd+=(-DCMAKE_BUILD_TYPE="${build_type}")
cmake_cmd+=(-DCMAKE_CXX_COMPILER=/opt/rocm/llvm/bin/clang++)
cmake_cmd+=(-DBUILD_TESTING=ON)
# Add DTYPES if specified
if [ -n "$dtypes" ]; then
cmake_cmd+=(-DDTYPES="${dtypes}")
info "Using DTYPES: ${dtypes}"
fi
fi
# Add any additional CMake variables
for var in "${cmake_vars[@]}"; do
cmake_cmd+=("$var")
done
# Run CMake
info "Configuring build in: ${BUILD_DIR}"
echo "Running: ${cmake_cmd[*]}"
echo ""
"${cmake_cmd[@]}"
echo ""
info "Configuration complete. Build directory: ${BUILD_DIR}"
info "Next: run 'ck-build' to build targets"

231
script/tools/ck-docker Executable file
View File

@@ -0,0 +1,231 @@
#!/bin/bash
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
# CK Docker Tool - Build and test composable_kernel in Docker with ROCm support
set -e
set -o pipefail
# Find script directory and load common utilities
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/common.sh"
# Initialize configuration
PROJECT_ROOT=$(get_project_root "${SCRIPT_DIR}")
CONTAINER_NAME=$(get_container_name "${PROJECT_ROOT}")
# Help message
show_help() {
cat << EOF
CK Docker Tool - Build and test composable_kernel in Docker
Usage: ck-docker <command> [options]
Container Management:
start [name] Start Docker container
stop [name] Stop and remove container
status [name] Check container status
shell [name] Open shell in container
Build/Test (delegates to core tools inside container):
configure [opts] Run ck-configure in container
build [opts] Run ck-build in container
test [opts] Run ck-test in container
exec <cmd> Run arbitrary command in container
Examples:
ck-docker start
ck-docker configure --preset dev-gfx950
ck-docker build test_amdgcn_mma
ck-docker test test_amdgcn_mma --filter '*Fp16*'
ck-docker shell
ck-docker exec rocminfo
Environment:
CK_CONTAINER_NAME - Override default container name (default: ck_<username>_<branch>)
CK_DOCKER_IMAGE - Override Docker image (default: rocm/composable_kernel:ck_ub24.04_rocm7.0.1)
EOF
}
# Start container
cmd_start() {
local name="${1:-${CONTAINER_NAME}}"
local docker_image=$(get_docker_image)
# Check if container exists and is running
if container_exists "${name}"; then
if container_is_running "${name}"; then
echo "Container '${name}' is already running"
return 0
else
echo "Starting existing container '${name}'..."
docker start "${name}"
echo "Container started"
return 0
fi
fi
echo "Creating new Docker container '${name}'..."
docker run -d \
--name "${name}" \
--device=/dev/kfd --device=/dev/dri \
--security-opt seccomp=unconfined \
--group-add video \
-v "${PROJECT_ROOT}":/workspace \
-w /workspace \
"${docker_image}" \
tail -f /dev/null
echo "Container '${name}' started successfully"
docker exec "${name}" bash -c "echo 'Working directory:' && pwd"
}
# Configure (delegate to ck-configure in container)
cmd_configure() {
ensure_container_running "${CONTAINER_NAME}" "${SCRIPT_DIR}"
docker exec "${CONTAINER_NAME}" /workspace/script/tools/ck-configure "$@"
}
# Build (delegate to ck-build in container)
cmd_build() {
ensure_container_running "${CONTAINER_NAME}" "${SCRIPT_DIR}"
docker exec "${CONTAINER_NAME}" /workspace/script/tools/ck-build "$@"
}
# Test (delegate to ck-test in container)
cmd_test() {
ensure_container_running "${CONTAINER_NAME}" "${SCRIPT_DIR}"
docker exec "${CONTAINER_NAME}" /workspace/script/tools/ck-test "$@"
}
# Execute arbitrary command in container
cmd_exec() {
if [ $# -eq 0 ]; then
error "command required"
echo "Usage: ck-docker exec <command>"
return 1
fi
ensure_container_running "${CONTAINER_NAME}" "${SCRIPT_DIR}"
local docker_flags=()
[ -t 0 ] && [ -t 1 ] && docker_flags+=("-it")
# Auto-detect Python commands and enable unbuffered output for live streaming
local is_python=false
for arg in "$@"; do
if [[ "$arg" == "python" || "$arg" == "python3" || "$arg" == *.py ]]; then
is_python=true
break
fi
done
if [ "$is_python" = true ]; then
docker_flags+=("-e" "PYTHONUNBUFFERED=1")
fi
docker exec "${docker_flags[@]}" "${CONTAINER_NAME}" "$@"
}
# Shell
cmd_shell() {
local name="${1:-${CONTAINER_NAME}}"
# Check if container is running
if ! container_is_running "${name}"; then
echo "Container '${name}' not running. Starting..."
cmd_start "${name}"
fi
echo "Opening shell in '${name}' (type 'exit' to leave)..."
docker exec -it "${name}" bash
}
# Status
cmd_status() {
local name="${1:-}"
local docker_image=$(get_docker_image)
if [ -z "$name" ]; then
echo "Composable Kernel Docker Containers:"
echo "---"
docker ps -a --filter "ancestor=${docker_image}" \
--format "table {{.Names}}\t{{.Status}}\t{{.CreatedAt}}" || echo "No containers found"
else
# Check container status
if container_is_running "${name}"; then
echo "Container '${name}' is RUNNING"
docker ps --filter "name=^${name}$" --format "table {{.Names}}\t{{.Status}}\t{{.Image}}"
echo ""
echo "GPU Information:"
docker exec "${name}" bash -c "rocm-smi --showproductname 2>/dev/null | head -10 || echo 'No GPU detected'"
elif container_exists "${name}"; then
echo "Container '${name}' exists but is STOPPED"
echo "Start with: ck-docker start ${name}"
else
echo "Container '${name}' does NOT exist"
echo "Create with: ck-docker start ${name}"
fi
fi
}
# Stop
cmd_stop() {
local name="${1:-${CONTAINER_NAME}}"
# Check if container exists
if container_exists "${name}"; then
echo "Stopping and removing container '${name}'..."
docker stop "${name}" 2>/dev/null || true
docker rm "${name}" 2>/dev/null || true
echo "Container stopped and removed"
else
echo "Container '${name}' does not exist"
fi
}
# Main command dispatcher
case "${1:-}" in
start)
shift
cmd_start "$@"
;;
configure)
shift
cmd_configure "$@"
;;
build)
shift
cmd_build "$@"
;;
test)
shift
cmd_test "$@"
;;
exec)
shift
cmd_exec "$@"
;;
shell)
shift
cmd_shell "$@"
;;
status)
shift
cmd_status "$@"
;;
stop)
shift
cmd_stop "$@"
;;
help|--help|-h)
show_help
;;
*)
echo "Unknown command: ${1:-}"
echo ""
show_help
exit 1
;;
esac

120
script/tools/ck-exec Executable file
View File

@@ -0,0 +1,120 @@
#!/bin/bash
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
# CK Exec - Execute arbitrary commands in Docker container
set -e
set -o pipefail
# Find script directory and load common utilities
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/common.sh"
# Initialize configuration
PROJECT_ROOT=$(get_project_root "${SCRIPT_DIR}")
CONTAINER_NAME=$(get_container_name "${PROJECT_ROOT}")
# Help message
show_help() {
cat << EOF
CK Exec - Execute arbitrary commands in Docker container
Usage: ck-exec [options] <command> [args...]
Options:
-h, --help Show this help message
--name <name> Specify container name
-w <dir> Working directory (default: /workspace)
-i, --interactive Interactive mode (allocate TTY)
Arguments:
command Command to execute (required)
args Arguments to the command
Environment:
CK_CONTAINER_NAME - Override default container name
Examples:
ck-exec rocm-smi # Run rocm-smi
ck-exec rocminfo # Run rocminfo
ck-exec ls -la build/bin # List build binaries
ck-exec -w /workspace/build ninja -t commands # Run ninja commands
ck-exec --interactive python3 # Interactive Python session
Common Commands:
ck-exec rocm-smi # Check GPU status
ck-exec rocminfo \| grep gfx # Check GPU architecture
ck-exec hipcc --version # Check HIP compiler version
ck-exec cmake --version # Check CMake version
ck-exec ninja -C build -t targets # List all build targets
EOF
}
# Parse arguments
workdir="/workspace"
interactive=false
command_args=()
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_help
exit 0
;;
--name)
CONTAINER_NAME="$2"
shift 2
;;
-w)
workdir="$2"
shift 2
;;
-i|--interactive)
interactive=true
shift
;;
*)
command_args+=("$1")
shift
;;
esac
done
# Validate command
if [ ${#command_args[@]} -eq 0 ]; then
echo "Error: command required"
echo ""
show_help
exit 1
fi
# Ensure container is running
if ! container_is_running "${CONTAINER_NAME}"; then
echo "Container '${CONTAINER_NAME}' not running. Starting..."
"${SCRIPT_DIR}/ck-start" "${CONTAINER_NAME}"
echo ""
fi
# Build command string
cmd_string=""
for arg in "${command_args[@]}"; do
cmd_string="${cmd_string} $(printf '%q' "$arg")"
done
# Auto-detect Python commands and enable unbuffered output for live streaming
env_flags=""
for arg in "${command_args[@]}"; do
if [[ "$arg" == "python" || "$arg" == "python3" || "$arg" == *.py ]]; then
env_flags="-e PYTHONUNBUFFERED=1"
break
fi
done
# Execute command
if [ "$interactive" = true ]; then
docker exec -it ${env_flags} -w "${workdir}" "${CONTAINER_NAME}" bash -c "${cmd_string}"
else
docker exec ${env_flags} -w "${workdir}" "${CONTAINER_NAME}" bash -c "${cmd_string}"
fi

134
script/tools/ck-logs Executable file
View File

@@ -0,0 +1,134 @@
#!/bin/bash
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
# CK Logs - View container logs and build output
set -e
set -o pipefail
# Find script directory and load common utilities
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/common.sh"
# Initialize configuration
PROJECT_ROOT=$(get_project_root "${SCRIPT_DIR}")
CONTAINER_NAME=$(get_container_name "${PROJECT_ROOT}")
# Help message
show_help() {
cat << EOF
CK Logs - View container logs and build output
Usage: ck-logs [options] [container_name]
Options:
-h, --help Show this help message
--name <name> Specify container name
-f, --follow Follow log output
-n, --tail <N> Show last N lines (default: 100)
--cmake Show CMake configuration log
--build Show last build log
Arguments:
container_name Optional container name (default: ck_<username>_<branch>)
Environment:
CK_CONTAINER_NAME - Override default container name
Examples:
ck-logs # Show last 100 lines of container logs
ck-logs -f # Follow container logs
ck-logs -n 500 # Show last 500 lines
ck-logs --cmake # Show CMake configuration
ck-logs --build # Show build log
EOF
}
# Parse arguments
follow=false
tail_lines=100
show_cmake=false
show_build=false
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_help
exit 0
;;
--name)
CONTAINER_NAME="$2"
shift 2
;;
-f|--follow)
follow=true
shift
;;
-n|--tail)
tail_lines="$2"
shift 2
;;
--cmake)
show_cmake=true
shift
;;
--build)
show_build=true
shift
;;
*)
CONTAINER_NAME="$1"
shift
;;
esac
done
# Check if container exists
if ! container_exists "${CONTAINER_NAME}"; then
echo "Container '${CONTAINER_NAME}' does not exist"
exit 1
fi
# Show CMake log
if [ "$show_cmake" = true ]; then
echo "CMake Configuration Log:"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
if docker exec "${CONTAINER_NAME}" test -f /workspace/build/CMakeCache.txt 2>/dev/null; then
docker exec "${CONTAINER_NAME}" bash -c "
cd /workspace/build
echo 'GPU_TARGETS:' \$(grep 'GPU_TARGETS:' CMakeCache.txt | cut -d'=' -f2)
echo 'CMAKE_BUILD_TYPE:' \$(grep 'CMAKE_BUILD_TYPE:' CMakeCache.txt | cut -d'=' -f2)
echo 'CMAKE_CXX_COMPILER:' \$(grep 'CMAKE_CXX_COMPILER:' CMakeCache.txt | cut -d'=' -f2)
echo 'BUILD_TESTING:' \$(grep 'BUILD_TESTING:' CMakeCache.txt | cut -d'=' -f2)
"
else
echo "CMake not configured yet"
fi
exit 0
fi
# Show build log (last build output)
if [ "$show_build" = true ]; then
echo "Last Build Log:"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
if docker exec "${CONTAINER_NAME}" test -f /workspace/build/.ninja_log 2>/dev/null; then
docker exec "${CONTAINER_NAME}" bash -c "tail -50 /workspace/build/.ninja_log"
else
echo "No build log found"
fi
exit 0
fi
# Show container logs
echo "Container Logs (${CONTAINER_NAME}):"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
if [ "$follow" = true ]; then
docker logs -f "${CONTAINER_NAME}"
else
docker logs --tail "${tail_lines}" "${CONTAINER_NAME}"
fi

806
script/tools/ck-rocprof Executable file
View File

@@ -0,0 +1,806 @@
#!/bin/bash
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
# CK ROCProf Tool - Profile CK applications with rocprof-compute
# Native-only tool. For Docker usage, run via: ck-docker exec ck-rocprof ...
set -e
set -o pipefail
# Find script directory and load common utilities
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/common.sh"
# Initialize configuration
PROJECT_ROOT=$(find_project_root "${SCRIPT_DIR}" || get_project_root "${SCRIPT_DIR}")
# ============================================================================
# rocprof-compute detection
# ============================================================================
# Common rocprof-compute binary locations
# Order: user installs first, then system ROCm versions (newest first)
ROCPROF_CANDIDATES=(
"${HOME}/.local/rocprofiler-compute/3.4.0/bin/rocprof-compute"
"/opt/rocm/bin/rocprof-compute"
"/opt/rocm-7.2.0/bin/rocprof-compute"
"/opt/rocm-7.0.1/bin/rocprof-compute"
"/opt/rocm-6.2.0/bin/rocprof-compute"
"/opt/rocm-6.1.0/bin/rocprof-compute"
)
# Find rocprof-compute binary
find_rocprof_bin() {
# Check CK_ROCPROF_BIN first
if [ -n "${CK_ROCPROF_BIN:-}" ] && [ -f "${CK_ROCPROF_BIN}" ]; then
echo "${CK_ROCPROF_BIN}"
return 0
fi
# Check PATH
if command -v rocprof-compute &>/dev/null; then
command -v rocprof-compute
return 0
fi
# Check common ROCm locations and user installations
for bin in "${ROCPROF_CANDIDATES[@]}"; do
if [ -f "$bin" ]; then
echo "$bin"
return 0
fi
done
return 1
}
# Find ROCm requirements file
find_rocm_requirements() {
local rocprof_bin="${1:-$(find_rocprof_bin)}"
if [ -z "$rocprof_bin" ]; then
return 1
fi
# Requirements file is typically at ../libexec/rocprofiler-compute/requirements.txt
local rocm_dir
rocm_dir=$(dirname "$(dirname "$rocprof_bin")")
local req_file="${rocm_dir}/libexec/rocprofiler-compute/requirements.txt"
if [ -f "$req_file" ]; then
echo "$req_file"
return 0
fi
return 1
}
# ============================================================================
# Configuration
# ============================================================================
ROCPROF_BIN="${CK_ROCPROF_BIN:-$(find_rocprof_bin || echo "")}"
VENV_PATH="${CK_PROFILE_VENV:-${PROJECT_ROOT}/.ck-rocprof-venv}"
WORKLOAD_DIR="${CK_WORKLOAD_DIR:-$(get_build_dir "${PROJECT_ROOT}")/workloads}"
ROCM_REQUIREMENTS="${CK_ROCM_REQUIREMENTS:-$(find_rocm_requirements "${ROCPROF_BIN}" || echo "")}"
# ============================================================================
# Helper functions
# ============================================================================
# Get file/directory size
get_size() {
local path="$1"
du -sh "$path" 2>/dev/null | cut -f1
}
# Get file modification date (cross-platform: Linux and macOS)
get_date() {
local path="$1"
# Try GNU stat first (Linux), fall back to BSD stat (macOS)
if stat --version &>/dev/null 2>&1; then
stat -c %y "$path" 2>/dev/null | cut -d' ' -f1
else
stat -f %Sm -t %Y-%m-%d "$path" 2>/dev/null
fi
}
# Help message
show_help() {
cat << EOF
CK ROCProf Tool - Profile CK applications with rocprof-compute
Usage: ck-rocprof <command> [options]
Commands:
setup One-time setup: create Python venv and install dependencies
run <name> <executable> [args] Profile executable and save results as <name>
analyze <name> [block] Analyze profiling results (default: block 12 - LDS metrics)
compare <name1> <name2> Compare two profiling runs
list List available profiling runs
clean <name> Remove a profiling run (use --all for all runs)
status Show current configuration and status
help Show this help message
Examples:
ck-rocprof setup
ck-rocprof run baseline ./bin/tile_example_gemm_universal
ck-rocprof analyze baseline
ck-rocprof analyze baseline 12
ck-rocprof compare baseline optimized
ck-rocprof list
ck-rocprof clean baseline
ck-rocprof status
Environment Variables:
CK_GPU_TARGET - Override GPU detection (e.g., gfx950, MI300X)
CK_PROFILE_VENV - Python venv path (default: \$PROJECT/.ck-rocprof-venv)
CK_ROCPROF_BIN - rocprof-compute binary path
CK_ROCM_REQUIREMENTS - Path to rocprofiler-compute requirements.txt
CK_WORKLOAD_DIR - Workload storage directory
Profiling Blocks (use with 'analyze <name> <block>'):
Block 2: System Speed-of-Light (SOL)
Block 6: Shader Engine (SE) utilization
Block 7: L2 Cache metrics
Block 11: Vector L1D Cache metrics
Block 12: LDS (Local Data Share) - DEFAULT
Block 16: Instruction mix statistics
Block 17: Compute Unit (CU) metrics
LDS Metrics (Block 12):
- 12.1.3: Bank Conflict Rate (% of peak)
- 12.2.9: Bank Conflicts/Access (conflicts/access)
- 12.2.12: Bank Conflict (cycles per kernel)
- 12.2.17: LDS Data FIFO Full Rate (cycles)
Notes:
- Workload names must be alphanumeric with hyphens/underscores only
- Profiling skips roofline analysis (--no-roof) for faster execution
- Results stored in workloads/<name>/
- For Docker usage, run via: ck-docker exec ck-rocprof ...
EOF
}
# Get rocprof-compute wrapper path
get_rocprof_wrapper() {
echo "${VENV_PATH}/bin/rocprof-compute"
}
# Validate workload name to prevent path traversal and shell injection
# Allowed: alphanumeric, hyphens, underscores
validate_workload_name() {
local name="$1"
if [[ ! "$name" =~ ^[a-zA-Z0-9_-]+$ ]]; then
error "Invalid workload name: '$name'"
echo "Names must contain only letters, numbers, hyphens, and underscores"
return 1
fi
# Prevent reserved names
if [[ "$name" == "." || "$name" == ".." ]]; then
error "Invalid workload name: '$name'"
return 1
fi
return 0
}
# Check if setup is complete
is_setup_complete() {
local wrapper
wrapper=$(get_rocprof_wrapper)
[ -d "${VENV_PATH}" ] && [ -f "${wrapper}" ]
}
# ============================================================================
# Source installation
# ============================================================================
# rocprofiler-compute source installation location
ROCPROF_SOURCE_VERSION="3.4.0"
ROCPROF_SOURCE_DIR="${HOME}/.local/rocprofiler-compute/${ROCPROF_SOURCE_VERSION}"
ROCPROF_SOURCE_BIN="${ROCPROF_SOURCE_DIR}/bin/rocprof-compute"
ROCPROF_REPO_URL="https://github.com/ROCm/rocprofiler-compute.git"
ROCPROF_REPO_BRANCH="release/rocprofiler-compute-v${ROCPROF_SOURCE_VERSION}"
# Install rocprofiler-compute from source
install_from_source() {
local install_dir="${ROCPROF_SOURCE_DIR}"
local src_dir="${install_dir}/src"
info "Installing rocprofiler-compute ${ROCPROF_SOURCE_VERSION} from source..."
echo "Install location: ${install_dir}"
echo ""
# Ensure uv is available
if ! command -v uv &>/dev/null; then
info "Installing uv package manager via pip..."
if ! python3 -m pip install --user uv; then
error "Failed to install uv package manager"
return 1
fi
export PATH="${HOME}/.local/bin:${PATH}"
if ! command -v uv &>/dev/null; then
error "uv installed but not found in PATH"
return 1
fi
fi
# Create installation directory
mkdir -p "${install_dir}"
# Clone repository
if [ -d "${src_dir}" ]; then
info "Source already exists, updating..."
git -C "${src_dir}" fetch --quiet
git -C "${src_dir}" checkout --quiet "${ROCPROF_REPO_BRANCH}" 2>/dev/null || \
git -C "${src_dir}" checkout --quiet "amd-mainline"
else
info "Cloning rocprofiler-compute repository..."
if ! git clone --quiet --branch "${ROCPROF_REPO_BRANCH}" --depth 1 "${ROCPROF_REPO_URL}" "${src_dir}" 2>/dev/null; then
# Fall back to amd-mainline if release branch doesn't exist
info "Release branch not found, using amd-mainline..."
git clone --quiet --branch "amd-mainline" --depth 1 "${ROCPROF_REPO_URL}" "${src_dir}"
fi
fi
# Create venv for source installation
local venv_dir="${install_dir}/venv"
if [ ! -d "${venv_dir}" ]; then
info "Creating Python virtual environment..."
uv venv "${venv_dir}"
fi
# Install dependencies from requirements.txt
info "Installing dependencies (this may take a minute)..."
uv pip install --python "${venv_dir}/bin/python" -r "${src_dir}/requirements.txt" --quiet
# Pin pandas to avoid CSV conversion bug
uv pip install --python "${venv_dir}/bin/python" 'pandas<3.0' --quiet
# Create bin directory and wrapper script
mkdir -p "${install_dir}/bin"
cat > "${ROCPROF_SOURCE_BIN}" << 'WRAPPER_EOF'
#!/bin/bash
# rocprof-compute wrapper for source installation
INSTALL_DIR="$(cd "$(dirname "$0")/.." && pwd)"
SRC_DIR="${INSTALL_DIR}/src/src"
VENV_DIR="${INSTALL_DIR}/venv"
# Set PYTHONPATH to source directory for module imports
export PYTHONPATH="${SRC_DIR}:${PYTHONPATH}"
# Execute rocprof-compute script with venv Python
exec "${VENV_DIR}/bin/python3" "${SRC_DIR}/rocprof-compute" "$@"
WRAPPER_EOF
chmod +x "${ROCPROF_SOURCE_BIN}"
info "rocprofiler-compute installed successfully!"
echo " Binary: ${ROCPROF_SOURCE_BIN}"
echo ""
}
# ============================================================================
# Commands
# ============================================================================
# Setup: Create Python venv and install rocprof-compute dependencies
cmd_setup() {
echo "Setting up rocprof-compute profiling environment..."
echo "==========================================="
# Check if rocprof-compute exists, install from source if not
if [ -z "${ROCPROF_BIN}" ] || [ ! -f "${ROCPROF_BIN}" ]; then
warn "rocprof-compute not found in standard locations"
echo ""
echo "Searched locations:"
for bin in "${ROCPROF_CANDIDATES[@]}"; do
echo " - $bin"
done
echo ""
# Check if we can install from source
if ! command -v git &>/dev/null; then
error "git is required to install from source"
return 1
fi
if ! command -v python3 &>/dev/null; then
error "python3 is required to install from source"
return 1
fi
echo "Installing rocprofiler-compute from source..."
echo ""
if ! install_from_source; then
error "Failed to install rocprofiler-compute from source"
return 1
fi
# Update configuration with source installation
ROCPROF_BIN="${ROCPROF_SOURCE_BIN}"
ROCM_REQUIREMENTS="${ROCPROF_SOURCE_DIR}/libexec/rocprofiler-compute/requirements.txt"
fi
info "Using rocprof-compute: ${ROCPROF_BIN}"
# Check requirements file (only needed for non-source installs that use separate venv)
if [ -z "${ROCM_REQUIREMENTS}" ] || [ ! -f "${ROCM_REQUIREMENTS}" ]; then
# For source installs, requirements are bundled
if [[ "${ROCPROF_BIN}" == "${ROCPROF_SOURCE_BIN}" ]]; then
ROCM_REQUIREMENTS="${ROCPROF_SOURCE_DIR}/libexec/rocprofiler-compute/requirements.txt"
else
error "ROCm requirements file not found"
local expected_path
expected_path="$(dirname "$(dirname "${ROCPROF_BIN}")")/libexec/rocprofiler-compute/requirements.txt"
echo "Expected at: ${expected_path}"
echo "Set CK_ROCM_REQUIREMENTS to override"
return 1
fi
fi
# Check GPU access
if [ ! -r /dev/kfd ]; then
warn "No read access to /dev/kfd - GPU profiling may fail"
warn "Add user to video/render group: sudo usermod -a -G video,render \$USER"
fi
# For source installations, the venv is already set up - just create wrapper
if [[ "${ROCPROF_BIN}" == "${ROCPROF_SOURCE_BIN}" ]]; then
# Source install already has everything set up
local wrapper
wrapper=$(get_rocprof_wrapper)
mkdir -p "$(dirname "${wrapper}")"
# For source install, wrapper just calls the source binary
cat > "${wrapper}" << WRAPPER_EOF
#!/bin/bash
# rocprof-compute wrapper (using source installation)
exec "${ROCPROF_BIN}" "\$@"
WRAPPER_EOF
chmod +x "${wrapper}"
info "Wrapper created at ${wrapper}"
# Create marker file for venv directory
mkdir -p "${VENV_PATH}/bin"
touch "${VENV_PATH}/.source-install"
else
# System install - need to set up venv with dependencies
# Install uv if needed
if ! command -v uv &>/dev/null; then
info "Installing uv package manager via pip..."
if ! python3 -m pip install --user uv; then
error "Failed to install uv package manager"
return 1
fi
export PATH="${HOME}/.local/bin:${PATH}"
if ! command -v uv &>/dev/null; then
error "uv installed but not found in PATH"
echo "Try adding ~/.local/bin to your PATH"
return 1
fi
fi
# Create venv
if [ -d "${VENV_PATH}" ]; then
info "Python venv already exists at ${VENV_PATH}"
else
info "Creating Python venv at ${VENV_PATH}..."
uv venv "${VENV_PATH}"
fi
# Install dependencies
info "Installing dependencies..."
uv pip install --python "${VENV_PATH}/bin/python" -r "${ROCM_REQUIREMENTS}"
uv pip install --python "${VENV_PATH}/bin/python" 'pandas<3.0'
# Create wrapper script
local wrapper
wrapper=$(get_rocprof_wrapper)
mkdir -p "$(dirname "${wrapper}")"
cat > "${wrapper}" << WRAPPER_EOF
#!/bin/bash
# rocprof-compute wrapper using venv Python
VENV_DIR="\$(cd "\$(dirname "\$0")/.." && pwd)"
exec "\${VENV_DIR}/bin/python" "${ROCPROF_BIN}" "\$@"
WRAPPER_EOF
chmod +x "${wrapper}"
info "Wrapper created at ${wrapper}"
fi
# Create workload directory
mkdir -p "${WORKLOAD_DIR}"
info "Workload directory: ${WORKLOAD_DIR}"
echo ""
info "Setup complete! You can now use:"
echo " ck-rocprof run <name> <executable>"
}
# Detect GPU architecture
detect_gpu_arch() {
# Allow override via environment variable
if [ -n "${CK_GPU_TARGET:-}" ]; then
echo "${CK_GPU_TARGET}"
return 0
fi
if command -v rocminfo &>/dev/null; then
# Try marketing name first (MI350, MI300X)
local marketing_name
marketing_name=$(rocminfo 2>/dev/null | grep 'Marketing Name:' | grep -oE 'MI[0-9]+[A-Z]*' | head -1)
if [ -n "$marketing_name" ]; then
echo "$marketing_name"
return 0
fi
# Fallback to gfx name
local gfx_name
gfx_name=$(rocminfo 2>/dev/null | grep -oE 'gfx[0-9a-z]+' | head -1)
if [ -n "$gfx_name" ]; then
echo "$gfx_name"
return 0
fi
fi
# Try existing workload directories
if [ -d "${WORKLOAD_DIR}" ]; then
local first_dir
first_dir=$(find "${WORKLOAD_DIR}" -maxdepth 2 -type d \( -name 'gfx*' -o -name 'MI*' \) 2>/dev/null | head -1)
if [ -n "$first_dir" ]; then
basename "$first_dir"
return 0
fi
fi
# Final fallback - use gfx950 consistent with common.sh
echo "gfx950"
}
# Run profiling
cmd_run() {
# Validate argument count before shifting
if [ $# -lt 2 ]; then
error "name and executable required"
echo "Usage: ck-rocprof run <name> <executable> [args]"
return 1
fi
local name="$1"
local executable="$2"
shift 2
local -a exe_args=("$@")
# Validate workload name (prevents path traversal)
if ! validate_workload_name "$name"; then
return 1
fi
# Check setup
if ! is_setup_complete; then
error "Profiling environment not set up"
echo "Run: ck-rocprof setup"
return 1
fi
# Check if executable exists
if [ ! -f "$executable" ]; then
error "Executable not found: $executable"
return 1
fi
local wrapper
wrapper=$(get_rocprof_wrapper)
local gpu_arch
gpu_arch=$(detect_gpu_arch)
echo "Profiling: $executable ${exe_args[*]}"
echo "Run name: $name"
echo "GPU arch: $gpu_arch"
echo "==========================================="
# Build command with proper escaping to prevent shell injection
# --no-roof skips roofline analysis to speed up profiling
local escaped_executable
escaped_executable=$(printf '%q' "$executable")
local escaped_workload_dir
escaped_workload_dir=$(printf '%q' "${WORKLOAD_DIR}/${name}")
local cmd="${wrapper} profile --no-roof --path ${escaped_workload_dir} --name ${name} -- ${escaped_executable}"
for arg in "${exe_args[@]}"; do
cmd="${cmd} $(printf '%q' "$arg")"
done
# Run profiling
bash -c "${cmd}"
echo ""
info "Profiling complete"
echo "Results saved to: ${WORKLOAD_DIR}/${name}/"
echo ""
echo "Analyze with: ck-rocprof analyze ${name}"
}
# Find workload path for a given run name
find_workload_path() {
local name="$1"
local run_dir="${WORKLOAD_DIR}/${name}"
if [ ! -d "$run_dir" ]; then
return 1
fi
# Check if profiling data exists
if [ -f "${run_dir}/pmc_perf.csv" ]; then
echo "$run_dir"
return 0
fi
return 1
}
# Analyze profiling results
cmd_analyze() {
local name="$1"
local block="${2:-12}" # Default to block 12 (LDS metrics)
if [ -z "$name" ]; then
error "name required"
echo "Usage: ck-rocprof analyze <name> [block]"
return 1
fi
# Validate workload name (prevents path traversal)
if ! validate_workload_name "$name"; then
return 1
fi
# Check setup
if ! is_setup_complete; then
error "Profiling environment not set up"
echo "Run: ck-rocprof setup"
return 1
fi
local wrapper
wrapper=$(get_rocprof_wrapper)
local workload_path
workload_path=$(find_workload_path "${name}")
if [ -z "$workload_path" ]; then
error "Profiling results not found for '${name}'"
echo ""
echo "Available runs:"
cmd_list
return 1
fi
echo "Analyzing: ${name} (Block ${block})"
echo "==========================================="
echo ""
"${wrapper}" analyze --path "${workload_path}" --block "${block}"
}
# Compare two profiling runs
cmd_compare() {
local name1="$1"
local name2="$2"
if [ -z "$name1" ] || [ -z "$name2" ]; then
error "two run names required"
echo "Usage: ck-rocprof compare <name1> <name2>"
return 1
fi
# Validate workload names (prevents path traversal)
if ! validate_workload_name "$name1"; then
return 1
fi
if ! validate_workload_name "$name2"; then
return 1
fi
# Check setup
if ! is_setup_complete; then
error "Profiling environment not set up"
echo "Run: ck-rocprof setup"
return 1
fi
# Verify both runs exist
local path1
path1=$(find_workload_path "${name1}")
local path2
path2=$(find_workload_path "${name2}")
if [ -z "$path1" ]; then
error "Profiling results not found for '${name1}'"
return 1
fi
if [ -z "$path2" ]; then
error "Profiling results not found for '${name2}'"
return 1
fi
echo "Comparing profiling runs:"
echo " Baseline: ${name1}"
echo " Optimized: ${name2}"
echo "==========================================="
echo ""
echo "=== ${name1} - Block 12 (LDS) ==="
cmd_analyze "${name1}" 12 2>/dev/null | head -40
echo ""
echo "=== ${name2} - Block 12 (LDS) ==="
cmd_analyze "${name2}" 12 2>/dev/null | head -40
echo ""
echo "==========================================="
echo "For detailed analysis, run:"
echo " ck-rocprof analyze ${name1} 12"
echo " ck-rocprof analyze ${name2} 12"
}
# List available profiling runs
cmd_list() {
if [ ! -d "${WORKLOAD_DIR}" ]; then
echo "No profiling runs found (workload directory doesn't exist)"
return 0
fi
local runs
runs=$(find "${WORKLOAD_DIR}" -maxdepth 1 -mindepth 1 -type d -exec basename {} \; 2>/dev/null | sort)
if [ -z "$runs" ]; then
echo "No profiling runs found in ${WORKLOAD_DIR}"
return 0
fi
echo "Available profiling runs:"
echo "==========================================="
while IFS= read -r run; do
local path
path=$(find_workload_path "$run")
if [ -n "$path" ]; then
local size
size=$(get_size "$path")
local date
date=$(get_date "$path")
printf " %-25s [%s, %s]\n" "$run" "$size" "$date"
else
printf " %-25s [no data]\n" "$run"
fi
done <<< "$runs"
echo ""
echo "Analyze with: ck-rocprof analyze <name>"
}
# Clean (remove) profiling runs
cmd_clean() {
local name="${1:-}"
if [ -z "$name" ]; then
error "name required (or use --all to remove all runs)"
echo "Usage: ck-rocprof clean <name>"
echo " ck-rocprof clean --all"
return 1
fi
if [ "$name" = "--all" ]; then
# Remove all profiling runs
if [ ! -d "${WORKLOAD_DIR}" ]; then
echo "No profiling runs to clean"
return 0
fi
echo "This will remove ALL profiling runs in ${WORKLOAD_DIR}"
read -r -p "Are you sure? [y/N] " confirm
if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
echo "Cancelled"
return 0
fi
rm -rf "${WORKLOAD_DIR:?}"/*
info "All profiling runs removed"
else
# Validate name
if ! validate_workload_name "$name"; then
return 1
fi
local run_dir="${WORKLOAD_DIR}/${name}"
if [ ! -d "$run_dir" ]; then
error "Profiling run not found: ${name}"
return 1
fi
rm -rf "${run_dir}"
info "Removed profiling run: ${name}"
fi
}
# Show status information
cmd_status() {
echo "CK ROCProf Status"
echo "==========================================="
echo ""
# rocprof-compute binary
if [ -n "${ROCPROF_BIN}" ] && [ -f "${ROCPROF_BIN}" ]; then
echo "rocprof-compute: ${ROCPROF_BIN}"
else
echo "rocprof-compute: not found"
fi
echo ""
# Paths
echo "Paths:"
echo " Venv: ${VENV_PATH}"
echo " Workloads: ${WORKLOAD_DIR}"
echo ""
# Setup status
echo "Setup status:"
if is_setup_complete; then
echo " Profiling environment: ready"
else
echo " Profiling environment: not configured (run 'ck-rocprof setup')"
fi
echo ""
# Workload count
if [ -d "${WORKLOAD_DIR}" ]; then
local count
count=$(find "${WORKLOAD_DIR}" -maxdepth 1 -mindepth 1 -type d 2>/dev/null | wc -l)
echo "Profiling runs: ${count}"
else
echo "Profiling runs: 0"
fi
}
# ============================================================================
# Main command dispatcher
# ============================================================================
case "${1:-}" in
setup)
cmd_setup
;;
run)
shift
cmd_run "$@"
;;
analyze)
shift
cmd_analyze "$@"
;;
compare)
shift
cmd_compare "$@"
;;
list)
cmd_list
;;
clean)
shift
cmd_clean "$@"
;;
status)
cmd_status
;;
help|--help|-h)
show_help
;;
*)
if [ -z "${1:-}" ]; then
show_help
else
echo "Unknown command: ${1}"
echo ""
show_help
exit 1
fi
;;
esac

167
script/tools/ck-rocprof.md Normal file
View File

@@ -0,0 +1,167 @@
# CK ROCProf Tool
GPU performance profiling for Composable Kernel applications using AMD rocprof-compute.
**Note:** This is a native-only tool. For Docker usage, run via `ck-docker exec ck-rocprof ...`
## Quick Start
```bash
# One-time setup (requires rocprofiler-compute installed)
./script/tools/ck-rocprof setup
# Profile executable
cd build
../script/tools/ck-rocprof run baseline ./bin/tile_example_gemm_universal
# Analyze LDS metrics
../script/tools/ck-rocprof analyze baseline
# Compare optimizations
../script/tools/ck-rocprof run optimized ./bin/tile_example_gemm_universal
../script/tools/ck-rocprof compare baseline optimized
```
## Commands
### `setup`
One-time setup: creates Python venv, installs dependencies, configures rocprof-compute.
### `run <name> <executable> [args]`
Profile executable and save results.
```bash
# Basic profiling
ck-rocprof run baseline ./bin/gemm_example
# With arguments
ck-rocprof run large_matrix ./bin/gemm_example -m 8192 -n 8192 -k 4096
# Test filtering
ck-rocprof run unit_test ./bin/test_gemm --gtest_filter="*Fp16*"
```
### `analyze <name> [block]`
Display profiling metrics (default: Block 12 - LDS).
```bash
ck-rocprof analyze baseline # LDS metrics
ck-rocprof analyze baseline 2 # L2 Cache
ck-rocprof analyze baseline 7 # Instruction Mix
```
### `compare <name1> <name2>`
Side-by-side comparison of two runs.
### `list`
List all profiling runs with size and date.
### `clean <name>` / `clean --all`
Remove profiling runs. Use `--all` to remove all runs.
### `status`
Show current configuration: mode (native/Docker), paths, setup status.
## Key LDS Metrics (Block 12)
**Target Values:**
- Bank Conflicts/Access: <0.01 (1% conflict rate)
- Bank Conflict Rate: >90% of peak bandwidth
**Critical Metrics:**
- **12.2.9 Bank Conflicts/Access**: Direct conflict measure
- Baseline (naive): ~0.04 (4% conflicts)
- Optimized: <0.005 (<0.5% conflicts)
- **12.2.12 Bank Conflict Cycles**: Wasted cycles per kernel
- **12.2.17 LDS Data FIFO Full**: Memory system pressure
## Optimization Workflow
```bash
# 1. Baseline
ck-rocprof run baseline ./bin/my_kernel
# 2. Check conflicts
ck-rocprof analyze baseline
# Look for Bank Conflicts/Access > 0.02
# 3. Optimize code (XOR transforms, padding, etc.)
# ... edit source ...
# 4. Test optimization
ninja my_kernel
ck-rocprof run optimized ./bin/my_kernel
# 5. Verify improvement
ck-rocprof compare baseline optimized
# Target: 8-10x reduction in conflicts
```
## Environment Variables
- `CK_PROFILE_VENV`: Python venv path (default: `$PROJECT/.ck-rocprof-venv`)
- `CK_ROCPROF_BIN`: rocprof-compute binary path (auto-detected from PATH or /opt/rocm)
- `CK_ROCM_REQUIREMENTS`: Path to rocprofiler-compute requirements.txt (auto-detected)
- `CK_WORKLOAD_DIR`: Results directory (default: `$PROJECT/build/workloads`)
- `CK_GPU_TARGET`: Override GPU detection (e.g., `gfx950`, `MI300X`)
## Interpreting Results
**Good Performance:**
```
Bank Conflicts/Access: <0.01
Bank Conflict Rate: >90% of peak
LDS Data FIFO Full: Minimal cycles
```
**Needs Optimization:**
```
Bank Conflicts/Access: >0.02
Bank Conflict Cycles: High MAX values
LDS Data FIFO Full: High memory pressure
```
## Troubleshooting
**"Profiling environment not set up"**
```bash
ck-rocprof setup
```
**"rocprof-compute not found"**
```bash
export CK_ROCPROF_BIN=/custom/path/rocprof-compute
ck-rocprof setup
```
**"Profiling results not found"**
```bash
ck-rocprof list # Check available runs
rocminfo | grep gfx # Verify GPU arch
export CK_GPU_TARGET=gfx950 # Override if needed
```
## Storage Layout
Results stored in `workloads/<name>/`:
- `pmc_perf.csv`: Performance counters (primary data file)
- `perfmon/`: Input metric files
- `out/`: Raw output data from profiler runs
- `log.txt`: Profiling log
## Technical Details
- **Setup**: Creates isolated Python venv, installs dependencies
- **Profiling**: Runs `rocprof-compute profile --name <name> -- <executable>`
- **Analysis**: Runs `rocprof-compute analyze --path <path> --block <block>`
- **GPU Support**: MI300/MI350 series, auto-detects architecture
## Related Tools
- `ck-docker`: Container management
- `rocprof-compute`: AMD GPU profiler v2
- `rocm-smi`: System monitoring
## License
Copyright (c) Advanced Micro Devices, Inc. SPDX-License-Identifier: MIT

84
script/tools/ck-shell Executable file
View File

@@ -0,0 +1,84 @@
#!/bin/bash
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
# CK Shell - Open interactive shell in Docker container
set -e
set -o pipefail
# Find script directory and load common utilities
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/common.sh"
# Initialize configuration
PROJECT_ROOT=$(get_project_root "${SCRIPT_DIR}")
CONTAINER_NAME=$(get_container_name "${PROJECT_ROOT}")
# Help message
show_help() {
cat << EOF
CK Shell - Open interactive shell in Docker container
Usage: ck-shell [options] [container_name]
Options:
-h, --help Show this help message
--name <name> Specify container name
-c <command> Execute command instead of interactive shell
Arguments:
container_name Optional container name (default: ck_<username>_<branch>)
Environment:
CK_CONTAINER_NAME - Override default container name
Examples:
ck-shell # Open interactive shell
ck-shell my_container # Open shell in specific container
ck-shell -c "rocm-smi" # Execute single command
ck-shell -c "cd build && ls bin" # Execute command in build directory
EOF
}
# Parse arguments
command=""
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_help
exit 0
;;
--name)
CONTAINER_NAME="$2"
shift 2
;;
-c)
command="$2"
shift 2
;;
*)
CONTAINER_NAME="$1"
shift
;;
esac
done
# Ensure container is running
if ! container_is_running "${CONTAINER_NAME}"; then
echo "Container '${CONTAINER_NAME}' not running. Starting..."
"${SCRIPT_DIR}/ck-start" "${CONTAINER_NAME}"
echo ""
fi
# Execute command or open shell
if [ -n "$command" ]; then
echo "Executing: ${command}"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
docker exec "${CONTAINER_NAME}" bash -c "${command}"
else
echo "Opening shell in '${CONTAINER_NAME}' (type 'exit' to leave)..."
docker exec -it "${CONTAINER_NAME}" bash
fi

103
script/tools/ck-start Executable file
View File

@@ -0,0 +1,103 @@
#!/bin/bash
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
# CK Start - Start Docker container for Composable Kernel testing
set -e
set -o pipefail
# Find script directory and load common utilities
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/common.sh"
# Initialize configuration
PROJECT_ROOT=$(get_project_root "${SCRIPT_DIR}")
CONTAINER_NAME=$(get_container_name "${PROJECT_ROOT}")
# Help message
show_help() {
cat << EOF
CK Start - Start Docker container for Composable Kernel testing
Usage: ck-start [options] [container_name]
Options:
-h, --help Show this help message
--image <image> Specify Docker image (overrides CK_DOCKER_IMAGE)
Arguments:
container_name Optional container name (default: ck_<username>_<branch>)
Environment:
CK_CONTAINER_NAME - Override default container name
CK_DOCKER_IMAGE - Override Docker image (default: rocm/composable_kernel:ck_ub24.04_rocm7.0.1)
Examples:
ck-start # Start container with default name
ck-start my_ck_container # Start container with custom name
ck-start --image rocm/composable_kernel:latest
EOF
}
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_help
exit 0
;;
--image)
export CK_DOCKER_IMAGE="$2"
shift 2
;;
*)
CONTAINER_NAME="$1"
shift
;;
esac
done
# Get Docker image
DOCKER_IMAGE=$(get_docker_image)
# Check if container exists and is running
if container_exists "${CONTAINER_NAME}"; then
if container_is_running "${CONTAINER_NAME}"; then
echo "Container '${CONTAINER_NAME}' is already running"
docker exec "${CONTAINER_NAME}" bash -c "echo 'Working directory:' && pwd"
exit 0
else
echo "Starting existing container '${CONTAINER_NAME}'..."
docker start "${CONTAINER_NAME}"
echo "Container started"
docker exec "${CONTAINER_NAME}" bash -c "echo 'Working directory:' && pwd"
exit 0
fi
fi
# Create new container
echo "Creating new Docker container '${CONTAINER_NAME}'..."
echo "Docker image: ${DOCKER_IMAGE}"
echo "Project root: ${PROJECT_ROOT}"
echo ""
docker run -d \
--name "${CONTAINER_NAME}" \
--device=/dev/kfd --device=/dev/dri \
--security-opt seccomp=unconfined \
--group-add video \
-v "${PROJECT_ROOT}":/workspace \
-w /workspace \
"${DOCKER_IMAGE}" \
tail -f /dev/null
echo ""
echo "Container '${CONTAINER_NAME}' started successfully"
docker exec "${CONTAINER_NAME}" bash -c "echo 'Working directory:' && pwd"
# Show GPU info
echo ""
echo "GPU Information:"
docker exec "${CONTAINER_NAME}" bash -c "rocm-smi --showproductname 2>/dev/null | head -5 || echo 'No GPU detected'"

153
script/tools/ck-status Executable file
View File

@@ -0,0 +1,153 @@
#!/bin/bash
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
# CK Status - Check container status and information
set -e
set -o pipefail
# Find script directory and load common utilities
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/common.sh"
# Initialize configuration
PROJECT_ROOT=$(get_project_root "${SCRIPT_DIR}")
CONTAINER_NAME=$(get_container_name "${PROJECT_ROOT}")
# Help message
show_help() {
cat << EOF
CK Status - Check container status and information
Usage: ck-status [options] [container_name]
Options:
-h, --help Show this help message
--name <name> Specify container name
--all Show all CK containers
-v, --verbose Show detailed information
Arguments:
container_name Optional container name (default: ck_<username>_<branch>)
Environment:
CK_CONTAINER_NAME - Override default container name
Examples:
ck-status # Check default container status
ck-status my_container # Check specific container
ck-status --all # Show all CK containers
ck-status -v # Show detailed information
EOF
}
# Parse arguments
show_all=false
verbose=false
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_help
exit 0
;;
--name)
CONTAINER_NAME="$2"
shift 2
;;
--all)
show_all=true
shift
;;
-v|--verbose)
verbose=true
shift
;;
*)
CONTAINER_NAME="$1"
shift
;;
esac
done
DOCKER_IMAGE=$(get_docker_image)
# Show all containers
if [ "$show_all" = true ]; then
echo "Composable Kernel Docker Containers:"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
username=$(get_username)
containers=$(docker ps -a --filter "name=ck_${username}_" --format "table {{.Names}}\t{{.Status}}\t{{.CreatedAt}}" 2>/dev/null || echo "")
if [ -z "$containers" ] || [ "$containers" = "NAMES STATUS CREATED AT" ]; then
echo "No CK containers found for user '${username}'"
else
echo "$containers"
fi
exit 0
fi
# Check specific container status
echo "Container: ${CONTAINER_NAME}"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
if container_is_running "${CONTAINER_NAME}"; then
echo "Status: RUNNING ✓"
echo ""
docker ps --filter "name=^${CONTAINER_NAME}$" --format "table {{.Names}}\t{{.Status}}\t{{.Image}}"
if [ "$verbose" = true ]; then
echo ""
echo "Container Details:"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
docker inspect "${CONTAINER_NAME}" --format '
Image: {{.Config.Image}}
Created: {{.Created}}
Platform: {{.Platform}}
Mounts: {{range .Mounts}}
- {{.Source}} -> {{.Destination}}{{end}}
'
fi
echo ""
echo "GPU Information:"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
docker exec "${CONTAINER_NAME}" bash -c "rocm-smi --showproductname 2>/dev/null | head -10 || echo 'No GPU detected'"
if [ "$verbose" = true ]; then
echo ""
echo "GPU Target:"
gpu_target=$(detect_gpu_target "${CONTAINER_NAME}")
echo " ${gpu_target}"
echo ""
echo "Build Status:"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
if docker exec "${CONTAINER_NAME}" test -d /workspace/build 2>/dev/null; then
if docker exec "${CONTAINER_NAME}" test -f /workspace/build/build.ninja 2>/dev/null; then
echo " CMake configured ✓"
echo " Build directory: /workspace/build"
# Count built test binaries
bin_count=$(docker exec "${CONTAINER_NAME}" bash -c "ls -1 /workspace/build/bin 2>/dev/null | wc -l" || echo "0")
echo " Test binaries: ${bin_count}"
else
echo " CMake not configured"
fi
else
echo " Build directory not found"
fi
fi
elif container_exists "${CONTAINER_NAME}"; then
echo "Status: STOPPED"
echo ""
echo "Start with: ck-start"
else
echo "Status: DOES NOT EXIST"
echo ""
echo "Create with: ck-start"
fi

141
script/tools/ck-stop Executable file
View File

@@ -0,0 +1,141 @@
#!/bin/bash
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
# CK Stop - Stop and remove Docker container
set -e
set -o pipefail
# Find script directory and load common utilities
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/common.sh"
# Initialize configuration
PROJECT_ROOT=$(get_project_root "${SCRIPT_DIR}")
CONTAINER_NAME=$(get_container_name "${PROJECT_ROOT}")
# Help message
show_help() {
cat << EOF
CK Stop - Stop and remove Docker container
Usage: ck-stop [options] [container_name]
Options:
-h, --help Show this help message
-f, --force Force stop without confirmation
--all Stop all CK containers for this user
Arguments:
container_name Optional container name (default: ck_<username>_<branch>)
Environment:
CK_CONTAINER_NAME - Override default container name
Examples:
ck-stop # Stop default container
ck-stop my_ck_container # Stop specific container
ck-stop --all # Stop all user's CK containers
ck-stop --force # Stop without confirmation
EOF
}
# Parse arguments
force=false
stop_all=false
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_help
exit 0
;;
-f|--force)
force=true
shift
;;
--all)
stop_all=true
shift
;;
*)
CONTAINER_NAME="$1"
shift
;;
esac
done
# Function to stop a single container
stop_container() {
local name="$1"
if ! container_exists "${name}"; then
echo "Container '${name}' does not exist"
return 1
fi
echo "Stopping and removing container '${name}'..."
docker stop "${name}" 2>/dev/null || true
docker rm "${name}" 2>/dev/null || true
echo "Container '${name}' stopped and removed"
}
# Stop all user containers
if [ "$stop_all" = true ]; then
username=$(get_username)
containers=$(docker ps -a --filter "name=ck_${username}_" --format '{{.Names}}')
if [ -z "$containers" ]; then
echo "No CK containers found for user '${username}'"
exit 0
fi
echo "Found CK containers for user '${username}':"
echo "$containers"
echo ""
if [ "$force" = false ]; then
read -p "Stop and remove all these containers? (y/N) " -n 1 -r
echo ""
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "Cancelled"
exit 0
fi
fi
echo ""
while IFS= read -r container; do
stop_container "$container"
done <<< "$containers"
echo ""
echo "All containers stopped and removed"
exit 0
fi
# Stop single container
if ! container_exists "${CONTAINER_NAME}"; then
echo "Container '${CONTAINER_NAME}' does not exist"
exit 0
fi
# Show container info
if container_is_running "${CONTAINER_NAME}"; then
echo "Container '${CONTAINER_NAME}' is currently running"
else
echo "Container '${CONTAINER_NAME}' exists but is stopped"
fi
# Confirm if not forced
if [ "$force" = false ]; then
read -p "Stop and remove container '${CONTAINER_NAME}'? (y/N) " -n 1 -r
echo ""
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "Cancelled"
exit 0
fi
fi
stop_container "${CONTAINER_NAME}"

231
script/tools/ck-test Executable file
View File

@@ -0,0 +1,231 @@
#!/bin/bash
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
# CK Test - Run Composable Kernel tests
# Environment-agnostic: works natively on ROCm hosts or inside containers
set -e
set -o pipefail
# Find script directory and load common utilities
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/common.sh"
# Initialize configuration
PROJECT_ROOT=$(find_project_root "${SCRIPT_DIR}" || get_project_root "${SCRIPT_DIR}")
BUILD_DIR=$(get_build_dir "${PROJECT_ROOT}")
# Help message
show_help() {
cat << EOF
CK Test - Run Composable Kernel tests
Usage: ck-test [options] [test_name] [-- gtest_options]
Options:
-h, --help Show this help message
--build-dir <dir> Build directory (default: ./build)
--no-build Skip building, run test directly
--list List available tests
--smoke Run all smoke tests (via CTest -L SMOKE_TEST)
--regression Run all regression tests (via CTest -L REGRESSION_TEST)
--all Run all tests (via CTest)
--filter <pattern> Shorthand for --gtest_filter=<pattern>
Arguments:
test_name Name of test executable (optional for --smoke/--regression/--all)
gtest_options Additional options passed to test (after --)
Environment:
CK_BUILD_DIR - Override build directory
Examples:
ck-test test_amdgcn_mma # Build and run specific test
ck-test test_amdgcn_mma --filter '*Fp16*' # Run with gtest filter
ck-test test_amdgcn_mma -- --gtest_filter=*Fp16* # Explicit gtest options
ck-test --no-build test_amdgcn_mma # Run without rebuilding
ck-test --list # List available tests
ck-test --smoke # Run all smoke tests
ck-test --regression # Run all regression tests
ck-test --all # Run all tests
EOF
}
# Parse arguments
test_name=""
no_build=false
list_tests=false
run_smoke=false
run_regression=false
run_all=false
gtest_filter=""
gtest_options=()
parsing_gtest=false
while [[ $# -gt 0 ]]; do
if [ "$parsing_gtest" = true ]; then
gtest_options+=("$1")
shift
continue
fi
case $1 in
-h|--help)
show_help
exit 0
;;
--build-dir)
require_arg "$1" "${2:-}"
BUILD_DIR="$2"
shift 2
;;
--no-build)
no_build=true
shift
;;
--list)
list_tests=true
shift
;;
--smoke)
run_smoke=true
shift
;;
--regression)
run_regression=true
shift
;;
--all)
run_all=true
shift
;;
--filter)
require_arg "$1" "${2:-}"
gtest_filter="$2"
shift 2
;;
--)
parsing_gtest=true
shift
;;
--gtest_*)
gtest_options+=("$1")
shift
;;
*)
if [ -z "$test_name" ]; then
test_name="$1"
else
gtest_options+=("$1")
fi
shift
;;
esac
done
# Add filter to gtest options if specified
if [ -n "$gtest_filter" ]; then
gtest_options+=("--gtest_filter=${gtest_filter}")
fi
# Validate mutual exclusivity of test suite options
suite_count=0
[ "$run_smoke" = true ] && suite_count=$((suite_count + 1))
[ "$run_regression" = true ] && suite_count=$((suite_count + 1))
[ "$run_all" = true ] && suite_count=$((suite_count + 1))
if [ "$suite_count" -gt 1 ]; then
error "Options --smoke, --regression, and --all are mutually exclusive"
exit 1
fi
# Check build is configured
if ! is_build_configured "${BUILD_DIR}"; then
error "Build not configured. Run 'ck-configure' first"
exit 1
fi
# Handle --list
if [ "$list_tests" = true ]; then
info "Available tests:"
if [ -d "${BUILD_DIR}/bin" ]; then
ls -1 "${BUILD_DIR}/bin/" 2>/dev/null | grep -E '^test_' | sort || echo " (No test binaries found)"
else
echo " (No bin directory found)"
fi
echo ""
echo "CTest labels:"
cd "${BUILD_DIR}"
ctest -N 2>/dev/null | head -20 || echo " (Run 'ctest -N' for full list)"
exit 0
fi
# Handle CTest-based test suites
if [ "$run_smoke" = true ] || [ "$run_regression" = true ] || [ "$run_all" = true ]; then
cd "${BUILD_DIR}"
ctest_cmd=(ctest --output-on-failure)
if [ "$run_smoke" = true ]; then
ctest_cmd+=(-L SMOKE_TEST)
info "Running smoke tests..."
elif [ "$run_regression" = true ]; then
ctest_cmd+=(-L REGRESSION_TEST)
info "Running regression tests..."
else
info "Running all tests..."
fi
"${ctest_cmd[@]}"
exit_code=$?
echo ""
if [ $exit_code -eq 0 ]; then
info "Tests completed successfully"
else
error "Tests failed with exit code: ${exit_code}"
fi
exit $exit_code
fi
# Validate test name for individual test runs
if [ -z "$test_name" ]; then
error "test_name required (or use --smoke/--regression/--all for test suites)"
echo ""
show_help
exit 1
fi
# Build test if needed (unless --no-build is specified)
if [ "$no_build" = false ]; then
info "Building ${test_name}..."
"${SCRIPT_DIR}/ck-build" --build-dir "${BUILD_DIR}" "${test_name}"
echo ""
fi
# Verify test executable exists
test_binary="${BUILD_DIR}/bin/${test_name}"
if [ ! -f "$test_binary" ]; then
error "Test executable not found: ${test_binary}"
echo "Run 'ck-build ${test_name}' first"
exit 1
fi
# Run test
echo "Running: ${test_name} ${gtest_options[*]}"
echo "---"
cd "${BUILD_DIR}"
"./bin/${test_name}" "${gtest_options[@]}"
exit_code=$?
echo "---"
if [ $exit_code -eq 0 ]; then
info "Test completed successfully"
else
error "Test failed with exit code: ${exit_code}"
fi
exit $exit_code

181
script/tools/common.sh Normal file
View File

@@ -0,0 +1,181 @@
#!/bin/bash
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
# Common utilities for CK Docker tools
# Shared configuration and helper functions
# Find project root using relative path (fallback)
get_project_root() {
local script_dir="$1"
cd "${script_dir}/../.." && pwd
}
# Detect git branch and sanitize for Docker naming
get_sanitized_branch() {
local project_root="$1"
local branch
branch=$(cd "${project_root}" && git rev-parse --abbrev-ref HEAD 2>/dev/null | tr '/' '_' | tr -cd 'a-zA-Z0-9_-' || echo "")
branch=${branch:-unknown}
# Handle detached HEAD state
if [ "${branch}" = "HEAD" ]; then
branch="detached"
fi
echo "${branch}"
}
# Get username with fallback
get_username() {
echo "${USER:-$(whoami 2>/dev/null || echo "user")}"
}
# Generate default container name: ck_<username>_<branch>
get_default_container_name() {
local project_root="$1"
local user_name
local git_branch
user_name=$(get_username)
git_branch=$(get_sanitized_branch "${project_root}")
echo "ck_${user_name}_${git_branch}"
}
# Get container name (respects CK_CONTAINER_NAME env var)
get_container_name() {
local project_root="$1"
local default_name
default_name=$(get_default_container_name "${project_root}")
echo "${CK_CONTAINER_NAME:-${default_name}}"
}
# Get Docker image (respects CK_DOCKER_IMAGE env var)
get_docker_image() {
echo "${CK_DOCKER_IMAGE:-rocm/composable_kernel:ck_ub24.04_rocm7.0.1}"
}
# Check if container exists (exact match)
container_exists() {
local name="$1"
docker ps -a --filter "name=^${name}$" --format '{{.Names}}' | grep -q "^${name}$"
}
# Check if container is running (exact match)
container_is_running() {
local name="$1"
docker ps --filter "name=^${name}$" --format '{{.Names}}' | grep -q "^${name}$"
}
# Detect GPU target in container
detect_gpu_target() {
local container="$1"
# Allow override via CK_GPU_TARGET environment variable
if [ -n "${CK_GPU_TARGET:-}" ]; then
echo "${CK_GPU_TARGET}"
return 0
fi
docker exec "${container}" bash -c "
rocminfo 2>/dev/null | grep -oE 'gfx[0-9a-z]+' | head -1 || echo 'gfx950'
" | tr -d '\r\n'
}
# Ensure container is running, start if needed
ensure_container_running() {
local container="$1"
local script_dir="$2"
if ! container_is_running "${container}"; then
echo "Container '${container}' not running. Starting with ck-docker..."
"${script_dir}/ck-docker" start "${container}"
fi
}
# ============================================================================
# Native (non-Docker) utilities
# ============================================================================
# Output utilities
info() { echo "[info] $*"; }
warn() { echo "[warn] $*" >&2; }
error() { echo "[error] $*" >&2; }
# Require argument for option (validates $2 exists and is not another flag)
require_arg() {
local option="$1"
local value="$2"
if [ -z "$value" ] || [[ "$value" == -* ]]; then
error "Option $option requires an argument"
exit 1
fi
}
# Native GPU detection (no Docker required)
detect_gpu_native() {
# Allow override via CK_GPU_TARGET environment variable
if [ -n "${CK_GPU_TARGET:-}" ]; then
echo "${CK_GPU_TARGET}"
return 0
fi
# Try rocminfo if available
if command -v rocminfo &>/dev/null; then
local gpu
gpu=$(rocminfo 2>/dev/null | grep -oE 'gfx[0-9a-z]+' | head -1)
if [ -n "$gpu" ]; then
echo "$gpu"
return 0
fi
fi
# Fallback
echo "gfx950"
}
# Get build directory (respects CK_BUILD_DIR env var)
get_build_dir() {
local project_root="${1:-$(get_project_root "$(dirname "${BASH_SOURCE[0]}")")}"
echo "${CK_BUILD_DIR:-${project_root}/build}"
}
# Check if build is configured (build.ninja exists)
is_build_configured() {
local build_dir="${1:-$(get_build_dir)}"
[ -f "${build_dir}/build.ninja" ]
}
# Find project root from any subdirectory (walks up to find .ck-project-root)
find_project_root() {
local dir="${1:-$(pwd)}"
while [ "$dir" != "/" ]; do
if [ -f "$dir/.ck-project-root" ]; then
echo "$dir"
return 0
fi
dir=$(dirname "$dir")
done
return 1
}
# List available CMake presets
list_cmake_presets() {
local project_root="${1:-$(find_project_root)}"
local presets_file="${project_root}/CMakePresets.json"
if [ ! -f "$presets_file" ]; then
return 1
fi
# Extract non-hidden preset names
if command -v jq &>/dev/null; then
jq -r '.configurePresets[] | select(.hidden != true) | .name' "$presets_file" 2>/dev/null
else
# Fallback: sed-based extraction (more portable than grep -P)
sed -n 's/.*"name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$presets_file" | grep -v '^use-'
fi
}

4
script/uninstall_precommit.sh Executable file
View File

@@ -0,0 +1,4 @@
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
pre-commit uninstall

View File

@@ -0,0 +1,295 @@
#!/usr/bin/env python3
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
"""
Purpose:
Normalize and enforce AMD two-line copyright + SPDX headers across files.
Target files:
- C/C++-style: .cpp, .hpp, .inc -> uses "//" comment style
- Hash-style: .py, .cmake, .sh, and CMakeLists.txt -> uses "#" style
Header formats inserted (top of file, followed by exactly one blank line):
C/C++ :
// Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
// SPDX-License-Identifier: MIT
<blank>
Hash :
<blank>
Shebang special case (hash-style only):
- If line 1 starts with "#!", keep shebang, then a blank line, then the
two hash-style header lines, then a blank line.
Removal rules:
- Remove any comment lines (anywhere in file) containing the keywords
"copyright" or "spdx" (case-insensitive). Blank lines are preserved.
- Remove long-form MIT license block comment when:
a) The file starts with the block (absolute top), OR
b) The block appears immediately after the AMD header position
(i.e., when remainder at insertion point begins with "/*" and
the first content line is "* The MIT License (MIT)").
Blank-line normalization:
- Enforce exactly ONE blank line immediately after the AMD header.
(Drop only the leading blank lines at the insertion point before
re-inserting the header.)
- Do not change blank lines between other non-copyright comments.
Preservation:
- Preserve original newline style: CRLF (\r\n) vs LF (\n).
- Preserve UTF-8 BOM if present.
- Do not modify non-comment code lines.
Idempotency:
- Running this script multiple times does not further modify files.
"""
from __future__ import annotations
import re
import sys
from pathlib import Path
from typing import List, Tuple
AMD_CPP_HEADER_TEXT = [
"// Copyright (c) Advanced Micro Devices, Inc., or its affiliates.",
"// SPDX-License-Identifier: MIT",
]
AMD_HASH_HEADER_TEXT = [
"# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.",
"# SPDX-License-Identifier: MIT",
]
CPP_EXTS = {".cpp", ".hpp", ".inc"}
HASH_EXTS = {".py", ".cmake", ".sh"}
# --- Encoding helpers -------------------------------------------------------
def has_bom(raw: bytes) -> bool:
return raw.startswith(b"\xef\xbb\xbf")
def decode_text(raw: bytes) -> str:
return raw.decode("utf-8-sig", errors="replace")
def encode_text(text: str, bom: bool) -> bytes:
data = text.encode("utf-8")
return (b"\xef\xbb\xbf" + data) if bom else data
# --- Newline detection ------------------------------------------------------
def detect_newline_sequence(raw: bytes) -> str:
if b"\r\n" in raw:
return "\r\n"
elif b"\n" in raw:
return "\n"
else:
return "\n"
# --- Utilities --------------------------------------------------------------
def is_comment_line(line: str, style: str) -> bool:
stripped = line.lstrip()
if style == "cpp":
return (
stripped.startswith("//")
or stripped.startswith("/*")
or stripped.startswith("*")
or stripped.startswith("*/")
)
elif style == "hash":
return stripped.startswith("#")
return False
def has_keywords(line: str) -> bool:
lower_line = line.lower()
return ("copyright" in lower_line) or ("spdx" in lower_line)
# --- MIT License banner detection ------------------------------
MIT_C_FIRST_LINE_RE = re.compile(r"^\s*\*\s*The MIT License \(MIT\)")
MIT_HASH_FIRST_LINE_RE = re.compile(r"^\s*#\s*The MIT License \(MIT\)")
def remove_top_mit_block(lines: List[str]) -> Tuple[List[str], bool]:
"""
Unified MIT banner removal at the top of 'lines'.
Supports:
- C-style block starting with '/*' and ending with '*/'; removes only if
a line within the block matches MIT_C_FIRST_LINE_RE.
- Hash-style banner: contiguous top run of lines starting with '#';
removes only if any line in that run matches MIT_HASH_FIRST_LINE_RE.
Returns (new_lines, removed_flag). Preserves EOLs.
"""
if not lines:
return lines, False
first = lines[0].lstrip()
# C-style block
if first.startswith("/*"):
end_idx, saw_mit = None, False
for i, line in enumerate(lines[1:], 1):
if not saw_mit and MIT_C_FIRST_LINE_RE.match(line):
saw_mit = True
s = line.lstrip()
if s.startswith("*/") or s.rstrip().endswith("*/"):
end_idx = i + 1
break
if end_idx is not None and saw_mit:
return lines[end_idx:], True
return lines, False
# Hash-style contiguous banner
if first.startswith("#"):
end_idx, saw_mit = 0, False
for i, line in enumerate(lines):
if line.lstrip().startswith("#"):
if not saw_mit and MIT_HASH_FIRST_LINE_RE.match(line):
saw_mit = True
end_idx = i + 1
else:
break
if saw_mit:
return lines[end_idx:], True
return lines, False
return lines, False
# --- Removal + normalization helpers ---------------------------------------
def remove_keyword_comment_lines_globally(lines: List[str], style: str) -> List[str]:
"""Remove comment lines containing keywords anywhere in the file.
**Do not** remove blank lines; preserve all other lines as-is."""
out: List[str] = []
for line in lines:
if is_comment_line(line, style) and has_keywords(line):
continue
out.append(line)
return out
def drop_leading_blank_lines(lines: List[str]) -> List[str]:
"""Drop only the leading blank lines at the start of the given list."""
i = 0
while i < len(lines) and lines[i].strip() == "":
i += 1
return lines[i:]
# --- Header builder ---------------------------------------------------------
def build_header_lines(style: str, nl: str) -> List[str]:
base = AMD_CPP_HEADER_TEXT if style == "cpp" else AMD_HASH_HEADER_TEXT
return [base[0] + nl, base[1] + nl, nl] # header + exactly one blank
# --- Main transforms --------------------------------------------------------
def process_cpp(text: str, nl: str) -> str:
lines = text.splitlines(True)
# Remove MIT block if it is at the *absolute* top
lines, _ = remove_top_mit_block(lines)
# Remove keyworded comment lines globally (blank lines preserved)
lines = remove_keyword_comment_lines_globally(lines, style="cpp")
# Normalize insertion point and remove MIT block if it appears *after header*
lines = drop_leading_blank_lines(lines)
lines, _ = remove_top_mit_block(lines)
# Prepend AMD header (guarantee exactly one blank after)
return "".join(build_header_lines("cpp", nl) + lines)
def process_hash(text: str, nl: str) -> str:
lines = text.splitlines(True)
if not lines:
return "".join(build_header_lines("hash", nl))
shebang = lines[0].startswith("#!")
if shebang:
remainder = remove_keyword_comment_lines_globally(lines[1:], style="hash")
remainder = drop_leading_blank_lines(remainder)
remainder, _ = remove_top_mit_block(remainder) # remove MIT block after header
new_top = [lines[0], nl] + build_header_lines("hash", nl)
return "".join(new_top + remainder)
else:
remainder = remove_keyword_comment_lines_globally(lines, style="hash")
remainder = drop_leading_blank_lines(remainder)
remainder, _ = remove_top_mit_block(remainder) # remove MIT block after header
return "".join(build_header_lines("hash", nl) + remainder)
# --- File processing & CLI --------------------------------------------------
def process_file(path: Path) -> bool:
name = path.name
suffix = path.suffix.lower()
if suffix in CPP_EXTS:
style = "cpp"
elif suffix in HASH_EXTS or name == "CMakeLists.txt":
style = "hash"
else:
return False
raw = path.read_bytes()
bom = has_bom(raw)
nl = detect_newline_sequence(raw)
text = decode_text(raw)
updated = process_cpp(text, nl) if style == "cpp" else process_hash(text, nl)
if updated != text:
path.write_bytes(encode_text(updated, bom))
return True
return False
def main(argv: List[str]) -> int:
if len(argv) < 2:
print(__doc__)
return 2
changed = 0
skipped = 0
errors: List[str] = []
for arg in argv[1:]:
p = Path(arg)
try:
if not p.exists():
errors.append(f"Not found: {p}")
continue
if p.is_dir():
errors.append(f"Is a directory (pass specific files): {p}")
continue
if process_file(p):
changed += 1
print(f"Updated: {p}")
else:
skipped += 1
print(f"Skipped (no change needed or unsupported type): {p}")
except Exception as e:
errors.append(f"Error processing {p}: {e}")
print(f"\nSummary: {changed} updated, {skipped} skipped, {len(errors)} errors")
for msg in errors:
print(f" - {msg}")
return 0 if not errors else 1
if __name__ == "__main__":
raise SystemExit(main(sys.argv))