Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,15 @@
# TIER 1: High-specificity combined rules (message + file/class)
# ==================================================================

# --- PT2.0 - Convolution: conv2d backward parametrized skipped on ROCm ---
# skipIfRocmArch(...) skips test_conv2d_backward_parametrized (see upstream
# #188671, CI timeout). Categorize it as a convolution issue rather than the
# generic Misc "test skipped on ('gfx...')" bucket below. Must precede that
# Misc arch rule.
{"reason": "PT2.0 - Convolution",
"msg": r"test skipped on \('gfx",
"name": r"(?i)conv2d_backward"},

# --- bfloat16_SDPA_ME: dropout mask in test_transformers with bfloat16 in TEST NAME ---
# Must be before generic SDPA_ME rule
{"reason": "bfloat16_SDPA_ME",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,23 @@


def classify_log_file(filename):
"""Return (platform, test_config, shard_num) from a log filename like rocm3.txt."""
"""Return (platform, test_config, shard_num) from a log filename like rocm3.txt.

Commit-vs-commit parity prefixes log files with the short commit SHA
(for example, 09e0c59b_rocm3.txt). In that mode the SHA label is the
platform name used by generate_summary.py, so preserve it here.
"""
stem = Path(filename).stem
label = None
m = re.match(r"(?P<label>[0-9a-f]{8,40})_(?P<stem>.+)", stem)
if m:
label = m.group("label")[:8]
stem = m.group("stem")
for prefix, (platform, test_config) in sorted(LOG_FILE_MAP.items(), key=lambda x: -len(x[0])):
if stem.startswith(prefix):
remainder = stem[len(prefix):]
if remainder.isdigit():
return platform, test_config, int(remainder)
return label or platform, test_config, int(remainder)
return None, None, None


Expand Down
76 changes: 60 additions & 16 deletions .automation_scripts/pytorch-unit-test-scripts/download_testlogs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,36 @@ def get_workflow_jobs(wf, all_attempts=False):
jobs += page_json["jobs"]
return jobs

def derive_shard_count(wf, job_prefix, test_config, fallback):
"""Return the shard total upstream actually used for this (job_prefix,
test_config), parsed from job names like
'<job_prefix> / test (<config>, <idx>, <total>, ...)' -> <total>.

Shard counts differ between an arch's primary workflow and its fallback
(e.g. mi200 default is 6 shards in rocm-mi200 but 10 in the
trunk-rocm-sandbox fallback), so we read the real total from the resolved
run instead of assuming the config value - otherwise the constructed
"(default, i, 6)" keys miss the actual "(default, i, 10)" jobs. Falls back
to `fallback` when the run has no matching jobs.
"""
try:
jobs = get_workflow_jobs(wf)
except Exception:
return fallback
pat = re.compile(
re.escape(job_prefix) + r" / \S+ \(" + re.escape(test_config) + r", \d+, (\d+)"
)
totals = {}
for job in jobs:
m = pat.search(job.get("name", ""))
if m:
total = int(m.group(1))
totals[total] = totals.get(total, 0) + 1
if not totals:
return fallback
# Most common total wins (guards against a stray reshaped/duplicate job).
return sorted(totals.items(), key=lambda kv: (-kv[1], -kv[0]))[0][0]

def get_check_runs_for_commit(sha, prefix):
"""Get check runs for a commit filtered by name prefix.

Expand Down Expand Up @@ -526,7 +556,7 @@ def parse_args():
parser.add_argument('--no_rocm', action='store_true')
parser.add_argument('--no_cuda', action='store_true')
parser.add_argument('--pr_id', type=int, help='The pull request ID')
parser.add_argument('--arch', type=str, choices=['mi200', 'mi300', 'mi350', 'navi31', 'nightly'], default='mi350', help='ROCm GPU architecture (mi200, mi300, mi350, navi31, or nightly, default: mi350)')
parser.add_argument('--arch', type=str, choices=['mi200', 'mi300', 'mi350', 'navi31', 'nightly', 'preview'], default='mi350', help='ROCm GPU architecture (mi200, mi300, mi350, navi31, nightly, or preview, default: mi350)')
parser.add_argument('--include_inductor_periodic', action='store_true', help='Also download inductor-periodic benchmark artifacts (into a separate directory, not included in parity CSV)')
parser.add_argument('--baseline_sha', type=str, help='Baseline commit SHA to compare against. Downloads the same ROCm workflows for this commit into baseline_xml/.')
return parser.parse_args()
Expand Down Expand Up @@ -654,17 +684,37 @@ def main():
token = os.getenv('GITHUB_TOKEN', '...')
global authentication_headers
authentication_headers = {'Authorization': f'token {token}'}
if (args.pr_id and args.sha1) or (not args.pr_id and not args.sha1):
error_msg = "Error: Please provide either pr_id or sha!"
status = "success"
# An empty --sha1 (or the literal "latest") means "use the latest green
# run on main" rather than a specific commit. download_workflow_run()
# already resolves that when given no head_sha, so treat these as unset.
sha_is_latest = (not args.sha1) or (str(args.sha1).strip().lower() == "latest")
if args.pr_id and not sha_is_latest:
error_msg = "Error: Please provide either pr_id or sha (not both)!"
print(error_msg)
sys.exit(1)
if args.pr_id:
pr_id = args.pr_id
sha = get_latest_commit_sha(pr_id, token)
else:
sha = get_latest_commit_sha(pr_id, token)
elif not sha_is_latest:
sha = args.sha1
pr_id = None
status = "success"
else:
# No pr_id and no explicit sha: resolve the latest green ROCm run on
# main and use its head commit as the target sha.
pr_id = None
print("No sha or pr_id given; resolving latest green ROCm run on main...")
latest_wf = download_workflow_run(
created=args.created,
max_pages=args.max_pages,
workflow=ROCmWorkflowNames["default"],
sha=None,
ignore_status=args.ignore_status,
status=status,
error_msg="Error: could not find a latest green ROCm run on main",
)
sha = latest_wf["head_sha"]
print(f"Resolved 'latest' to sha: {sha}")
print(f"sha: {sha}")

# When comparing two commits, prefix log filenames with short SHAs
Expand Down Expand Up @@ -742,10 +792,7 @@ def main():
# HUD link: https://hud.pytorch.org/hud/pytorch/pytorch/main/1?per_page=50&name_filter=rocm
# Make sure "Hide unstable jobs" is unselected, in case ROCm jobs are marked as unstable

if arch == "mi350":
dist_shards = 3 if not periodic_fallback_used else rocm_shards["distributed"]
else:
dist_shards = rocm_shards["distributed"]
dist_shards = derive_shard_count(periodic_wf, dist_job_prefix, "distributed", rocm_shards["distributed"])
print(f"Using final ROCm shard count {dist_shards} for distributed")

if not args.artifacts_only:
Expand Down Expand Up @@ -805,10 +852,7 @@ def main():
# Download logs
# If logs aren't found you might want to check the HUD for the correct tags
# HUD link: https://hud.pytorch.org/hud/pytorch/pytorch/main/1?per_page=50&name_filter=rocm
if arch == "mi350":
default_shards = 6 if default_fallback_used else rocm_shards["default"]
else:
default_shards = rocm_shards["default"]
default_shards = derive_shard_count(rocm_wf, rocm_job_prefix['default'], "default", rocm_shards["default"])
print(f"Using final ROCm shard count {default_shards} for default")

if not args.artifacts_only:
Expand Down Expand Up @@ -863,12 +907,12 @@ def main():

folder_list = get_or_create_test_folder(inductor_wf_rocm)

inductor_shards = rocm_shards["inductor"]
print(f"Using final ROCm shard count {inductor_shards} for inductor")
if inductor_fallback_used and arch in inductor_fallbacks:
inductor_job_prefix = inductor_fallbacks[arch][1]
else:
inductor_job_prefix = rocm_job_prefix['inductor']
inductor_shards = derive_shard_count(inductor_wf_rocm, inductor_job_prefix, "inductor", rocm_shards["inductor"])
print(f"Using final ROCm shard count {inductor_shards} for inductor")

# Download logs
if not args.artifacts_only:
Expand Down
27 changes: 21 additions & 6 deletions .automation_scripts/pytorch-unit-test-scripts/generate_summary.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,10 @@ def test_config_stats_keys(s1_name, s2_name, has_set2=True):
f'TOTAL {s1}',
]
return [
f'SKIPPED (on {s1_name}, but not on {s2_name})',
f'SKIPPED (on {s1_name}, PASSED on {s2_name})',
f'SKIPPED (on {s1_name})',
f'SKIPPED (on {s2_name})',
f'MISSED (MISSED on {s1_name}, NOT SKIPPED on {s2_name})',
f'MISSED (on {s1_name}, PASSED on {s2_name})',
f'{s1}ONLY (PASSED on {s1}, NOT PASSED on {s2})',
s2,
s1,
Expand All @@ -141,15 +141,18 @@ def compute_test_config_stats(rows, s1_col, s2_col, s1_name, s2_name, has_set2=T
vals[keys[4]] = sum(1 for r in rows if r[s1_col].strip())
return vals

# Count a ROCm SKIPPED/MISSED test as a disagreement only when CUDA actually
# PASSED it. This excludes parametrization variants CUDA never runs (CUDA
# MISSED) or also skips, which aren't real ROCm-vs-CUDA coverage gaps.
s1_skip_not_s2 = sum(
1 for r in rows
if r[s1_col] == 'SKIPPED' and r[s2_col] != 'SKIPPED'
if r[s1_col] == 'SKIPPED' and r[s2_col] == 'PASSED'
)
s1_skip = sum(1 for r in rows if r[s1_col] == 'SKIPPED')
s2_skip = sum(1 for r in rows if r[s2_col] == 'SKIPPED')
s1_miss_not_s2_skip = sum(
1 for r in rows
if r[s1_col] == 'MISSED' and r[s2_col] != 'SKIPPED'
if r[s1_col] == 'MISSED' and r[s2_col] == 'PASSED'
)
only_s1 = sum(
1 for r in rows
Expand Down Expand Up @@ -233,11 +236,11 @@ def safe_float(v):
wf_rows = [r for r in rows if r['test_config'] == wf]
s1_skip_not_s2 = sum(
1 for r in wf_rows
if r[s1_col] == 'SKIPPED' and r[s2_col] != 'SKIPPED'
if r[s1_col] == 'SKIPPED' and r[s2_col] == 'PASSED'
)
s1_miss_not_s2_skip = sum(
1 for r in wf_rows
if r[s1_col] == 'MISSED' and r[s2_col] != 'SKIPPED'
if r[s1_col] == 'MISSED' and r[s2_col] == 'PASSED'
)
total_disagree += s1_skip_not_s2 + s1_miss_not_s2_skip
total_s2 += sum(1 for r in wf_rows if r[s2_col].strip() and r[s2_col].strip() != 'MISSED')
Expand Down Expand Up @@ -639,6 +642,18 @@ def build_rows(args, archs, arch_data):

if args.sha:
out.append(('__header__', f'Commit SHA: {args.sha}'))
# Link straight to the upstream HUD page filtered (regex) to the trunk
# CUDA / inductor / rocm test jobs this report is built from, so a
# reviewer can jump from the summary to the matching CI jobs. Parens and
# pipes are percent-encoded to keep the markdown link valid.
hud_url = (
f'https://hud.pytorch.org/hud/pytorch/pytorch/{args.sha}/1'
'?per_page=50'
'&name_filter=%28trunk.*cuda%7Cinductor%7Crocm%29.*test.*'
'%28default%7Cdistributed%7Cinductor%29%2C'
'&useRegexFilter=true'
)
out.append(('__header__', f'HUD: [parity jobs for this commit]({hud_url})'))
if args.pr_id:
out.append(('__header__', f'PR ID: {args.pr_id}'))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,13 @@
"inductor": [{ "workflow": "rocm-nightly", "job_prefix": "linux-noble-rocm-nightly-py3.12-gfx942" }],
"shard_counts": { "default": 6, "distributed": 3, "inductor": 2 },
"checkrun_regex": "rocm-nightly.*/ test [(](default|distributed|inductor),"
},
"preview": {
"default": [{ "workflow": "rocm-preview", "job_prefix": "linux-noble-rocm-preview-py3.12-gfx942" }],
"distributed": [{ "workflow": "rocm-preview", "job_prefix": "linux-noble-rocm-preview-py3.12-gfx942" }],
"inductor": [{ "workflow": "rocm-preview", "job_prefix": "linux-noble-rocm-preview-py3.12-gfx942" }],
"shard_counts": { "default": 6, "distributed": 3, "inductor": 2 },
"checkrun_regex": "linux-noble-rocm-preview-py3.12-gfx942 / test [(](default|distributed|inductor),"
}
}
}
Loading