* Add precision support audit tooling Introduces two slash commands for auditing data-type support across ROCm libraries between releases: - /precision-check: scopes to libraries changed in the manifest diff - /precision-check-delta: scopes to libraries whose source file SHA changed between releases (faster for routine release-over-release use) precision_fetch.py fetches raw source files (RST, C headers) and YAML snapshots from GitHub to /tmp/. The slash commands read them directly and do semantic comparison — no regex parsers. Auto-updates the YAML for unambiguous missing entries; flags ambiguous findings (macro expansion, combination tables, support level mismatches, typedef-only sources) for human review. manifest_diff.py fetches and diffs ROCm manifests between two versions, with branch-first and tag fallback for pre-GA use. Also includes: - .claude/settings.json: pre-approved permissions so the commands run without repeated prompts - tools/autotag/precision-update-log/: gitignored directory for local audit logs - README updates documenting the commands, prerequisites, and how to extend coverage to new libraries * Fix markdown issues * Move precision support scripts to tools/precision-support/ * Fix precision-check command path after script move to tools/precision-support --------- Co-authored-by: Istvan Kiss <istvan.kiss@amd.com>
481 lines
18 KiB
Python
481 lines
18 KiB
Python
#!/usr/bin/env python3
|
|
"""Fetch precision support source files and YAML for Claude to compare.
|
|
|
|
Writes raw content to /tmp/ for the Claude precision-check command.
|
|
Claude reads the files and does the comparison — no regex parsers used.
|
|
|
|
Usage (manifest-scoped — full audit):
|
|
python3 precision_fetch.py -t $GITHUB_TOKEN --previous 7.1.1 --current 7.2.0
|
|
|
|
Usage (SHA-filtered — only libraries whose source file changed):
|
|
python3 precision_fetch.py -t $GITHUB_TOKEN --previous 7.1.1 --current 7.2.0 --sha-filter
|
|
|
|
Usage (explicit library list):
|
|
python3 precision_fetch.py -t $GITHUB_TOKEN --version 7.2.0 --libs hipblas,hipsparselt
|
|
"""
|
|
|
|
import argparse
|
|
import base64
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
from urllib.error import HTTPError, URLError
|
|
from urllib.request import Request, urlopen
|
|
|
|
try:
|
|
import yaml
|
|
except ImportError:
|
|
sys.exit("pyyaml is required: pip install pyyaml")
|
|
|
|
from manifest_diff import fetch_manifest, diff_manifests
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Monorepo expansion
|
|
# ---------------------------------------------------------------------------
|
|
# Some manifest entries are monorepos covering multiple precision-tracked
|
|
# libraries. When one of these appears in the manifest diff, expand it to
|
|
# all tracked sub-libraries rather than treating it as a single component.
|
|
|
|
MONOREPO_LIBS: dict[str, list[str]] = {
|
|
"rocm-libraries": [
|
|
"hipBLAS", "hipBLASLt", "hipFFT", "hipRAND",
|
|
"hipSOLVER", "hipSPARSE", "hipSPARSELt", "hipTensor", "hipCUB",
|
|
"rocBLAS", "rocFFT", "rocRAND", "rocSOLVER", "rocSPARSE",
|
|
"rocWMMA", "rocPRIM", "rocThrust", "Tensile",
|
|
],
|
|
}
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Mapping: manifest component name → YAML library tag
|
|
# ---------------------------------------------------------------------------
|
|
|
|
MANIFEST_TO_TAG: dict[str, str] = {
|
|
"hipBLAS": "hipblas",
|
|
"hipBLASLt": "hipblaslt",
|
|
"hipFFT": "hipfft",
|
|
"hipRAND": "hiprand",
|
|
"hipSOLVER": "hipsolver",
|
|
"hipSPARSE": "hipsparse",
|
|
"hipSPARSELt": "hipsparselt",
|
|
"hipTensor": "hiptensor",
|
|
"hipCUB": "hipcub",
|
|
"rocBLAS": "rocblas",
|
|
"rocFFT": "rocfft",
|
|
"rocRAND": "rocrand",
|
|
"rocSOLVER": "rocsolver",
|
|
"rocSPARSE": "rocsparse",
|
|
"rocWMMA": "rocwmma",
|
|
"rocPRIM": "rocprim",
|
|
"rocThrust": "rocthrust",
|
|
"composable_kernel": "composable-kernel",
|
|
"MIOpen": "miopen",
|
|
"AMDMIGraphX": "migraphx",
|
|
"rccl": "rccl",
|
|
"Tensile": "tensile",
|
|
}
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Source config: YAML tag → GitHub fetch details
|
|
# ---------------------------------------------------------------------------
|
|
# Each entry defines where to fetch the authoritative source file for a library.
|
|
# org: GitHub org (default "ROCm")
|
|
# repo: GitHub repo name
|
|
# path: Path to the source file within the repo
|
|
# note: Set if the library is skipped or has no parseable source
|
|
|
|
SOURCE_CONFIG: dict[str, dict] = {
|
|
"hipblas": {
|
|
"org": "ROCm",
|
|
"repo": "rocm-libraries",
|
|
"path": "projects/hipblas/docs/reference/data-type-support.rst",
|
|
},
|
|
"hipblaslt": {
|
|
"org": "ROCm",
|
|
"repo": "rocm-libraries",
|
|
"path": "projects/hipblaslt/docs/reference/data-type-support.rst",
|
|
},
|
|
"hipfft": {
|
|
"org": "ROCm",
|
|
"repo": "rocm-libraries",
|
|
"path": "projects/hipfft/docs/reference/hipfft-api-usage.rst",
|
|
},
|
|
"hiprand": {
|
|
"org": "ROCm",
|
|
"repo": "rocm-libraries",
|
|
"path": "projects/hiprand/docs/api-reference/data-type-support.rst",
|
|
},
|
|
"hipsolver": {
|
|
"org": "ROCm",
|
|
"repo": "rocm-libraries",
|
|
"path": "projects/hipsolver/docs/reference/precision.rst",
|
|
},
|
|
"hipsparse": {
|
|
"org": "ROCm",
|
|
"repo": "rocm-libraries",
|
|
"path": "projects/hipsparse/docs/reference/precision.rst",
|
|
},
|
|
"hipsparselt": {
|
|
"org": "ROCm",
|
|
"repo": "rocm-libraries",
|
|
"path": "projects/hipsparselt/docs/reference/data-type-support.rst",
|
|
},
|
|
"hiptensor": {
|
|
"org": "ROCm",
|
|
"repo": "rocm-libraries",
|
|
"path": "projects/hiptensor/docs/api-reference/api-reference.rst",
|
|
},
|
|
"hipcub": {
|
|
"org": "ROCm",
|
|
"repo": "rocm-libraries",
|
|
"path": "projects/hipcub/docs/api-reference/data-type-support.rst",
|
|
"note": "No data-type-support page exists — hipCUB is header-only and type-agnostic",
|
|
},
|
|
"rocblas": {
|
|
"org": "ROCm",
|
|
"repo": "rocm-libraries",
|
|
"path": "projects/rocblas/docs/reference/data-type-support.rst",
|
|
},
|
|
"rocfft": {
|
|
"org": "ROCm",
|
|
"repo": "rocm-libraries",
|
|
"path": "projects/rocfft/library/include/rocfft/rocfft.h",
|
|
},
|
|
"rocrand": {
|
|
"org": "ROCm",
|
|
"repo": "rocm-libraries",
|
|
"path": "projects/rocrand/docs/api-reference/data-type-support.rst",
|
|
},
|
|
"rocsolver": {
|
|
"org": "ROCm",
|
|
"repo": "rocm-libraries",
|
|
"path": "projects/rocsolver/docs/reference/precision.rst",
|
|
},
|
|
"rocsparse": {
|
|
"org": "ROCm",
|
|
"repo": "rocm-libraries",
|
|
"path": "projects/rocsparse/docs/reference/precision.rst",
|
|
},
|
|
"rocwmma": {
|
|
"org": "ROCm",
|
|
"repo": "rocm-libraries",
|
|
"path": "projects/rocwmma/docs/api-reference/api-reference-guide.rst",
|
|
},
|
|
"rocprim": {
|
|
"org": "ROCm",
|
|
"repo": "rocm-libraries",
|
|
"path": "projects/rocprim/docs/reference/data-type-support.rst",
|
|
},
|
|
"rocthrust": {
|
|
"org": "ROCm",
|
|
"repo": "rocm-libraries",
|
|
"path": "projects/rocthrust/docs/data-type-support.rst",
|
|
"note": "No data-type-support page exists — rocThrust is a template library and type-agnostic",
|
|
},
|
|
"composable-kernel": {
|
|
"org": "ROCm",
|
|
"repo": "composable_kernel",
|
|
"path": "docs/reference/Composable_Kernel_supported_scalar_types.rst",
|
|
},
|
|
"miopen": {
|
|
"org": "ROCm",
|
|
"repo": "MIOpen",
|
|
"path": "docs/reference/datatypes.rst",
|
|
},
|
|
"migraphx": {
|
|
"org": "ROCm",
|
|
"repo": "AMDMIGraphX",
|
|
"path": "src/api/include/migraphx/migraphx.h",
|
|
},
|
|
"rccl": {
|
|
"org": "ROCm",
|
|
"repo": "rccl",
|
|
"path": "src/nccl.h.in",
|
|
},
|
|
"tensile": {
|
|
"org": "ROCm",
|
|
"repo": "rocm-libraries",
|
|
"path": "projects/hipblaslt/tensilelite/Tensile/docs/src/reference/precision-support.rst",
|
|
"note": "Tensile has no docs folder — it is an internal kernel generator, not a user-facing library",
|
|
},
|
|
}
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# GitHub helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
_GH_API = "https://api.github.com"
|
|
|
|
|
|
def _gh_request(token: str, url: str) -> dict:
|
|
req = Request(url, headers={
|
|
"Authorization": f"Bearer {token}",
|
|
"Accept": "application/vnd.github+json",
|
|
"X-GitHub-Api-Version": "2022-11-28",
|
|
})
|
|
with urlopen(req, timeout=30) as resp:
|
|
return json.loads(resp.read())
|
|
|
|
|
|
def _version_to_ref(version: str) -> str:
|
|
"""Convert a ROCm version string to a release branch ref.
|
|
|
|
e.g. '7.2.0' → 'release/rocm-rel-7.2'
|
|
"""
|
|
major_minor = ".".join(version.split(".")[:2])
|
|
return f"release/rocm-rel-{major_minor}"
|
|
|
|
|
|
def fetch_yaml(token: str, version: str) -> dict:
|
|
"""Fetch precision-support.yaml from the ROCm/ROCm repo.
|
|
|
|
Tries the release branch first, falls back to the release tag.
|
|
"""
|
|
path = "docs/data/reference/precision-support/precision-support.yaml"
|
|
for ref in _refs_for_version(version):
|
|
url = f"{_GH_API}/repos/ROCm/ROCm/contents/{path}?ref={ref}"
|
|
try:
|
|
data = _gh_request(token, url)
|
|
return yaml.safe_load(base64.b64decode(data["content"]).decode())
|
|
except HTTPError as e:
|
|
if e.code in (401, 403):
|
|
sys.exit(f"GitHub authentication failed (HTTP {e.code}) — check your token.")
|
|
continue
|
|
except URLError as e:
|
|
sys.exit(f"Network error: {e}")
|
|
sys.exit(f"Cannot fetch precision-support.yaml for ROCm {version}: not found at branch or tag.")
|
|
|
|
|
|
def _refs_for_version(version: str) -> list[str]:
|
|
"""Return refs to try in order: release branch first, tag as fallback."""
|
|
return [_version_to_ref(version), f"rocm-{version}"]
|
|
|
|
|
|
def fetch_file_sha(token: str, org: str, repo_name: str, path: str, version: str) -> str | None:
|
|
"""Return the blob SHA of a file at the given ROCm version, or None if not found.
|
|
|
|
Tries the release branch first, falls back to the release tag.
|
|
"""
|
|
for ref in _refs_for_version(version):
|
|
url = f"{_GH_API}/repos/{org}/{repo_name}/contents/{path}?ref={ref}"
|
|
try:
|
|
data = _gh_request(token, url)
|
|
return data.get("sha")
|
|
except HTTPError as e:
|
|
if e.code in (401, 403):
|
|
sys.exit(f"GitHub authentication failed (HTTP {e.code}) — check your token.")
|
|
continue
|
|
except URLError as e:
|
|
sys.exit(f"Network error fetching SHA for {path}: {e}")
|
|
return None
|
|
|
|
|
|
def fetch_source_file(token: str, org: str, repo_name: str, path: str, version: str) -> str | None:
|
|
"""Fetch a source file from GitHub at the given ROCm version.
|
|
|
|
Tries the release branch first, falls back to the release tag.
|
|
Returns the file content as a string, or None if not found at either ref.
|
|
"""
|
|
for ref in _refs_for_version(version):
|
|
url = f"{_GH_API}/repos/{org}/{repo_name}/contents/{path}?ref={ref}"
|
|
try:
|
|
data = _gh_request(token, url)
|
|
return base64.b64decode(data["content"]).decode()
|
|
except HTTPError as e:
|
|
if e.code in (401, 403):
|
|
sys.exit(f"GitHub authentication failed (HTTP {e.code}) — check your token.")
|
|
continue
|
|
except URLError as e:
|
|
sys.exit(f"Network error fetching {path}: {e}")
|
|
return None
|
|
|
|
|
|
def load_yaml_types(yaml_data: dict) -> dict[str, dict[str, str]]:
|
|
"""Return {tag: {type: support}} from precision-support.yaml."""
|
|
result: dict[str, dict[str, str]] = {}
|
|
for group in yaml_data.get("library_groups", []):
|
|
for lib in group.get("libraries", []):
|
|
tag = lib["tag"]
|
|
result[tag] = {dt["type"]: dt["support"] for dt in lib.get("data_types", [])}
|
|
return result
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Scoping
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def sha_filter_scope(token: str, previous: str, current: str) -> list[str]:
|
|
"""Return sorted list of library tags where the source file changed between two ROCm versions.
|
|
|
|
Checks all tracked libraries — no manifest diff. Skips libraries where the
|
|
source file SHA is identical at both tags, writing a skip file so Claude knows why.
|
|
"""
|
|
print(f"SHA-filtering all tracked libraries {previous} → {current}...", file=sys.stderr)
|
|
changed = []
|
|
for lib, config in SOURCE_CONFIG.items():
|
|
if config.get("note"):
|
|
continue # permanently skipped libraries (type-agnostic, internal, etc.)
|
|
org, repo, path = config["org"], config["repo"], config["path"]
|
|
sha_prev = fetch_file_sha(token, org, repo, path, previous)
|
|
sha_curr = fetch_file_sha(token, org, repo, path, current)
|
|
if sha_prev is None and sha_curr is None:
|
|
Path(f"/tmp/precision_{lib}_skip.txt").write_text(
|
|
f"Source file not found at either {previous} or {current}",
|
|
encoding="utf-8",
|
|
)
|
|
print(f" {lib}: not found at either version — skip", file=sys.stderr)
|
|
elif sha_prev == sha_curr:
|
|
Path(f"/tmp/precision_{lib}_skip.txt").write_text(
|
|
f"Source file unchanged between {previous} and {current} (SHA match)",
|
|
encoding="utf-8",
|
|
)
|
|
print(f" {lib}: unchanged — skip", file=sys.stderr)
|
|
else:
|
|
changed.append(lib)
|
|
print(f" {lib}: changed — include", file=sys.stderr)
|
|
print(f" {len(changed)} libraries with changed source files", file=sys.stderr)
|
|
return sorted(changed)
|
|
|
|
|
|
def scope_from_manifest(token: str, previous: str, current: str) -> list[str]:
|
|
"""Return sorted list of library tags that changed between two ROCm versions."""
|
|
print(f"Diffing manifests {previous} → {current}...", file=sys.stderr)
|
|
prev_manifest = fetch_manifest(previous)
|
|
curr_manifest = fetch_manifest(current)
|
|
added, removed, version_changed, _ = diff_manifests(prev_manifest, curr_manifest)
|
|
|
|
changed_components = (
|
|
set(added)
|
|
| set(removed)
|
|
| {c.name for c in version_changed}
|
|
)
|
|
|
|
# Expand monorepo entries
|
|
for monorepo, sub_libs in MONOREPO_LIBS.items():
|
|
if monorepo in changed_components:
|
|
changed_components |= set(sub_libs)
|
|
|
|
tags = sorted(
|
|
MANIFEST_TO_TAG[name]
|
|
for name in changed_components
|
|
if name in MANIFEST_TO_TAG
|
|
)
|
|
print(f" {len(changed_components)} components changed → {len(tags)} tracked libraries", file=sys.stderr)
|
|
return tags
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Fetching
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def fetch_library(token: str, lib: str, version: str) -> None:
|
|
"""Fetch source file and YAML entry for one library, write to /tmp/."""
|
|
config = SOURCE_CONFIG.get(lib)
|
|
if not config:
|
|
print(f" {lib}: no SOURCE_CONFIG entry — skipping", file=sys.stderr)
|
|
return
|
|
|
|
note = config.get("note")
|
|
if note:
|
|
Path(f"/tmp/precision_{lib}_skip.txt").write_text(note, encoding="utf-8")
|
|
print(f" {lib}: skipped — {note}", file=sys.stderr)
|
|
return
|
|
|
|
content = fetch_source_file(
|
|
token,
|
|
config["org"],
|
|
config["repo"],
|
|
config["path"],
|
|
version,
|
|
)
|
|
|
|
if content is None:
|
|
Path(f"/tmp/precision_{lib}_skip.txt").write_text(
|
|
f"Source file not found: {config['path']}", encoding="utf-8"
|
|
)
|
|
print(f" {lib}: source file not found", file=sys.stderr)
|
|
return
|
|
|
|
Path(f"/tmp/precision_{lib}_source.txt").write_text(content, encoding="utf-8")
|
|
|
|
ref = _version_to_ref(version)
|
|
gh_url = f"https://github.com/{config['org']}/{config['repo']}/blob/{ref}/{config['path']}"
|
|
Path(f"/tmp/precision_{lib}_url.txt").write_text(gh_url, encoding="utf-8")
|
|
|
|
print(f" {lib}: source written ({len(content):,} chars)", file=sys.stderr)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Entry point
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(
|
|
description="Fetch precision support source files for Claude to compare."
|
|
)
|
|
parser.add_argument("-t", "--token", required=True, help="GitHub personal access token")
|
|
|
|
mode = parser.add_mutually_exclusive_group(required=True)
|
|
mode.add_argument("--previous", help="Previous ROCm version (use with --current)")
|
|
mode.add_argument("--version", help="ROCm version for explicit --libs mode")
|
|
|
|
parser.add_argument("--current", help="Current ROCm version (use with --previous)")
|
|
parser.add_argument("--libs", help="Comma-separated library tags (use with --version)")
|
|
parser.add_argument(
|
|
"--sha-filter",
|
|
action="store_true",
|
|
help="Check all tracked libraries; skip those whose source file SHA is unchanged. "
|
|
"Use with --previous/--current. No manifest diff is performed.",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
# Resolve version and library list
|
|
if args.previous:
|
|
if not args.current:
|
|
sys.exit("--current is required when using --previous")
|
|
current_version = args.current
|
|
if args.sha_filter:
|
|
libs = sha_filter_scope(args.token, args.previous, args.current)
|
|
else:
|
|
libs = scope_from_manifest(args.token, args.previous, args.current)
|
|
else:
|
|
if args.sha_filter:
|
|
sys.exit("--sha-filter requires --previous and --current")
|
|
if not args.libs:
|
|
sys.exit("--libs is required when using --version")
|
|
current_version = args.version
|
|
libs = [lib.strip() for lib in args.libs.split(",")]
|
|
|
|
# Fetch YAML once
|
|
print(f"\nFetching precision-support.yaml for ROCm {current_version}...", file=sys.stderr)
|
|
yaml_data = fetch_yaml(args.token, current_version)
|
|
yaml_types = load_yaml_types(yaml_data)
|
|
|
|
# Write YAML entries for all tracked libraries (not just changed ones —
|
|
# Claude needs the full context for comparison)
|
|
for lib, types in yaml_types.items():
|
|
Path(f"/tmp/precision_{lib}_yaml.txt").write_text(
|
|
yaml.dump({lib: types}, allow_unicode=True, default_flow_style=False),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
# Write manifest-scoped library list so Claude knows what to check
|
|
libs_file = Path("/tmp/precision_libs.txt")
|
|
libs_file.write_text("\n".join(libs), encoding="utf-8")
|
|
print(f"\nLibraries to check: {', '.join(libs)}", file=sys.stderr)
|
|
|
|
# Fetch source files
|
|
print("\nFetching source files...", file=sys.stderr)
|
|
for lib in libs:
|
|
fetch_library(args.token, lib, current_version)
|
|
|
|
print(f"\nDone. Files written to /tmp/precision_*", file=sys.stderr)
|
|
print(f"Library list: /tmp/precision_libs.txt", file=sys.stderr)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|