Skip to content

Commit 71c8abe

Browse files
authored
Merge pull request #6462 from hpcaitech/ci/three-layer-workflow-validation
[ci]: introduce three-layer workflows and qualify GPU batches
2 parents df01070 + 72d4aab commit 71c8abe

65 files changed

Lines changed: 2202 additions & 333 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/actionlint.yaml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
self-hosted-runner:
2+
labels:
3+
- colossalai-e1-h20
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
#!/usr/bin/env python3
2+
"""Report idle H20 capacity for the hourly layered-CI gate."""
3+
4+
import argparse
5+
import json
6+
import os
7+
import socket
8+
import subprocess
9+
import sys
10+
from datetime import datetime, timezone
11+
from pathlib import Path
12+
13+
from run_gpu_batch import find_idle, parse_pool, query_gpus
14+
15+
16+
def utcnow():
17+
return datetime.now(timezone.utc).isoformat()
18+
19+
20+
def readiness(idle_count, layer2_required=2, layer3_required=8):
21+
if idle_count < 0 or layer2_required < 1 or layer3_required < layer2_required:
22+
raise ValueError("Invalid GPU readiness thresholds")
23+
return {
24+
"layer2_ready": idle_count >= layer2_required,
25+
"layer3_ready": idle_count >= layer3_required,
26+
}
27+
28+
29+
def append_outputs(path, values):
30+
if path is None:
31+
return
32+
with path.open("a", encoding="utf-8") as output:
33+
for key, value in values.items():
34+
if isinstance(value, bool):
35+
value = str(value).lower()
36+
output.write(f"{key}={value}\n")
37+
38+
39+
def append_summary(path, result):
40+
if path is None:
41+
return
42+
with path.open("a", encoding="utf-8") as summary:
43+
summary.write("## Hourly GPU capacity gate\n\n")
44+
summary.write(f"- Host: `{result['host']}`\n")
45+
summary.write(f"- Runner: `{result['runner']}`\n")
46+
summary.write(f"- Idle GPU indices: `{result['idle_gpu_indices']}`\n")
47+
summary.write(f"- Layer 2 ready (needs 2): `{str(result['layer2_ready']).lower()}`\n")
48+
summary.write(f"- Layer 3 ready (needs 8): `{str(result['layer3_ready']).lower()}`\n")
49+
50+
51+
def main():
52+
parser = argparse.ArgumentParser(description=__doc__)
53+
parser.add_argument("--layer2-required", type=int, default=2)
54+
parser.add_argument("--layer3-required", type=int, default=8)
55+
parser.add_argument(
56+
"--lock",
57+
type=Path,
58+
default=Path(os.environ.get("CI_GPU_LOCK", "/tmp/colossalai-ci-gpu.lock")),
59+
)
60+
parser.add_argument("--github-output", type=Path, default=os.environ.get("GITHUB_OUTPUT"))
61+
parser.add_argument("--summary", type=Path, default=os.environ.get("GITHUB_STEP_SUMMARY"))
62+
args = parser.parse_args()
63+
64+
if os.name != "posix":
65+
raise RuntimeError("GPU capacity checks require a Linux runner")
66+
import fcntl
67+
68+
args.lock.parent.mkdir(parents=True, exist_ok=True)
69+
result = {
70+
"host": socket.gethostname().split(".")[0],
71+
"runner": os.environ.get("RUNNER_NAME"),
72+
"checked_at": utcnow(),
73+
}
74+
75+
try:
76+
with args.lock.open("w") as lock:
77+
fcntl.flock(lock, fcntl.LOCK_EX)
78+
inventory, processes = query_gpus()
79+
idle = find_idle(inventory, processes, parse_pool(os.environ.get("COLOSSAL_GPU_POOL")))
80+
result["idle_gpu_count"] = len(idle)
81+
result["idle_gpu_indices"] = [index for index, _ in idle]
82+
result.update(readiness(len(idle), args.layer2_required, args.layer3_required))
83+
append_outputs(
84+
args.github_output,
85+
{
86+
"idle_gpu_count": result["idle_gpu_count"],
87+
"layer2_ready": result["layer2_ready"],
88+
"layer3_ready": result["layer3_ready"],
89+
},
90+
)
91+
append_summary(args.summary, result)
92+
except (RuntimeError, OSError, ValueError, subprocess.SubprocessError) as error:
93+
result["error"] = str(error)
94+
print(json.dumps(result, indent=2), flush=True)
95+
return 1
96+
97+
print(json.dumps(result, indent=2), flush=True)
98+
return 0
99+
100+
101+
if __name__ == "__main__":
102+
sys.exit(main())

0 commit comments

Comments
 (0)