Skip to content

Commit a75d742

Browse files
authored
Fix Triton AOT multi-context launches (#195)
* Add Triton AOT multi-context design * Fix Triton AOT multi-context launches * Cover fresh Triton AOT context guard * Remove development process documents
1 parent a0cd5bb commit a75d742

3 files changed

Lines changed: 674 additions & 14 deletions

File tree

src/ninetoothed/backends/materializers/triton.py

Lines changed: 240 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import ctypes
44
import functools
55
import os
6+
import re
67
import shutil
78
import subprocess
89
import sys
@@ -19,10 +20,22 @@
1920
artifact_directory,
2021
cache_lock,
2122
compilation_cache_key,
23+
read_manifest,
2224
write_manifest,
2325
write_source,
2426
)
2527

28+
_TRITON_AOT_LAUNCHER_SCHEMA = 1
29+
_TRITON_MODULE_PATTERN = re.compile(
30+
r"^CUmodule ([A-Za-z_][A-Za-z0-9_]*)_mod = NULL;$", re.MULTILINE
31+
)
32+
_TRITON_FUNCTION_PATTERN = re.compile(
33+
r"^CUfunction ([A-Za-z_][A-Za-z0-9_]*)_func = NULL;$", re.MULTILINE
34+
)
35+
_TRITON_LOADER_PATTERN = re.compile(
36+
r"^void load_([A-Za-z_][A-Za-z0-9_]*)\(\) \{$", re.MULTILINE
37+
)
38+
2639

2740
class TritonMaterializer(Materializer):
2841
target = Target.TRITON
@@ -49,12 +62,19 @@ def load_built_artifact(self, built: BuiltArtifact):
4962
_runtime_specs,
5063
)
5164

52-
library = ctypes.CDLL(built.binary_path)
53-
function = getattr(library, f"{built.source.kernel_name}_kernel_default")
54-
function.restype = ctypes.c_int
65+
_, function, enter, leave = _load_aot_exports(
66+
built.binary_path,
67+
built.source.kernel_name,
68+
)
5569
specs = _runtime_specs(built.source)
5670

57-
return _aot_wrapper(function, _launch_abi_from_dict(built.abi), specs)
71+
return _aot_wrapper(
72+
function,
73+
enter,
74+
leave,
75+
_launch_abi_from_dict(built.abi),
76+
specs,
77+
)
5878

5979

6080
def _ensure_c_compiler() -> None:
@@ -182,7 +202,7 @@ def validate(args, kwargs):
182202

183203

184204
def _aot_materialize(compilation, *, output_dir):
185-
from ninetoothed.compiler.runtime import Handle, _built_manifest, _publish_library
205+
from ninetoothed.compiler.runtime import Handle, _publish_library
186206

187207
artifact = compilation.artifact
188208
cache_key = compilation_cache_key(compilation)
@@ -195,25 +215,74 @@ def _aot_materialize(compilation, *, output_dir):
195215
cache_library = artifact_directory(cache_key) / f"{artifact.kernel_name}.triton.so"
196216

197217
with cache_lock(cache_library):
198-
if not cache_library.is_file():
199-
_compile_aot_library(compilation, source, cache_library)
218+
_ensure_aot_library(compilation, source, cache_library)
200219

201220
write_manifest(
202221
cache_library.with_suffix(".manifest.json"),
203-
_built_manifest(compilation, cache_key, source, cache_library),
222+
_triton_aot_manifest(compilation, cache_key, source, cache_library),
204223
)
205224

206225
library_path = _publish_library(
207226
cache_library,
208227
output_dir,
209228
f"{artifact.kernel_name}.triton.so",
210229
)
230+
_, function, enter, leave = _load_aot_exports(
231+
library_path,
232+
artifact.kernel_name,
233+
)
234+
wrapped = _aot_wrapper(
235+
function,
236+
enter,
237+
leave,
238+
compilation.launch_abi,
239+
compilation.kernel.tensors,
240+
)
241+
handle = Handle(compilation, function, wrapped, source, library_path)
242+
write_manifest(
243+
handle._built_artifact.manifest_path,
244+
_triton_aot_manifest(compilation, cache_key, source, library_path),
245+
)
246+
247+
return handle
248+
249+
250+
def _ensure_aot_library(compilation, source: Path, library: Path) -> None:
251+
manifest = read_manifest(library.with_suffix(".manifest.json"))
252+
schema = None if manifest is None else manifest.get("triton_aot_launcher_schema")
253+
254+
if not library.is_file() or schema != _TRITON_AOT_LAUNCHER_SCHEMA:
255+
_compile_aot_library(compilation, source, library)
256+
257+
258+
def _triton_aot_manifest(compilation, cache_key, source, library):
259+
from ninetoothed.compiler.runtime import _built_manifest
260+
261+
return dict(_built_manifest(compilation, cache_key, source, library)) | {
262+
"triton_aot_launcher_schema": _TRITON_AOT_LAUNCHER_SCHEMA,
263+
}
264+
265+
266+
def _load_aot_exports(library_path, kernel_name):
211267
library = ctypes.CDLL(str(library_path))
212-
function = getattr(library, f"{artifact.kernel_name}_kernel_default")
268+
function = getattr(library, f"{kernel_name}_kernel_default")
269+
270+
try:
271+
enter = library.ninetoothed_triton_enter
272+
leave = library.ninetoothed_triton_leave
273+
except AttributeError as exc:
274+
raise RuntimeError(
275+
"Triton AOT artifact is missing the CUDA context guard exports; "
276+
"rebuild the artifact with the current NineToothed version."
277+
) from exc
278+
213279
function.restype = ctypes.c_int
214-
wrapped = _aot_wrapper(function, compilation.launch_abi, compilation.kernel.tensors)
280+
enter.argtypes = []
281+
enter.restype = ctypes.c_int
282+
leave.argtypes = []
283+
leave.restype = None
215284

216-
return Handle(compilation, function, wrapped, source, library_path)
285+
return library, function, enter, leave
217286

218287

219288
def _compile_aot_library(compilation, source: Path, library: Path) -> None:
@@ -262,6 +331,13 @@ def _compile_aot_library(compilation, source: Path, library: Path) -> None:
262331
if not headers or not sources:
263332
raise RuntimeError("Triton AOT compiler did not produce C artifacts.")
264333

334+
kernel_names = _triton_aot_kernel_names(sources)
335+
context_guard = temporary / "ninetoothed_triton_context_guard.cu"
336+
context_guard.write_text(
337+
_triton_context_guard_source(kernel_names),
338+
encoding="utf-8",
339+
)
340+
265341
subprocess.run(
266342
[
267343
sys.executable,
@@ -279,12 +355,20 @@ def _compile_aot_library(compilation, source: Path, library: Path) -> None:
279355
[
280356
find_nvcc(),
281357
"-shared",
358+
"-std=c++17",
282359
"-Xcompiler",
283360
"-fPIC",
361+
"-Xcompiler",
362+
"-pthread",
284363
"-O3",
285-
"-lcuda",
286364
*(str(path) for path in sources),
287365
str(linked.with_suffix(".c")),
366+
str(context_guard),
367+
"-lcuda",
368+
"-Xlinker",
369+
"-z",
370+
"-Xlinker",
371+
"defs",
288372
"-o",
289373
str(output),
290374
],
@@ -293,6 +377,139 @@ def _compile_aot_library(compilation, source: Path, library: Path) -> None:
293377
os.replace(output, library)
294378

295379

380+
def _triton_aot_kernel_names(sources: tuple[Path, ...]) -> tuple[str, ...]:
381+
"""Return low-level kernels with matching module, function, and loader symbols."""
382+
if not sources:
383+
raise ValueError("Unsupported Triton AOT source format: no C sources found.")
384+
385+
kernel_names = []
386+
387+
for source in sources:
388+
text = source.read_text(encoding="utf-8")
389+
modules = _TRITON_MODULE_PATTERN.findall(text)
390+
functions = _TRITON_FUNCTION_PATTERN.findall(text)
391+
loaders = _TRITON_LOADER_PATTERN.findall(text)
392+
module_names = set(modules)
393+
394+
if (
395+
not modules
396+
or len(modules) != len(module_names)
397+
or len(functions) != len(set(functions))
398+
or len(loaders) != len(set(loaders))
399+
or module_names != set(functions)
400+
or module_names != set(loaders)
401+
):
402+
raise ValueError(
403+
f"Unsupported Triton AOT source format in `{source}`: expected "
404+
"one matching `CUmodule`, `CUfunction`, and `load_*` symbol per "
405+
"low-level kernel."
406+
)
407+
408+
kernel_names.extend(modules)
409+
410+
if len(kernel_names) != len(set(kernel_names)):
411+
raise ValueError(
412+
"Unsupported Triton AOT source format: duplicate low-level kernel symbols."
413+
)
414+
return tuple(kernel_names)
415+
416+
417+
def _triton_context_guard_source(kernel_names: tuple[str, ...]) -> str:
418+
"""Generate the CUDA-context guard linked beside Triton's AOT launchers."""
419+
declarations = "\n".join(
420+
(
421+
f'extern "C" CUmodule {name}_mod;\n'
422+
f'extern "C" CUfunction {name}_func;\n'
423+
f'extern "C" void load_{name}(void);'
424+
)
425+
for name in kernel_names
426+
)
427+
resets_and_loads = "\n".join(
428+
(
429+
f" {name}_mod = nullptr;\n"
430+
f" {name}_func = nullptr;\n"
431+
f" load_{name}();\n"
432+
f" if ({name}_mod == nullptr || {name}_func == nullptr) {{\n"
433+
" launch_mutex.unlock();\n"
434+
" return CUDA_ERROR_INVALID_HANDLE;\n"
435+
" }"
436+
)
437+
for name in kernel_names
438+
)
439+
stores = "\n".join(
440+
(
441+
f" state.modules[{index}] = {name}_mod;\n"
442+
f" state.functions[{index}] = {name}_func;"
443+
)
444+
for index, name in enumerate(kernel_names)
445+
)
446+
restores = "\n".join(
447+
(
448+
f" {name}_mod = found->second.modules[{index}];\n"
449+
f" {name}_func = found->second.functions[{index}];"
450+
)
451+
for index, name in enumerate(kernel_names)
452+
)
453+
454+
return f"""#include <array>
455+
#include <cuda.h>
456+
#include <mutex>
457+
#include <unordered_map>
458+
459+
{declarations}
460+
461+
namespace {{
462+
struct State {{
463+
std::array<CUmodule, {len(kernel_names)}> modules{{}};
464+
std::array<CUfunction, {len(kernel_names)}> functions{{}};
465+
}};
466+
467+
std::mutex launch_mutex;
468+
std::unordered_map<CUcontext, State> context_states;
469+
}}
470+
471+
extern "C" CUresult ninetoothed_triton_enter(void) {{
472+
launch_mutex.lock();
473+
474+
CUcontext context = nullptr;
475+
CUresult result = cuCtxGetCurrent(&context);
476+
477+
if (result != CUDA_SUCCESS) {{
478+
launch_mutex.unlock();
479+
return result;
480+
}}
481+
482+
if (context == nullptr) {{
483+
launch_mutex.unlock();
484+
return CUDA_ERROR_INVALID_CONTEXT;
485+
}}
486+
487+
try {{
488+
auto found = context_states.find(context);
489+
490+
if (found == context_states.end()) {{
491+
{resets_and_loads}
492+
493+
State state{{}};
494+
{stores}
495+
context_states.emplace(context, state);
496+
}} else {{
497+
{restores}
498+
}}
499+
}} catch (...) {{
500+
launch_mutex.unlock();
501+
return CUDA_ERROR_OUT_OF_MEMORY;
502+
}}
503+
504+
return CUDA_SUCCESS;
505+
}}
506+
507+
extern "C" void ninetoothed_triton_leave(void) {{
508+
launch_mutex.unlock();
509+
}}
510+
"""
511+
512+
296513
def _compile_signature(compilation) -> str:
297514
specs = {spec.name: spec for spec in compilation.kernel.tensors}
298515
values = []
@@ -421,7 +638,7 @@ def _compile_schedule(compilation) -> tuple[int, int]:
421638
return int(warps), int(stages)
422639

423640

424-
def _aot_wrapper(function, abi, tensor_specs):
641+
def _aot_wrapper(function, enter, leave, abi, tensor_specs):
425642
from ninetoothed.compiler.runtime import (
426643
KernelLaunchError,
427644
_bound_values,
@@ -462,7 +679,16 @@ def launch(*args, **kwargs):
462679
cuda_scalar=_triton_aot_scalar,
463680
)
464681
stream = ctypes.c_void_p(torch.cuda.current_stream().cuda_stream)
465-
result = function(stream, *values)
682+
enter_result = enter()
683+
684+
if enter_result != 0:
685+
raise KernelLaunchError(enter_result)
686+
687+
try:
688+
result = function(stream, *values)
689+
finally:
690+
leave()
691+
466692
del keepalive
467693

468694
if result != 0:

0 commit comments

Comments
 (0)