-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathmain.py
More file actions
126 lines (101 loc) · 3.51 KB
/
main.py
File metadata and controls
126 lines (101 loc) · 3.51 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import argparse
from pathlib import Path
import modal
def main():
parser = argparse.ArgumentParser(
prog="mcc", description="Compile and Run CUDA C scripts with Modal"
)
parser.add_argument("input", help="Input CUDA C file", type=Path)
parser.add_argument(
"--app",
help="App name to run the CUDA script; defaults to the input file name",
type=str,
)
parser.add_argument(
"--gpu",
help="Choose the GPU to run the script",
type=str,
default="T4",
choices=[
"T4",
"L4",
"A10",
"A100",
"A100-40GB",
"A100-80GB",
"L40S",
"H100",
"H200",
"B200",
],
)
parser.add_argument(
"--image",
type=str,
default="nvidia/cuda:12.4.1-devel-ubuntu22.04",
help="Container image to use for Modal (registry reference)",
)
parser.add_argument(
"--timeout",
type=int,
default=60 * 10,
help="Execution timeout in seconds for the CUDA run (default: 600)",
)
parser.add_argument(
"--nvcc-arg",
dest="nvcc_args",
action="append",
default=None,
metavar="FLAG",
help="Extra argument to pass to nvcc; repeat for multiple flags",
)
args = parser.parse_args()
if not args.input.exists():
parser.error(f"File {args.input} not found")
if args.input.suffix.lower() != ".cu":
parser.error("Input file must have a .cu extension")
code = args.input.read_text()
if not code.strip():
parser.error(f"File {args.input} is empty")
app_name = args.app or args.input.name
cuda_image = modal.Image.from_registry(args.image, add_python="3.12")
app = modal.App(app_name, image=cuda_image)
@app.function(gpu=args.gpu, timeout=args.timeout, serialized=True)
def compile_and_run_cuda(cuda_source: str, nvcc_args: list[str] | None = None):
import subprocess
import uuid
from pathlib import Path
run_id = uuid.uuid4().hex
source_path = Path(f"/tmp/{run_id}.cu")
binary_path = Path(f"/tmp/{run_id}")
source_path.write_text(cuda_source)
def _run_command(cmd: list[str]) -> subprocess.CompletedProcess[str]:
print(f"$ {' '.join(cmd)}")
completed = subprocess.run(cmd, capture_output=True, text=True, check=False)
if completed.stdout:
print(completed.stdout)
if completed.stderr:
print(completed.stderr)
return completed
try:
extra_args = nvcc_args or []
compile_result = _run_command(
["nvcc", str(source_path), "-o", str(binary_path), *extra_args]
)
if compile_result.returncode != 0:
raise RuntimeError("nvcc failed to compile the CUDA source file")
execution_result = _run_command([str(binary_path)])
if execution_result.returncode != 0:
raise RuntimeError(
"Executable exited with a non-zero status while running the CUDA program"
)
return execution_result.stdout
finally:
for path in (source_path, binary_path):
if path.exists():
path.unlink()
with modal.enable_output():
with app.run():
compile_and_run_cuda.remote(code, args.nvcc_args or [])
if __name__ == "__main__":
main()