Skip to content

Commit cbb575a

Browse files
authored
Add multi-device system support for AOT compilation (#133)
* Use `pathlib.Path.suffix` to get file extension in `ninetoothed.aot` * Add C++ class `ThreadSafeUnorderedMap` content string * Change generated kernel source files from C to C++ * Add multi-device system support for AOT * Add a test case for AOT's multi-device system support
1 parent 5d604f8 commit cbb575a

3 files changed

Lines changed: 88 additions & 13 deletions

File tree

src/ninetoothed/aot.py

Lines changed: 67 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import re
44
import subprocess
55
import tempfile
6+
import textwrap
67
import uuid
78

89
import ninetoothed.dtype
@@ -33,7 +34,7 @@ def aot(
3334
output_contents = _aot(func, caller, kernel_name, num_warps, num_stages)
3435

3536
for output_name, output_content in output_contents.items():
36-
output_path = output_dir / f"{kernel_name}{output_name[-2:]}"
37+
output_path = output_dir / f"{kernel_name}{pathlib.Path(output_name).suffix}"
3738

3839
with open(output_path, "w") as f:
3940
f.write(output_content)
@@ -121,24 +122,48 @@ def _find_tensor_by_source_name(tensors, name):
121122
pattern = rf"\({', '.join(rf'(.*) {param}' for param in param_strings)}\)"
122123
c_param_type_strings = re.search(pattern, c_header_file).groups()
123124

125+
kernel_name_with_hash = f"{kernel_name}_{signature_hash}"
126+
124127
unparser = _Unparser(c_param_type_strings)
125128

126129
launch_func_unparsed = unparser.unparse(launch_func)
130+
launch_func_unparsed_lines = launch_func_unparsed.splitlines()
131+
launch_func_unparsed_lines.insert(1, f"{_INDENTATION}cuCtxGetId(NULL, &ctx_id);\n")
132+
launch_func_unparsed_lines.insert(1, f"{_INDENTATION}unsigned long long ctx_id;")
133+
launch_func_unparsed = "\n".join(launch_func_unparsed_lines)
127134
launch_func_unparsed = launch_func_unparsed.replace(
128-
func.__name__, f"{kernel_name}_{signature_hash}"
135+
func.__name__, f"kernels[ctx_id].{kernel_name_with_hash}"
129136
)
130137

131-
c_source_file = f"{c_source_file}\n{launch_func_unparsed}\n"
132138
c_source_file = c_source_file.replace("<stdint.h>", f'"{_HEADER_PATH}"')
133139
output_contents[c_source_file_name] = c_source_file
134140

135141
c_header_file = f'{c_header_file}\n#ifdef __cplusplus\nextern "C" {unparser.header};\n#else\n{unparser.header};\n#endif\n'
136142
c_header_file = c_header_file.replace("<stdint.h>", f'"{_HEADER_PATH}"')
137143
output_contents[c_header_file_name] = c_header_file
138144

145+
kernel_start = c_source_file.find("//")
146+
kernel_end = len(c_source_file)
147+
cpp_source_file = (
148+
c_source_file[:kernel_start]
149+
+ f"namespace {kernel_name_with_hash} {{\n"
150+
+ "struct Kernel {\n"
151+
+ textwrap.indent(c_source_file[kernel_start:kernel_end], _INDENTATION)
152+
+ "};\n"
153+
+ textwrap.indent(c_source_file[kernel_end:], _INDENTATION)
154+
+ "}\n"
155+
+ f"\nstatic ThreadSafeUnorderedMap<unsigned long long, {kernel_name_with_hash}::Kernel> kernels;\n"
156+
+ f'\nextern "C" {launch_func_unparsed}\n'
157+
)
158+
cpp_source_file_name = f"{kernel_name}.{signature_hash}.cpp"
159+
output_contents[cpp_source_file_name] = cpp_source_file
160+
output_contents.pop(c_source_file_name)
161+
139162
return output_contents
140163

141164

165+
_INDENTATION = " "
166+
142167
_MACRO_MAPPING = {True: ("NINETOOTHED_TRUE", 1), False: ("NINETOOTHED_FALSE", 0)}
143168

144169
_DTYPE_MAPPING = {
@@ -163,6 +188,41 @@ def _find_tensor_by_source_name(tensors, name):
163188

164189
_DATA_TYPE_BODY_CONTENT = ",\n ".join(_DTYPE_MAPPING.values())
165190

191+
_THREAD_SAFE_UNORDERED_MAP_CONTENT = """#include <mutex>
192+
#include <shared_mutex>
193+
#include <unordered_map>
194+
195+
template<typename Key, typename Value>
196+
class ThreadSafeUnorderedMap {
197+
public:
198+
Value& operator[](const Key& key) {
199+
{
200+
std::shared_lock lock{mutex};
201+
202+
auto iter = map.find(key);
203+
204+
if (iter != map.end()) {
205+
return iter->second;
206+
}
207+
}
208+
209+
std::unique_lock lock{mutex};
210+
211+
auto iter = map.find(key);
212+
213+
if (iter == map.end()) {
214+
iter = map.emplace(key, Value{}).first;
215+
}
216+
217+
return iter->second;
218+
}
219+
220+
private:
221+
std::unordered_map<Key, Value> map;
222+
223+
mutable std::shared_mutex mutex;
224+
};"""
225+
166226
_HEADER_CONTENT = f"""#ifndef NINETOOTHED_H
167227
#define NINETOOTHED_H
168228
@@ -184,6 +244,10 @@ def _find_tensor_by_source_name(tensors, name):
184244
185245
typedef int NineToothedResult;
186246
247+
#ifdef __cplusplus
248+
{_THREAD_SAFE_UNORDERED_MAP_CONTENT}
249+
#endif
250+
187251
#endif // NINETOOTHED_H
188252
"""
189253

src/ninetoothed/build.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,15 +80,15 @@ def build(premake, configs, *, caller=None, kernel_name=None, output_dir=None):
8080
f"{type} {param}" for param, type in zip(param_names, param_types)
8181
)
8282

83-
source_file_name = f"{kernel_name}.c"
83+
source_file_name = f"{kernel_name}.cpp"
8484
header_file_name = f"{kernel_name}.h"
8585

8686
func_sig = f"NineToothedResult launch_{kernel_name}({param_decls})"
8787

8888
joined_launches = "\n".join(launches)
8989

9090
op_decl = f'#ifdef __cplusplus\nextern "C" {func_sig};\n#else\n{func_sig};\n#endif'
91-
op_def = f"""{func_sig} {{
91+
op_def = f"""extern "C" {func_sig} {{
9292
{joined_launches}
9393
return 1;
9494
}}"""

tests/test_aot.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
"dtype, ninetoothed_dtype", ((torch.bfloat16, ninetoothed.bfloat16),)
2525
)
2626
@pytest.mark.parametrize("size", (45327,))
27-
def test_add(size, dtype, device, ninetoothed_dtype):
27+
@pytest.mark.parametrize("test_multi_device", (False, True))
28+
def test_add(test_multi_device, size, dtype, device, ninetoothed_dtype):
2829
def _arrangement(input, other, output):
2930
def _arrange(tensor):
3031
return tensor.tile((256,))
@@ -52,15 +53,25 @@ def _application(input, other, output):
5253

5354
shape = (size,)
5455

55-
input = torch.randn(shape, dtype=dtype, device=device)
56-
other = torch.randn(shape, dtype=dtype, device=device)
57-
output = torch.empty_like(input)
56+
if test_multi_device:
57+
if torch.cuda.device_count() < 2:
58+
pytest.skip("multi-device testing requires at least 2 devices")
5859

59-
_run_launch_func(launch_func, input, other, output)
60+
devices = (f"{device}:0", f"{device}:1")
61+
else:
62+
devices = (device,)
6063

61-
expected = torch.add(input, other)
64+
for device in devices:
65+
with torch.cuda.Stream(device=device):
66+
input = torch.randn(shape, dtype=dtype, device=device)
67+
other = torch.randn(shape, dtype=dtype, device=device)
68+
output = torch.empty_like(input)
6269

63-
assert torch.allclose(output, expected)
70+
_run_launch_func(launch_func, input, other, output)
71+
72+
expected = torch.add(input, other)
73+
74+
assert torch.allclose(output, expected)
6475

6576

6677
@pytest.mark.parametrize("device", get_available_devices())
@@ -346,7 +357,7 @@ def _compile_library(kernel_name, output_dir):
346357
"-lcuda",
347358
"-o",
348359
output_dir / f"{kernel_name}.so",
349-
] + list(output_dir.glob(f"{kernel_name}*.c"))
360+
] + list(output_dir.glob(f"{kernel_name}*.cpp"))
350361

351362
subprocess.run(command, check=True)
352363

0 commit comments

Comments
 (0)