已合并
enhancement(ci): 自动检测变更 patch 并优化 NPU 测试流水线 #37907
enhancement(ci): 自动检测变更 patch 并优化 NPU 测试流水线 #37907
已合并
kerer-sk创建于 6月9日
共 11 个文件变更+204-33
@@ -505,7 +505,7 @@ def parse_args():
505 parser = argparse.ArgumentParser(description="Collect and shard test cases")505 parser = argparse.ArgumentParser(description="Collect and shard test cases")
506 parser.add_argument("--test-dir", required=True, help="PyTorch test directory")506 parser.add_argument("--test-dir", required=True, help="PyTorch test directory")
507 parser.add_argument("--case-paths-config", help="case_paths_ci.yml path")507 parser.add_argument("--case-paths-config", help="case_paths_ci.yml path")
508- parser.add_argument("--distributed-shards", type=int, default=2, help="Distributed test shards")508+ parser.add_argument("--distributed-shards", type=int, default=5, help="Distributed test shards")
509 parser.add_argument("--regular-shards", type=int, default=5, help="Regular test shards")509 parser.add_argument("--regular-shards", type=int, default=5, help="Regular test shards")
510 parser.add_argument("--output-dir", required=True, help="Output directory for shard JSONs")510 parser.add_argument("--output-dir", required=True, help="Output directory for shard JSONs")
511 parser.add_argument("--error-log-dir", help="Output directory for collection error logs (default: output-dir/collection_errors)")511 parser.add_argument("--error-log-dir", help="Output directory for collection error logs (default: output-dir/collection_errors)")
@@ -0,0 +1,140 @@
1+#!/bin/bash
2+# ==============================================================================
3+# detect_changed_patches.sh
4+#
5+# Detect changed patch files in test_upstream/ and derive corresponding test files.
6+#
7+# Environment inputs (set by GitHub Actions workflow):
8+# EVENT_NAME - "pull_request" or "workflow_dispatch"
9+# BASE_SHA - PR base commit SHA (pull_request only)
10+# HEAD_SHA - PR head commit SHA (pull_request only)
11+# BASE_REF - PR target branch ref (pull_request only)
12+# INPUT_PATCH_FILES - comma-separated patch paths (workflow_dispatch only)
13+#
14+# Outputs (written to $GITHUB_OUTPUT):
15+# test_patches - comma-separated test_upstream/test/ patch paths
16+# torch_patches - comma-separated test_upstream/torch/ patch paths
17+# test_files - comma-separated derived test file names
18+# has_test_changes - "true" or "false"
19+# has_torch_changes - "true" or "false"
20+# changed_summary - one of: test+torch, test-only, torch-only, none
21+# ==============================================================================
22+set -euo pipefail
23+ 
24+# ------------------------------------------------------------------
25+# Step 1: Collect changed files from the trigger source
26+# ------------------------------------------------------------------
27+if [ "${EVENT_NAME}" = "pull_request" ]; then
28+ echo "=== PR Event: detecting changes ==="
29+ echo "Base SHA: ${BASE_SHA:-unknown}"
30+ echo "Head SHA: ${HEAD_SHA:-unknown}"
31+ 
32+ # HEAD is the PR merge commit (checked out by actions/checkout).
33+ # HEAD^1 = base branch, HEAD^2 = PR head branch.
34+ # Use three-dot (...) to show only PR-side changes relative to merge-base,
35+ # excluding upstream changes that happened after the fork point.
36+ if git cat-file -e HEAD^2 2>/dev/null; then
37+ echo "Using merge commit parents: HEAD^1...HEAD^2 (PR-side changes only)"
38+ CHANGED_FILES=$(git diff --name-only HEAD^1...HEAD^2 -- 'test_upstream/' 2>/dev/null || true)
39+ else
40+ echo "Merge parents not available, falling back to base/head diff"
41+ git fetch --no-tags origin "${BASE_REF}" 2>/dev/null || true
42+ CHANGED_FILES=$(git diff --name-only \
43+ "${BASE_SHA}" "${HEAD_SHA}" \
44+ -- 'test_upstream/' 2>/dev/null || true)
45+ fi
46+else
47+ echo "=== Manual Dispatch: using input ==="
48+ CHANGED_FILES="${INPUT_PATCH_FILES:-}"
49+fi
50+ 
51+echo ""
52+echo "Raw changed files:"
53+echo "${CHANGED_FILES}" | sed 's/^/ /'
54+ 
55+# ------------------------------------------------------------------
56+# Step 2: Normalize (handle comma-separated input from dispatch)
57+# ------------------------------------------------------------------
58+CHANGED_FILES=$(echo "${CHANGED_FILES}" | tr ',' '\n' | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')
59+ 
60+# ------------------------------------------------------------------
61+# Step 3: Classify patches and derive test files
62+# ------------------------------------------------------------------
63+TEST_PATCHES=""
64+TORCH_PATCHES=""
65+TEST_FILES=""
66+ 
67+while IFS= read -r f; do
68+ [ -z "$f" ] && continue
69+ 
70+ case "$f" in
71+ test_upstream/test/*.patch|test_upstream/test/*.diff)
72+ # Derive test file by stripping prefix + suffix:
73+ # test_upstream/test/test_autograd.py.patch → test_autograd.py
74+ # test_upstream/test/ao/test_foo.py.patch → ao/test_foo.py
75+ # test_upstream/test/inductor/test_minifer.diff → inductor/test_minifer.py
76+ TEST_FILE=$(echo "$f" | sed 's|^test_upstream/test/||; s|\.patch$||; s|\.diff$||')
77+ # Ensure .py extension for cases where patch suffix was on bare name (e.g. test_foo.diff)
78+ [[ "$TEST_FILE" != *.py ]] && TEST_FILE="${TEST_FILE}.py"
79+ TEST_PATCHES="${TEST_PATCHES}${f},"
80+ TEST_FILES="${TEST_FILES}${TEST_FILE},"
81+ echo " → test patch: $f → test file: ${TEST_FILE}"
82+ ;;
83+ test_upstream/torch/*.patch|test_upstream/torch/*.diff)
84+ TORCH_PATCHES="${TORCH_PATCHES}${f},"
85+ echo " → torch patch: $f (no direct test mapping)"
86+ ;;
87+ *)
88+ echo " → skipped: $f (not a patch file)"
89+ ;;
90+ esac
91+done <<< "${CHANGED_FILES}"
92+ 
93+# Remove trailing commas
94+TEST_PATCHES="${TEST_PATCHES%,}"
95+TORCH_PATCHES="${TORCH_PATCHES%,}"
96+TEST_FILES="${TEST_FILES%,}"
97+ 
98+# Determine change type flags
99+HAS_TEST="false"
100+HAS_TORCH="false"
101+[ -n "${TEST_PATCHES}" ] && HAS_TEST="true"
102+[ -n "${TORCH_PATCHES}" ] && HAS_TORCH="true"
103+ 
104+# Determine summary string
105+if [ "${HAS_TEST}" = "true" ] && [ "${HAS_TORCH}" = "true" ]; then
106+ CHANGED_SUMMARY="test+torch"
107+elif [ "${HAS_TEST}" = "true" ]; then
108+ CHANGED_SUMMARY="test-only"
109+elif [ "${HAS_TORCH}" = "true" ]; then
110+ CHANGED_SUMMARY="torch-only"
111+else
112+ CHANGED_SUMMARY="none"
113+fi
114+ 
115+# ------------------------------------------------------------------
116+# Step 4: Report and write outputs
117+# ------------------------------------------------------------------
118+echo ""
119+echo "=== Detection Result ==="
120+echo "test_patches=${TEST_PATCHES}"
121+echo "torch_patches=${TORCH_PATCHES}"
122+echo "test_files=${TEST_FILES}"
123+echo "has_test_changes=${HAS_TEST}"
124+echo "has_torch_changes=${HAS_TORCH}"
125+echo "changed_summary=${CHANGED_SUMMARY}"
126+ 
127+{
128+ echo "test_patches=${TEST_PATCHES}"
129+ echo "torch_patches=${TORCH_PATCHES}"
130+ echo "test_files=${TEST_FILES}"
131+ echo "has_test_changes=${HAS_TEST}"
132+ echo "has_torch_changes=${HAS_TORCH}"
133+ echo "changed_summary=${CHANGED_SUMMARY}"
134+} >> "${GITHUB_OUTPUT}"
135+ 
136+if [ "${HAS_TEST}" = "false" ] && [ "${HAS_TORCH}" = "false" ]; then
137+ echo ""
138+ echo "WARNING: No patch files detected in changed files."
139+ echo "If this is a PR, ensure it modifies .patch or .diff files under test_upstream/."
140+fi
@@ -347,22 +347,33 @@ def parse_junit_xml_status(xml_file: Path) -> Dict:
347 # Check <skipped>347 # Check <skipped>
348 skipped_elem = testcase.find("skipped")348 skipped_elem = testcase.find("skipped")
349 if skipped_elem is not None:349 if skipped_elem is not None:
350+ skip_type = skipped_elem.get("type", "")
351+ if skip_type == "pytest.xfail":
352+ result["status"] = "passed"
353+ result["message"] = "xfailed: expected failure"
354+ return result
350 result["status"] = "skipped"355 result["status"] = "skipped"
351- result["message"] = skipped_elem.get("message", "")356+ attr_msg = skipped_elem.get("message", "")
357+ text_msg = (skipped_elem.text or "").strip()
358+ result["message"] = attr_msg + ("\n" + text_msg if text_msg else "")
352 return result359 return result
353 360 
354 # Check <failure>361 # Check <failure>
355 failure_elem = testcase.find("failure")362 failure_elem = testcase.find("failure")
356 if failure_elem is not None:363 if failure_elem is not None:
357 result["status"] = "failed"364 result["status"] = "failed"
358- result["message"] = failure_elem.get("message", "")365+ attr_msg = failure_elem.get("message", "")
366+ text_msg = (failure_elem.text or "").strip()
367+ result["message"] = attr_msg + ("\n" + text_msg if text_msg else "")
359 return result368 return result
360 369 
361 # Check <error>370 # Check <error>
362 error_elem = testcase.find("error")371 error_elem = testcase.find("error")
363 if error_elem is not None:372 if error_elem is not None:
364 result["status"] = "error"373 result["status"] = "error"
365- result["message"] = error_elem.get("message", "")374+ attr_msg = error_elem.get("message", "")
375+ text_msg = (error_elem.text or "").strip()
376+ result["message"] = attr_msg + ("\n" + text_msg if text_msg else "")
366 return result377 return result
367 378 
368 # No failure/error/skipped = passed379 # No failure/error/skipped = passed
@@ -1700,4 +1711,4 @@ def main():
1700 1711 
1701 1712 
1702if __name__ == "__main__":1713if __name__ == "__main__":
1703- main()1714+ main()
@@ -46,8 +46,7 @@ jobs:
46 - name: Checkout repository46 - name: Checkout repository
47 uses: actions/checkout@v447 uses: actions/checkout@v4
48 with:48 with:
49- repository: Ascend/pytorch49+ ref: ${{ github.sha }}
50- ref: v2.7.1
51 fetch-depth: 150 fetch-depth: 1
52 submodules: recursive51 submodules: recursive
53 52 
@@ -22,7 +22,7 @@ on:
22 distributed_shards:22 distributed_shards:
23 required: false23 required: false
24 type: string24 type: string
25- default: '2'25+ default: '5'
26 description: Number of shards for distributed tests26 description: Number of shards for distributed tests
27 regular_shards:27 regular_shards:
28 required: false28 required: false
@@ -65,6 +65,7 @@ jobs:
65 total_cases: ${{ steps.collect_and_shard.outputs.total_cases }}65 total_cases: ${{ steps.collect_and_shard.outputs.total_cases }}
66 66 
67 steps:67 steps:
68+ 
68 - name: Setup NPU test environment69 - name: Setup NPU test environment
69 uses: Ascend/pytorch/.github/actions/setup-npu-test-env@v2.7.170 uses: Ascend/pytorch/.github/actions/setup-npu-test-env@v2.7.1
70 with:71 with:
@@ -21,8 +21,7 @@ jobs:
21 - name: Checkout repository21 - name: Checkout repository
22 uses: actions/checkout@v422 uses: actions/checkout@v4
23 with:23 with:
24- repository: Ascend/pytorch24+ ref: ${{ github.sha }}
25- ref: v2.7.1
26 fetch-depth: 125 fetch-depth: 1
27 26 
28 - name: Clone PyTorch v2.7.1 (for test source)27 - name: Clone PyTorch v2.7.1 (for test source)
@@ -31,13 +31,14 @@ defaults:
31jobs:31jobs:
32 run_tests:32 run_tests:
33 name: test_custom33 name: test_custom
34- runs-on: linux-aarch64-a3-834+ runs-on: linux-aarch64-a3-16
35 timeout-minutes: 180035 timeout-minutes: 1800
36 container:36 container:
37 image: ${{ inputs.docker_image }}37 image: ${{ inputs.docker_image }}
38 options: --user root38 options: --user root
39 39 
40 steps:40 steps:
41+ 
41 - name: Setup NPU test environment42 - name: Setup NPU test environment
42 uses: Ascend/pytorch/.github/actions/setup-npu-test-env@v2.7.143 uses: Ascend/pytorch/.github/actions/setup-npu-test-env@v2.7.1
43 with:44 with:
@@ -62,6 +63,7 @@ jobs:
62 --disabled-testcases pytorch-test-src/test_upstream/disabled_testcases.json \63 --disabled-testcases pytorch-test-src/test_upstream/disabled_testcases.json \
63 --report-dir ${REPORT_DIR} \64 --report-dir ${REPORT_DIR} \
64 --timeout 1200 \65 --timeout 1200 \
66+ --max-workers 16 \
65 --verbose \67 --verbose \
66 2>&1 | tee /tmp/test_custom.log68 2>&1 | tee /tmp/test_custom.log
67 69 
@@ -46,6 +46,7 @@ jobs:
46 fail-fast: false46 fail-fast: false
47 47 
48 steps:48 steps:
49+ 
49 - name: Setup NPU test environment50 - name: Setup NPU test environment
50 uses: Ascend/pytorch/.github/actions/setup-npu-test-env@v2.7.151 uses: Ascend/pytorch/.github/actions/setup-npu-test-env@v2.7.1
51 with:52 with:
@@ -46,6 +46,7 @@ jobs:
46 fail-fast: false46 fail-fast: false
47 47 
48 steps:48 steps:
49+ 
49 - name: Setup NPU test environment50 - name: Setup NPU test environment
50 uses: Ascend/pytorch/.github/actions/setup-npu-test-env@v2.7.151 uses: Ascend/pytorch/.github/actions/setup-npu-test-env@v2.7.1
51 with:52 with:
@@ -80,7 +81,7 @@ jobs:
80 echo "Runner: linux-aarch64-a3-16 (16-card NPU)"81 echo "Runner: linux-aarch64-a3-16 (16-card NPU)"
81 echo "Execution mode: CONCURRENT (16 workers)"82 echo "Execution mode: CONCURRENT (16 workers)"
82 83 
83- # Regular tests: pre-collected cases, 16 concurrent workers84+ # Regular tests: pre-collected cases, 16 concurrent workers (one per NPU device)
84 set +e85 set +e
85 $PYTHON ascend_pytorch/.github/scripts/run_npu_test_shard.py \86 $PYTHON ascend_pytorch/.github/scripts/run_npu_test_shard.py \
86 --cases-json "${CASES_JSON}" \87 --cases-json "${CASES_JSON}" \
@@ -26,7 +26,7 @@ on:
26 distributed_shards:26 distributed_shards:
27 required: false27 required: false
28 type: string28 type: string
29- default: '2'29+ default: '5'
30 description: Number of shards for distributed tests30 description: Number of shards for distributed tests
31 regular_shards:31 regular_shards:
32 required: false32 required: false
@@ -4,28 +4,45 @@ on:
4 pull_request:4 pull_request:
5 paths:5 paths:
6 - '.github/**'6 - '.github/**'
7- workflow_dispatch:7+ - 'test_upstream/**'
8- inputs:
9- distributed_shards:
10- description: 'Number of shards for distributed tests (default 2)'
11- required: false
12- default: '2'
13- type: string
14- regular_shards:
15- description: 'Number of shards for regular tests (default 5)'
16- required: false
17- default: '5'
18- type: string
19- test_files:
20- description: 'Test files to run directly (comma-separated, e.g., "test_meta.py,test_nn.py"). Skip shard assignment if set.'
21- required: false
22- default: ''
23- type: string
24 8 
25jobs:9jobs:
10+ # ============================================================================
11+ # 1. Detect Changed Patches
12+ # ============================================================================
13+ detect:
14+ name: Detect changed patches
15+ runs-on: ubuntu-latest
16+ outputs:
17+ test_files: ${{ steps.detect.outputs.test_files }}
18+ has_test_changes: ${{ steps.detect.outputs.has_test_changes }}
19+ has_torch_changes: ${{ steps.detect.outputs.has_torch_changes }}
20+ steps:
21+ - name: Checkout repository
22+ uses: actions/checkout@v4
23+ with:
24+ fetch-depth: 0
25+ 
26+ - name: Detect changed patch files
27+ id: detect
28+ env:
29+ EVENT_NAME: ${{ github.event_name }}
30+ BASE_SHA: ${{ github.event.pull_request.base.sha }}
31+ HEAD_SHA: ${{ github.event.pull_request.head.sha }}
32+ BASE_REF: ${{ github.base_ref }}
33+ INPUT_PATCH_FILES: ''
34+ run: |
35+ chmod +x .github/scripts/detect_changed_patches.sh
36+ .github/scripts/detect_changed_patches.sh
37+ 
38+ # ============================================================================
39+ # 2. Trigger Tests
40+ # ============================================================================
26 trigger_test:41 trigger_test:
42+ needs: detect
43+ if: always()
27 uses: ./.github/workflows/_torch-npu-upstream-test.yml44 uses: ./.github/workflows/_torch-npu-upstream-test.yml
28 with:45 with:
29- distributed_shards: ${{ github.event.inputs.distributed_shards || '2' }}46+ distributed_shards: '5'
30- regular_shards: ${{ github.event.inputs.regular_shards || '5' }}47+ regular_shards: '5'
31- test_files: ${{ github.event.inputs.test_files || '' }}48+ test_files: ${{ needs.detect.outputs.test_files || '' }}