mirror of
https://github.com/ROCm/composable_kernel.git
synced 2026-07-15 11:34:54 +00:00
This commit is contained in:
263
script/analyze_build/README.md
Normal file
263
script/analyze_build/README.md
Normal 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
|
||||
251
script/analyze_build/notebooks/file_analysis_example.ipynb
Normal file
251
script/analyze_build/notebooks/file_analysis_example.ipynb
Normal 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
|
||||
}
|
||||
24
script/analyze_build/requirements.txt
Normal file
24
script/analyze_build/requirements.txt
Normal 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
|
||||
53
script/analyze_build/trace_analysis/__init__.py
Normal file
53
script/analyze_build/trace_analysis/__init__.py
Normal 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",
|
||||
]
|
||||
96
script/analyze_build/trace_analysis/parse_build.py
Normal file
96
script/analyze_build/trace_analysis/parse_build.py
Normal 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()
|
||||
)
|
||||
403
script/analyze_build/trace_analysis/parse_file.py
Normal file
403
script/analyze_build/trace_analysis/parse_file.py
Normal 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
|
||||
354
script/analyze_build/trace_analysis/phase_breakdown.py
Normal file
354
script/analyze_build/trace_analysis/phase_breakdown.py
Normal 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)
|
||||
292
script/analyze_build/trace_analysis/pipeline.py
Normal file
292
script/analyze_build/trace_analysis/pipeline.py
Normal 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
|
||||
80
script/analyze_build/trace_analysis/template_analysis.py
Normal file
80
script/analyze_build/trace_analysis/template_analysis.py
Normal 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
|
||||
301
script/analyze_build/trace_analysis/template_parser.py
Normal file
301
script/analyze_build/trace_analysis/template_parser.py
Normal 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
72
script/check_copyright_year.sh
Executable 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
|
||||
5
script/clang-format-overwrite.sh
Executable file
5
script/clang-format-overwrite.sh
Executable 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
41
script/cmake-ck-dev.sh
Executable 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[@]}"
|
||||
506
script/convert_miopen_driver_to_profiler.py
Normal file
506
script/convert_miopen_driver_to_profiler.py
Normal 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
23
script/count_vgpr.sh
Executable 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
|
||||
207
script/dependency-parser/README.md
Normal file
207
script/dependency-parser/README.md
Normal 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.
|
||||
@@ -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()
|
||||
110
script/dependency-parser/main.py
Normal file
110
script/dependency-parser/main.py
Normal 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()
|
||||
395
script/dependency-parser/src/enhanced_ninja_parser.py
Normal file
395
script/dependency-parser/src/enhanced_ninja_parser.py
Normal 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()
|
||||
175
script/dependency-parser/src/selective_test_filter.py
Normal file
175
script/dependency-parser/src/selective_test_filter.py
Normal 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
115
script/gemm_profile.sh
Executable 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
7
script/hip_fatbin_insert
Normal 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
28
script/hipclang_opt.sh
Executable 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
|
||||
54
script/infra_helper/capture_build_trace.js
Normal file
54
script/infra_helper/capture_build_trace.js
Normal 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
23
script/install_precommit.sh
Executable 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
132
script/launch_tests.sh
Executable 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
|
||||
119
script/monitor_sccache_during_build.sh
Normal file
119
script/monitor_sccache_during_build.sh
Normal 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
|
||||
549
script/ninja_json_converter.py
Normal file
549
script/ninja_json_converter.py
Normal 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
43
script/parse_ninja_trace.py
Executable 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
488
script/process_perf_data.py
Normal 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
86
script/process_perf_data.sh
Executable 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
66
script/process_qa_data.sh
Executable 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
40
script/profile_batched_gemm.sh
Executable 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
61
script/profile_gemm.sh
Executable 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
44
script/profile_gemm_bilinear.sh
Executable 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
|
||||
41
script/profile_grouped_conv_bwd_data.sh
Executable file
41
script/profile_grouped_conv_bwd_data.sh
Executable 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
|
||||
42
script/profile_grouped_conv_bwd_weight.sh
Executable file
42
script/profile_grouped_conv_bwd_weight.sh
Executable 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
|
||||
42
script/profile_grouped_conv_fwd.sh
Executable file
42
script/profile_grouped_conv_fwd.sh
Executable 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
|
||||
23
script/profile_grouped_conv_fwd_outelementop.sh
Executable file
23
script/profile_grouped_conv_fwd_outelementop.sh
Executable 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
21
script/profile_grouped_gemm.sh
Executable 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
55
script/profile_mixed_gemm.sh
Executable 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
34
script/profile_onnx_gemm.sh
Executable 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
46
script/profile_permute_scale.sh
Executable 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
|
||||
|
||||
81
script/profile_reduce_no_index.sh
Executable file
81
script/profile_reduce_no_index.sh
Executable 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
|
||||
|
||||
73
script/profile_reduce_with_index.sh
Executable file
73
script/profile_reduce_with_index.sh
Executable 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
72
script/profile_resnet50.sh
Executable 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
44
script/profile_splitK_gemm.sh
Executable 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
10
script/redis-cli.conf
Normal 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
16
script/remod_for_ck_tile.py
Executable 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
10
script/remove_exec_bit.sh
Executable 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
254
script/run-tests.ps1
Normal 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
|
||||
}
|
||||
313
script/run_ck_profiler_gemm_with_csv_shapes.py
Normal file
313
script/run_ck_profiler_gemm_with_csv_shapes.py
Normal 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()
|
||||
157
script/run_full_performance_tests.sh
Executable file
157
script/run_full_performance_tests.sh
Executable 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
|
||||
44
script/run_gemm_performance_tests.sh
Executable file
44
script/run_gemm_performance_tests.sh
Executable 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
71
script/run_performance_tests.sh
Executable 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
76
script/sccache_wrapper.sh
Executable 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
113
script/test_convnd_fwd.sh
Normal 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
66
script/test_reduce_no_index.sh
Executable 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
78
script/tools/README.md
Normal 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
|
||||
168
script/tools/README_ck-build-analysis.md
Normal file
168
script/tools/README_ck-build-analysis.md
Normal 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/
|
||||
```
|
||||
80
script/tools/README_ck-docker.md
Normal file
80
script/tools/README_ck-docker.md
Normal 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
|
||||
```
|
||||
347
script/tools/analyze_build_trace.py
Executable file
347
script/tools/analyze_build_trace.py
Executable 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
163
script/tools/ck-build
Executable 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
237
script/tools/ck-build-analysis
Executable 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
113
script/tools/ck-clean
Executable 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
187
script/tools/ck-configure
Executable 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
231
script/tools/ck-docker
Executable 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
120
script/tools/ck-exec
Executable 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
134
script/tools/ck-logs
Executable 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
806
script/tools/ck-rocprof
Executable 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
167
script/tools/ck-rocprof.md
Normal 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
84
script/tools/ck-shell
Executable 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
103
script/tools/ck-start
Executable 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
153
script/tools/ck-status
Executable 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
141
script/tools/ck-stop
Executable 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
231
script/tools/ck-test
Executable 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
181
script/tools/common.sh
Normal 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
4
script/uninstall_precommit.sh
Executable file
@@ -0,0 +1,4 @@
|
||||
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
pre-commit uninstall
|
||||
295
script/update_amd_copyright_headers.py
Normal file
295
script/update_amd_copyright_headers.py
Normal 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))
|
||||
Reference in New Issue
Block a user