kernel-lens 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,11 @@
1
+ # kernel_lens/__init__.py
2
+
3
+ from .compiler.core import compile
4
+ from .utils.deployment import extract_libs
5
+ from .compiler.core import load
6
+
7
+ __all__ = [
8
+ "compile",
9
+ "load",
10
+ "extract_libs",
11
+ ]
File without changes
@@ -0,0 +1,129 @@
1
+ import os
2
+ import subprocess
3
+ import urllib.request
4
+ import tarfile
5
+ import onnxruntime
6
+
7
+ def build_ort_plugin(ort_plugins_dir: str, cache_dir: str):
8
+ """
9
+ Natively compiles the generated C++ files into a Shared Library (.so)
10
+ replacing the need for an external bash script.
11
+ """
12
+ # 1. Get exact ORT version from the current Python environment
13
+ ort_version = onnxruntime.__version__.split('+')[0]
14
+ # print(f" [Builder] Detected ONNX Runtime v{ort_version}")
15
+
16
+ ort_release_dir = os.path.join(cache_dir, f"onnxruntime-linux-x64-gpu-{ort_version}")
17
+ ort_tgz = f"{ort_release_dir}.tgz"
18
+
19
+ # 2. Download exact matching C++ Developer Release if missing
20
+ if not os.path.exists(ort_release_dir):
21
+ url = f"https://github.com/microsoft/onnxruntime/releases/download/v{ort_version}/onnxruntime-linux-x64-gpu-{ort_version}.tgz"
22
+ # print(f" [Builder] Downloading ORT C++ headers from {url}...")
23
+ urllib.request.urlretrieve(url, ort_tgz)
24
+ with tarfile.open(ort_tgz, "r:gz") as tar:
25
+ tar.extractall(path=cache_dir)
26
+
27
+ ort_inc = os.path.join(ort_release_dir, "include")
28
+ ort_lib = os.path.join(ort_release_dir, "lib")
29
+
30
+ # # 3. Dynamically find CUDA paths via nvcc
31
+ # print(" [Builder] Querying system for CUDA configuration...")
32
+ try:
33
+ nvcc_path = subprocess.check_output(["which", "nvcc"]).decode().strip()
34
+ cuda_home = os.path.dirname(os.path.dirname(nvcc_path))
35
+ except Exception:
36
+ # Fallback to standard Linux path
37
+ cuda_home = "/usr/local/cuda"
38
+
39
+ cuda_inc = os.path.join(cuda_home, "include")
40
+ cuda_lib = os.path.join(cuda_home, "lib64")
41
+ # print(f" [Builder] Detected CUDA at {cuda_home}")
42
+
43
+ # 4. Compile the .cu files into object files
44
+ cu_files = [f for f in os.listdir(ort_plugins_dir) if f.endswith(".cu")]
45
+ obj_files = []
46
+
47
+ for cu_file in cu_files:
48
+ cu_path = os.path.join(ort_plugins_dir, cu_file)
49
+ obj_path = os.path.join(ort_plugins_dir, cu_file.replace(".cu", ".o"))
50
+ obj_files.append(obj_path)
51
+
52
+ cmd = [
53
+ "nvcc", "-c", cu_path, "-o", obj_path, "-O3", "-Xcompiler", "-fPIC",
54
+ f"-I{ort_inc}", f"-I{cuda_inc}"
55
+ ]
56
+ subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
57
+
58
+ # 5. Compile the registrar (register_ops.cpp)
59
+ reg_cpp = os.path.join(ort_plugins_dir, "register_ops.cpp")
60
+ reg_obj = os.path.join(ort_plugins_dir, "register_ops.o")
61
+ obj_files.append(reg_obj)
62
+
63
+ cmd = [
64
+ "g++", "-c", reg_cpp, "-o", reg_obj, "-O3", "-fPIC",
65
+ f"-I{ort_inc}", f"-I{cuda_inc}"
66
+ ]
67
+ subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
68
+
69
+ # 6. Link everything into the final .so library (with RPATH baked in)
70
+ so_path = os.path.join(ort_plugins_dir, "libtriton_ort_plugins.so")
71
+ abs_ort_lib = os.path.abspath(ort_lib)
72
+
73
+ cmd = [
74
+ "g++", "-shared", "-o", so_path
75
+ ] + obj_files + [
76
+ f"-L{ort_lib}", "-lonnxruntime",
77
+ f"-L{cuda_lib}", "-lcuda", "-lcudart",
78
+ f"-Wl,-rpath,{abs_ort_lib}"
79
+ ]
80
+ subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
81
+
82
+ # print(f" [Builder] Compilation successful! Plugin saved to {so_path}")
83
+
84
+ def build_trt_plugin(trt_plugins_dir: str, cache_dir: str):
85
+ """
86
+ Natively compiles the generated C++ files into a TensorRT Shared Library (.so).
87
+ """
88
+ # print(" [Builder] Querying system for CUDA configuration...")
89
+ try:
90
+ nvcc_path = subprocess.check_output(["which", "nvcc"]).decode().strip()
91
+ cuda_home = os.path.dirname(os.path.dirname(nvcc_path))
92
+ except Exception:
93
+ cuda_home = "/usr/local/cuda"
94
+
95
+ cuda_inc = os.path.join(cuda_home, "include")
96
+ cuda_lib = os.path.join(cuda_home, "lib64")
97
+
98
+ # In TRT 8.6+, the library is often split into nvinfer and nvinfer_plugin
99
+ # We will assume standard system paths for TRT (/usr/lib/x86_64-linux-gnu or LD_LIBRARY_PATH)
100
+
101
+ cu_files = [f for f in os.listdir(trt_plugins_dir) if f.endswith(".cu")]
102
+ obj_files = []
103
+
104
+ for cu_file in cu_files:
105
+ cu_path = os.path.join(trt_plugins_dir, cu_file)
106
+ obj_path = os.path.join(trt_plugins_dir, cu_file.replace(".cu", ".o"))
107
+ obj_files.append(obj_path)
108
+
109
+ cmd = [
110
+ "nvcc", "-c", cu_path, "-o", obj_path, "-O3", "-Xcompiler", "-fPIC",
111
+ f"-I{cuda_inc}", "-Wno-deprecated-gpu-targets"
112
+ ]
113
+ subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
114
+
115
+ so_path = os.path.join(trt_plugins_dir, "libtriton_trt_plugins.so")
116
+
117
+ cmd = [
118
+ "g++", "-shared", "-o", so_path
119
+ ] + obj_files + [
120
+ f"-L{cuda_lib}", "-lcuda", "-lcudart", "-lnvinfer"
121
+ ]
122
+
123
+ try:
124
+ subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
125
+ except subprocess.CalledProcessError as e:
126
+ print("\n[ERROR] TensorRT compilation failed. Ensure TensorRT is installed and in your LD_LIBRARY_PATH.")
127
+ raise e
128
+
129
+ # print(f" [Builder] TRT Compilation successful! Plugin saved to {so_path}")
@@ -0,0 +1,266 @@
1
+ import os
2
+ import textwrap
3
+ import re
4
+
5
+ class ORTGenerator:
6
+ def __init__(self, manifests, ops_package="triton_custom"):
7
+ self.manifests = manifests
8
+ self.ops_package = ops_package
9
+
10
+ def _generate_kernel_h(self, manifest) -> str:
11
+ op_name = f"{manifest.kernel_name}Op"
12
+ kernel_name = f"{manifest.kernel_name}Kernel"
13
+
14
+ inputs_to_node = manifest.arguments
15
+ outputs_from_node = [a for a in manifest.arguments if a.kind == 'output']
16
+
17
+ input_types_cpp = []
18
+ mem_types_cpp = []
19
+
20
+ for a in inputs_to_node:
21
+ if a.kind == 'scalar':
22
+ # --- NEW: Keep Scalars on the CPU so we can safely read them! ---
23
+ mem_types_cpp.append("OrtMemTypeCPUInput")
24
+ if 'float' in a.dtype.lower():
25
+ input_types_cpp.append("ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE")
26
+ else:
27
+ input_types_cpp.append("ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64")
28
+ else:
29
+ # --- Keep Tensors on the GPU ---
30
+ mem_types_cpp.append("OrtMemTypeDefault")
31
+ input_types_cpp.append("ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT")
32
+
33
+ input_types_str = ",\n ".join(input_types_cpp)
34
+ mem_types_str = ",\n ".join(mem_types_cpp)
35
+
36
+ tpl = f'''#pragma once
37
+ // Unlock the Custom Op Initialization API
38
+ #define ORT_API_MANUAL_INIT
39
+ #include <onnxruntime_cxx_api.h>
40
+ #include <cuda.h>
41
+
42
+ namespace custom {{
43
+
44
+ struct {kernel_name} {{
45
+ void Compute(OrtKernelContext* context);
46
+ }};
47
+
48
+ struct {op_name} : Ort::CustomOpBase<{op_name}, {kernel_name}> {{
49
+ {op_name}() = default;
50
+
51
+ void* CreateKernel(const OrtApi& api, const OrtKernelInfo* info) const {{
52
+ return new {kernel_name}();
53
+ }}
54
+
55
+ const char* GetName() const {{ return "{manifest.kernel_name}"; }}
56
+ const char* GetExecutionProviderType() const {{ return "CUDAExecutionProvider"; }}
57
+
58
+ size_t GetInputTypeCount() const {{ return {len(inputs_to_node)}; }}
59
+ ONNXTensorElementDataType GetInputType(size_t index) const {{
60
+ static const ONNXTensorElementDataType types[] = {{
61
+ {input_types_str}
62
+ }};
63
+ return types[index];
64
+ }}
65
+
66
+ size_t GetOutputTypeCount() const {{ return {max(1, len(outputs_from_node))}; }}
67
+ ONNXTensorElementDataType GetOutputType(size_t index) const {{
68
+ return ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT;
69
+ }}
70
+
71
+ // --- THE MAGIC FIX: Dynamic Memory Placement ---
72
+ OrtMemType GetInputMemoryType(size_t index) const {{
73
+ static const OrtMemType types[] = {{
74
+ {mem_types_str}
75
+ }};
76
+ return types[index];
77
+ }}
78
+
79
+ OrtCustomOpInputOutputCharacteristic GetInputCharacteristic(size_t index) const {{
80
+ return OrtCustomOpInputOutputCharacteristic::INPUT_OUTPUT_REQUIRED;
81
+ }}
82
+ }};
83
+
84
+ }} // namespace custom
85
+ '''
86
+ return textwrap.dedent(tpl).strip()
87
+
88
+ def _generate_kernel_cu(self, manifest) -> str:
89
+ kernel_name = f"{manifest.kernel_name}Kernel"
90
+
91
+ arg_setup_lines = []
92
+ arg_setup_lines.append("Ort::KernelContext ctx(context);")
93
+ arg_setup_lines.append("auto ref_in = ctx.GetInput(0);")
94
+ arg_setup_lines.append("auto info = ref_in.GetTensorTypeAndShapeInfo();")
95
+ arg_setup_lines.append("std::vector<int64_t> dim_values = info.GetShape();")
96
+
97
+ import re
98
+
99
+ # --- ROBUST GRID EVALUATION ---
100
+ grid_strs = []
101
+ if hasattr(manifest, '_sym_grid_asts') and manifest._sym_grid_asts:
102
+ for g in manifest._sym_grid_asts:
103
+ # Always extract pure SymPy string FIRST
104
+ expr = str(g.node.expr) if hasattr(g, 'node') else str(g)
105
+ expr = re.sub(r'([a-zA-Z0-9_]+)\*\*([a-zA-Z0-9_]+)', r'std::pow(\1, \2)', expr)
106
+ expr = expr.replace("//", "/")
107
+ expr = re.sub(r'floor\((.*?)\)', r'(\1)', expr)
108
+
109
+ # Replace SymPy symbols with ORT C++
110
+ # Use regex to avoid replacing s0 inside s01
111
+ expr = re.sub(r'\bs0\b', "(int64_t)dim_values[0]", expr)
112
+ expr = re.sub(r'\bs1\b', "(int64_t)(dim_values.size() > 1 ? dim_values[1] : 1)", expr)
113
+ expr = re.sub(r'\bs2\b', "(int64_t)(dim_values.size() > 2 ? dim_values[2] : 1)", expr)
114
+ grid_strs.append(expr)
115
+
116
+ while len(grid_strs) < 3:
117
+ grid_strs.append("1")
118
+
119
+ grid_eval_lines = [
120
+ f"unsigned int grid_x = std::max(1u, (unsigned int)({grid_strs[0]}));",
121
+ f"unsigned int grid_y = std::max(1u, (unsigned int)({grid_strs[1]}));",
122
+ f"unsigned int grid_z = std::max(1u, (unsigned int)({grid_strs[2]}));"
123
+ ]
124
+
125
+ arg_setup_lines.append("// Stable memory addresses for CUDA kernel launch")
126
+ for slot_idx, arg in enumerate(manifest.arguments):
127
+ if arg.kind == 'input':
128
+ arg_setup_lines.append(f"const void* arg_{slot_idx} = nullptr;")
129
+ elif arg.kind == 'scalar':
130
+ if 'float' in arg.dtype.lower():
131
+ arg_setup_lines.append(f"float arg_{slot_idx} = 0.0f;")
132
+ else:
133
+ arg_setup_lines.append(f"int32_t arg_{slot_idx} = 0;")
134
+ elif arg.kind == 'output':
135
+ arg_setup_lines.append(f"void* arg_{slot_idx} = nullptr;")
136
+
137
+ padding_idx = len(manifest.arguments)
138
+ arg_setup_lines.append(f"int64_t pad_{padding_idx} = 0;")
139
+
140
+ arg_setup_lines.append("std::vector<void*> kp;")
141
+
142
+ input_idx = 0
143
+ output_idx = 0
144
+
145
+ for slot_idx, arg in enumerate(manifest.arguments):
146
+ if arg.kind == 'input':
147
+ arg_setup_lines.append(f"auto input_{slot_idx} = ctx.GetInput({input_idx});")
148
+ arg_setup_lines.append(f"arg_{slot_idx} = (const void*)input_{slot_idx}.GetTensorData<float>();")
149
+ arg_setup_lines.append(f"kp.push_back(&arg_{slot_idx});")
150
+ input_idx += 1
151
+ elif arg.kind == 'scalar':
152
+ arg_setup_lines.append(f"auto input_{slot_idx} = ctx.GetInput({input_idx});")
153
+ # NOW THIS IS 100% SAFE because ONNX stored it in CPU memory!
154
+ if 'float' in arg.dtype.lower():
155
+ arg_setup_lines.append(f"arg_{slot_idx} = (float)(*input_{slot_idx}.GetTensorData<double>());")
156
+ else:
157
+ arg_setup_lines.append(f"arg_{slot_idx} = (int32_t)(*input_{slot_idx}.GetTensorData<int64_t>());")
158
+
159
+ arg_name = getattr(arg, 'name', '')
160
+ if 'stride' in arg_name.lower():
161
+ arg_setup_lines.append(f"if (arg_{slot_idx} != 1) kp.push_back(&arg_{slot_idx});")
162
+ else:
163
+ arg_setup_lines.append(f"kp.push_back(&arg_{slot_idx});")
164
+ input_idx += 1
165
+ elif arg.kind == 'output':
166
+ arg_setup_lines.append(f"// Skip PyTorch's dummy input")
167
+ arg_setup_lines.append(f"auto dummy_in_{slot_idx} = ctx.GetInput({input_idx});")
168
+ input_idx += 1
169
+
170
+ arg_setup_lines.append(f"auto output_{slot_idx} = ctx.GetOutput({output_idx}, dim_values.data(), dim_values.size());")
171
+ arg_setup_lines.append(f"arg_{slot_idx} = (void*)output_{slot_idx}.GetTensorMutableData<float>();")
172
+ arg_setup_lines.append(f"kp.push_back(&arg_{slot_idx});")
173
+ output_idx += 1
174
+
175
+ arg_setup_lines.append(f"kp.push_back(&pad_{padding_idx});")
176
+
177
+ dynamic_args_cpp = "\n ".join(arg_setup_lines)
178
+ grid_cpp = "\n ".join(grid_eval_lines)
179
+
180
+ tpl = f'''#include "{manifest.kernel_name}Op.h"
181
+ #include <stdexcept>
182
+ #include <iostream>
183
+ #include <vector>
184
+ #include <cmath>
185
+ #include <algorithm>
186
+
187
+ namespace custom {{
188
+
189
+ static const char* PTX_CODE = R"ptx(
190
+ {manifest.ptx}
191
+ )ptx";
192
+
193
+ void {kernel_name}::Compute(OrtKernelContext* context) {{
194
+ {dynamic_args_cpp}
195
+
196
+ static CUmodule mModule = nullptr;
197
+ static CUfunction mKernel = nullptr;
198
+
199
+ if (mModule == nullptr) {{
200
+ CUresult res = cuModuleLoadDataEx(&mModule, PTX_CODE, 0, nullptr, nullptr);
201
+ if (res != CUDA_SUCCESS) throw std::runtime_error("Failed to load PTX module");
202
+ res = cuModuleGetFunction(&mKernel, mModule, "{manifest.kernel_name}");
203
+ if (res != CUDA_SUCCESS) throw std::runtime_error("Failed to extract function");
204
+ }}
205
+
206
+ cudaStream_t stream = reinterpret_cast<cudaStream_t>(ctx.GetGPUComputeStream());
207
+
208
+ {grid_cpp}
209
+ unsigned int block_x = 128;
210
+
211
+ cuLaunchKernel(mKernel, grid_x, grid_y, grid_z, block_x, 1, 1, {manifest.shared_memory_bytes}, stream, kp.data(), nullptr);
212
+ }}
213
+
214
+ }} // namespace custom
215
+ '''
216
+ return textwrap.dedent(tpl).strip()
217
+
218
+ def generate(self, output_dir: str):
219
+ if not os.path.exists(output_dir):
220
+ os.makedirs(output_dir)
221
+
222
+ for m in self.manifests:
223
+ base_path = os.path.join(output_dir, f"{m.kernel_name}Op")
224
+ with open(f"{base_path}.h", "w") as f:
225
+ f.write(self._generate_kernel_h(m))
226
+ with open(f"{base_path}.cu", "w") as f:
227
+ f.write(self._generate_kernel_cu(m))
228
+
229
+ registrar_code = '''#define ORT_API_MANUAL_INIT
230
+ #include <onnxruntime_cxx_api.h>
231
+
232
+ '''
233
+ for m in self.manifests:
234
+ registrar_code += f'#include "{m.kernel_name}Op.h"\n'
235
+
236
+ registrar_code += '''
237
+ #ifndef ORT_EXPORT
238
+ #ifdef _WIN32
239
+ #define ORT_EXPORT __declspec(dllexport)
240
+ #else
241
+ #define ORT_EXPORT __attribute__((visibility("default")))
242
+ #endif
243
+ #endif
244
+
245
+ extern "C" {
246
+ ORT_EXPORT OrtStatus* ORT_API_CALL RegisterCustomOps(OrtSessionOptions* options, const OrtApiBase* api_base) {
247
+ Ort::InitApi(api_base->GetApi(ORT_API_VERSION));
248
+ static Ort::CustomOpDomain custom_domain("triton_custom");
249
+ '''
250
+ for m in self.manifests:
251
+ registrar_code += f' static custom::{m.kernel_name}Op c_{m.kernel_name};\n'
252
+ registrar_code += f' custom_domain.Add(&c_{m.kernel_name});\n'
253
+
254
+ registrar_code += '''
255
+ Ort::UnownedSessionOptions sess_options(options);
256
+ sess_options.Add(custom_domain);
257
+ return nullptr;
258
+ }
259
+ }
260
+ '''
261
+ with open(os.path.join(output_dir, "register_ops.cpp"), "w") as f:
262
+ f.write(registrar_code)
263
+
264
+ def generate_ort_bindings(manifests, output_path: str):
265
+ gen = ORTGenerator(manifests)
266
+ gen.generate(output_path)