tensoracle 0.5.0__tar.gz → 1.0.0__tar.gz
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.
- {tensoracle-0.5.0 → tensoracle-1.0.0}/PKG-INFO +20 -5
- {tensoracle-0.5.0 → tensoracle-1.0.0}/README.md +18 -4
- {tensoracle-0.5.0 → tensoracle-1.0.0}/cpp/cmake/TensoracleCubinPlugin.cmake +5 -1
- tensoracle-1.0.0/docs/cpp-python-repositories.md +199 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/docs/index.md +6 -4
- {tensoracle-0.5.0 → tensoracle-1.0.0}/docs/optimization.md +2 -2
- tensoracle-1.0.0/docs/precision.md +154 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/docs/tensorrt-sdk.md +88 -1
- {tensoracle-0.5.0 → tensoracle-1.0.0}/mkdocs.yml +2 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/pyproject.toml +1 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/src/tensoracle/_cache.py +32 -3
- {tensoracle-0.5.0 → tensoracle-1.0.0}/src/tensoracle/_version.py +2 -2
- {tensoracle-0.5.0 → tensoracle-1.0.0}/src/tensoracle/bundle.py +18 -6
- {tensoracle-0.5.0 → tensoracle-1.0.0}/tests/cubin_io/check.cpp +4 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/tests/example_runtime.py +3 -3
- tensoracle-1.0.0/tests/native/CMakeLists.txt +44 -0
- tensoracle-1.0.0/tests/native/export.h +7 -0
- tensoracle-1.0.0/tests/native_build.py +68 -0
- tensoracle-1.0.0/tests/precision_runtime.py +164 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/tests/test_bundle.py +67 -22
- {tensoracle-0.5.0 → tensoracle-1.0.0}/tests/test_cache.py +14 -18
- tensoracle-1.0.0/tests/test_cmake_plugin.py +56 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/tests/test_cpp_core.py +7 -19
- {tensoracle-0.5.0 → tensoracle-1.0.0}/tests/test_distribution.py +40 -12
- {tensoracle-0.5.0 → tensoracle-1.0.0}/tests/test_engine_io.py +1 -1
- {tensoracle-0.5.0 → tensoracle-1.0.0}/tests/test_example_plugin.py +33 -8
- tensoracle-1.0.0/tests/test_gpu_cache.py +204 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/tests/test_plugin_io.py +11 -49
- {tensoracle-0.5.0 → tensoracle-1.0.0}/tests/test_plugins.py +26 -18
- tensoracle-1.0.0/tests/test_precision.py +111 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/tests/test_weight_hash.py +13 -13
- {tensoracle-0.5.0 → tensoracle-1.0.0}/.gitignore +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/LICENSE +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/build_backend.py +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/cpp/CMakeLists.txt +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/cpp/include/tensoracle/cubin_plugin.hpp +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/cpp/src/cubin_plugin.cpp +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/cpp/tests/core.cpp +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/docs/api/bundle.md +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/docs/api/index.md +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/docs/api/producer.md +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/examples/cubin_plugin/CMakeLists.txt +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/examples/cubin_plugin/README.md +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/examples/cubin_plugin/bundle/bundle.cpp +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/examples/cubin_plugin/bundle/bundle.json +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/examples/cubin_plugin/bundle/cubins/sm120/bias.cubin +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/examples/cubin_plugin/bundle/cubins/sm120/dot.specialized.cubin +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/examples/cubin_plugin/bundle/cubins/sm120/dot.tf32-control.cubin +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/examples/cubin_plugin/bundle/cubins/sm120/dot.unspecialized.cubin +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/examples/cubin_plugin/bundle/cubins/sm120/scale.cubin +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/examples/cubin_plugin/bundle/cubins/sm86/bias.cubin +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/examples/cubin_plugin/bundle/cubins/sm86/dot.specialized.cubin +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/examples/cubin_plugin/bundle/cubins/sm86/dot.tf32-control.cubin +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/examples/cubin_plugin/bundle/cubins/sm86/dot.unspecialized.cubin +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/examples/cubin_plugin/bundle/cubins/sm86/scale.cubin +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/examples/cubin_plugin/bundle/cubins/sm89/bias.cubin +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/examples/cubin_plugin/bundle/cubins/sm89/dot.specialized.cubin +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/examples/cubin_plugin/bundle/cubins/sm89/dot.tf32-control.cubin +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/examples/cubin_plugin/bundle/cubins/sm89/dot.unspecialized.cubin +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/examples/cubin_plugin/bundle/cubins/sm89/scale.cubin +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/examples/cubin_plugin/example_cubin_plugin/__init__.py +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/examples/cubin_plugin/example_cubin_plugin/weights.safetensors +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/examples/cubin_plugin/kernels.py +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/examples/cubin_plugin/plugin.cpp +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/examples/cubin_plugin/pyproject.toml +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/src/tensoracle/__init__.py +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/src/tensoracle/_build.py +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/src/tensoracle/_comparison.py +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/src/tensoracle/_execution.py +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/src/tensoracle/_memory.py +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/src/tensoracle/_plugins.py +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/src/tensoracle/convert.py +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/src/tensoracle/producer.py +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/tests/build_memory_runtime.py +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/tests/cubin_io/kernels.cu +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/tests/cubin_io/plugin.cpp +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/tests/plugin_io_runtime.py +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/tests/test_build.py +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/tests/test_build_memory.py +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/tests/test_comparison.py +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/tests/test_convert.py +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/tests/test_execution.py +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/tests/test_producer.py +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/tests/test_shape_tensor_outputs.py +0 -0
- {tensoracle-0.5.0 → tensoracle-1.0.0}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: tensoracle
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.0
|
|
4
4
|
Summary: Build, save, reuse, and execute TensorRT engines from safetensors weights
|
|
5
5
|
Project-URL: documentation, https://sync-dev-org.github.io/tensoracle/
|
|
6
6
|
Project-URL: repository, https://github.com/sync-dev-org/tensoracle
|
|
@@ -12,6 +12,7 @@ Classifier: Development Status :: 4 - Beta
|
|
|
12
12
|
Classifier: Environment :: GPU :: NVIDIA CUDA :: 13
|
|
13
13
|
Classifier: Intended Audience :: Developers
|
|
14
14
|
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
15
16
|
Classifier: Operating System :: POSIX :: Linux
|
|
16
17
|
Classifier: Programming Language :: Python :: 3.12
|
|
17
18
|
Classifier: Programming Language :: Python :: 3.13
|
|
@@ -57,9 +58,11 @@ Choose the path that matches your work:
|
|
|
57
58
|
| TensorRT 11 SDK | Not needed | Not needed | Native plugin build |
|
|
58
59
|
| CMake 3.20 or later and C++17 compiler | Not needed | Not needed | Native plugin build |
|
|
59
60
|
|
|
60
|
-
The supported targets are Linux x86_64 (including WSL2), TensorRT 11, and GPU generations sm86, sm89, and sm120. The producer and native build tools are separate from the `tensoracle` runtime dependencies. Engine building, engine reuse, output comparison, and cubin production do not require torch. Converting `.pth` checkpoints requires the `convert` extra described below.
|
|
61
|
+
The supported targets are Linux x86_64 (including WSL2) and Windows x86_64, TensorRT 11, and GPU generations sm86, sm89, and sm120. The same `py3-none-any` wheel installs on both operating systems. Cubin production with `tensoracle[producer]` runs on Linux; cubins do not depend on the OS, so a plugin DLL built on Windows can embed cubins produced on Linux. The producer and native build tools are separate from the `tensoracle` runtime dependencies. Engine building, engine reuse, output comparison, and cubin production do not require torch. Converting `.pth` checkpoints requires the `convert` extra described below.
|
|
61
62
|
|
|
62
|
-
Follow the [four-stage optimization workflow](https://github.com/sync-dev-org/tensoracle/blob/main/docs/optimization.md) to establish output quality, evaluate
|
|
63
|
+
Follow the [four-stage optimization workflow](https://github.com/sync-dev-org/tensoracle/blob/main/docs/optimization.md) to establish output quality, evaluate lower precision and plugins, and measure speed.
|
|
64
|
+
|
|
65
|
+
Follow [Build a C++ and Python repository](https://sync-dev-org.github.io/tensoracle/cpp-python-repositories/) for source layout, development environments, bindings, and Linux wheel packaging.
|
|
63
66
|
|
|
64
67
|
## Convert a PyTorch checkpoint
|
|
65
68
|
|
|
@@ -114,6 +117,8 @@ Use `InputShape.fixed((1, 64, 2))` for one input size; the graph may declare tha
|
|
|
114
117
|
|
|
115
118
|
The engine's precision is determined by the tensor types that the graph function places in the network. For an fp16 graph, use `trt.float16` tensors and `np.float16` weights where needed; the graph can also contain fp32 tensors. `EngineBuild` declares input shapes and builder settings. TF32 is always disabled; `BuilderSettings(tf32=True)` raises `ValueError`. The function returns serialized plan bytes. Invalid declarations raise `ValueError`, and a failed TensorRT build raises `RuntimeError`.
|
|
116
119
|
|
|
120
|
+
The same API builds bf16, int8, and fp8 graphs. Cast tensors to `trt.bfloat16` for bf16; for int8/fp8, place `add_quantize` / `add_dequantize` layers and load their scales from safetensors. The application determines scales with an external quantization library and chooses output quality limits with `compare_outputs`. fp8 targets sm89 and sm120; bf16 and int8 also target sm86. Follow [Build bf16, int8, and fp8 engines](https://github.com/sync-dev-org/tensoracle/blob/main/docs/precision.md) for a complete example, NumPy dtype constraints, and comparison against an fp32 engine. Use different engine names or graph versions for different precisions.
|
|
121
|
+
|
|
117
122
|
## Save and reopen an engine
|
|
118
123
|
|
|
119
124
|
`open_engine` builds on the first call and reuses the saved engine on later calls, including calls from another process. It invokes `graph` only when building. Declare the expected input and output names, TensorRT types, and build-time shapes:
|
|
@@ -137,7 +142,7 @@ handle = open_engine(
|
|
|
137
142
|
engine = handle.engine # Keep handle.runtime alive while using the engine.
|
|
138
143
|
```
|
|
139
144
|
|
|
140
|
-
Saved files live in the `tensoracle` directory under `XDG_CACHE_HOME`, or under
|
|
145
|
+
Saved files live in the `tensoracle` directory under `XDG_CACHE_HOME`, or under the home directory's `.cache` when that variable is unset. The same rule applies on every OS; on Windows the home directory is `%USERPROFILE%`, so the default is `%USERPROFILE%\.cache\tensoracle`. The cache identity includes the contents of all declared safetensors weight files and optional `plugins` files, the engine name, graph version, input shapes, builder settings, GPU product name, OS, CPU architecture, and TensorRT version. Raise `graph_version` when graph code changes; without a version change, the previous engine is reused. Use a different `name` or `graph_version` for engines with different tensor types, such as fp32 and fp16. Include in `graph_version` any other caller value that changes the engine contents but is not part of the cache identity, such as a plugin manifest or a build recipe. Pass `force_rebuild=True` to replace a cached engine even when it is intact. tensoracle closes a saved engine file after reading it, so another process using an opened engine does not block replacement. On Windows, a process that keeps the saved file itself open prevents replacement or removal with `PermissionError`; after that process closes the file, a file left empty by the failed rebuild is rejected as `CorruptEngineError` and can be rebuilt with `force_rebuild=True`.
|
|
141
146
|
|
|
142
147
|
`plugins` names native TensorRT plugin libraries. Both `open_engine` and `build_serialized_engine` accept it. Each function checks that every library exports `getCreators`, loads it through TensorRT's registry, and retains it for the process before building the graph or deserializing an engine. Repeating the same library does not register it twice. `open_engine` also hashes the loaded library bytes into the engine identity; a changed library selects a different saved engine in a new process. A missing, unreadable, or incompatible library, or a library changed after loading in the same process, raises `PluginLibraryError` before a cached plan is opened. Keep the library and its cubin bundle together in the caller's wheel.
|
|
143
148
|
|
|
@@ -317,7 +322,17 @@ The example was also built with an older compiler and CUDA toolkit:
|
|
|
317
322
|
| --- | --- | --- | --- |
|
|
318
323
|
| gcc 13.3.0 / CMake 4.2.3 / CUDA toolkit 12.9.86 / TensorRT SDK 11.2.1.2 | `build` and a separate-process `reuse` with TensorRT runtime 11.2.1.2 on sm86 succeeded with zero outputs outside tolerance and both variants launched | 3.13.7 | WSL2 / glibc 2.39 |
|
|
319
324
|
|
|
320
|
-
Each GPU subprocess had a 50-second limit. Speed was not measured.
|
|
325
|
+
Each GPU subprocess had a 50-second limit. Speed was not measured.
|
|
326
|
+
|
|
327
|
+
On 2026-10-18, the test suite and a native plugin build were run on Windows:
|
|
328
|
+
|
|
329
|
+
| GPU | Result | Python | OS / NVIDIA driver / build tools / TensorRT |
|
|
330
|
+
| --- | --- | --- | --- |
|
|
331
|
+
| NVIDIA RTX 6000 Ada Generation (sm89) | `pytest`: 246 passed, 20 skipped (Linux-only memory checks and optional `convert` and `producer` dependencies) | 3.12.11 | Windows 11 Pro x64 build 26200 / 596.72 / Visual Studio 2022 (MSVC 19.44), CMake 3.31.6, Ninja, CUDA toolkit 12.8.93 / runtime and SDK 11.4.0.106 |
|
|
332
|
+
|
|
333
|
+
On Windows, the standalone `cpp/` build and CTest passed, the CMake helper built the example plugin DLL against `nvinfer_11.lib`, and the plugin engine was built and reused in a separate process. The `py3-none-any` wheel built on Linux was installed unchanged on Windows. With `PATH` reduced to the Windows system directories and no CUDA or TensorRT SDK paths, the driver's `nvcuda.dll` and the pip-installed TensorRT runtime were sufficient to build and reuse engines.
|
|
334
|
+
|
|
335
|
+
Native Linux, other kernels, Windows with sm86 or sm120, and TensorRT 11 versions not listed here have not been tested.
|
|
321
336
|
|
|
322
337
|
## Compare output arrays
|
|
323
338
|
|
|
@@ -27,9 +27,11 @@ Choose the path that matches your work:
|
|
|
27
27
|
| TensorRT 11 SDK | Not needed | Not needed | Native plugin build |
|
|
28
28
|
| CMake 3.20 or later and C++17 compiler | Not needed | Not needed | Native plugin build |
|
|
29
29
|
|
|
30
|
-
The supported targets are Linux x86_64 (including WSL2), TensorRT 11, and GPU generations sm86, sm89, and sm120. The producer and native build tools are separate from the `tensoracle` runtime dependencies. Engine building, engine reuse, output comparison, and cubin production do not require torch. Converting `.pth` checkpoints requires the `convert` extra described below.
|
|
30
|
+
The supported targets are Linux x86_64 (including WSL2) and Windows x86_64, TensorRT 11, and GPU generations sm86, sm89, and sm120. The same `py3-none-any` wheel installs on both operating systems. Cubin production with `tensoracle[producer]` runs on Linux; cubins do not depend on the OS, so a plugin DLL built on Windows can embed cubins produced on Linux. The producer and native build tools are separate from the `tensoracle` runtime dependencies. Engine building, engine reuse, output comparison, and cubin production do not require torch. Converting `.pth` checkpoints requires the `convert` extra described below.
|
|
31
31
|
|
|
32
|
-
Follow the [four-stage optimization workflow](https://github.com/sync-dev-org/tensoracle/blob/main/docs/optimization.md) to establish output quality, evaluate
|
|
32
|
+
Follow the [four-stage optimization workflow](https://github.com/sync-dev-org/tensoracle/blob/main/docs/optimization.md) to establish output quality, evaluate lower precision and plugins, and measure speed.
|
|
33
|
+
|
|
34
|
+
Follow [Build a C++ and Python repository](https://sync-dev-org.github.io/tensoracle/cpp-python-repositories/) for source layout, development environments, bindings, and Linux wheel packaging.
|
|
33
35
|
|
|
34
36
|
## Convert a PyTorch checkpoint
|
|
35
37
|
|
|
@@ -84,6 +86,8 @@ Use `InputShape.fixed((1, 64, 2))` for one input size; the graph may declare tha
|
|
|
84
86
|
|
|
85
87
|
The engine's precision is determined by the tensor types that the graph function places in the network. For an fp16 graph, use `trt.float16` tensors and `np.float16` weights where needed; the graph can also contain fp32 tensors. `EngineBuild` declares input shapes and builder settings. TF32 is always disabled; `BuilderSettings(tf32=True)` raises `ValueError`. The function returns serialized plan bytes. Invalid declarations raise `ValueError`, and a failed TensorRT build raises `RuntimeError`.
|
|
86
88
|
|
|
89
|
+
The same API builds bf16, int8, and fp8 graphs. Cast tensors to `trt.bfloat16` for bf16; for int8/fp8, place `add_quantize` / `add_dequantize` layers and load their scales from safetensors. The application determines scales with an external quantization library and chooses output quality limits with `compare_outputs`. fp8 targets sm89 and sm120; bf16 and int8 also target sm86. Follow [Build bf16, int8, and fp8 engines](https://github.com/sync-dev-org/tensoracle/blob/main/docs/precision.md) for a complete example, NumPy dtype constraints, and comparison against an fp32 engine. Use different engine names or graph versions for different precisions.
|
|
90
|
+
|
|
87
91
|
## Save and reopen an engine
|
|
88
92
|
|
|
89
93
|
`open_engine` builds on the first call and reuses the saved engine on later calls, including calls from another process. It invokes `graph` only when building. Declare the expected input and output names, TensorRT types, and build-time shapes:
|
|
@@ -107,7 +111,7 @@ handle = open_engine(
|
|
|
107
111
|
engine = handle.engine # Keep handle.runtime alive while using the engine.
|
|
108
112
|
```
|
|
109
113
|
|
|
110
|
-
Saved files live in the `tensoracle` directory under `XDG_CACHE_HOME`, or under
|
|
114
|
+
Saved files live in the `tensoracle` directory under `XDG_CACHE_HOME`, or under the home directory's `.cache` when that variable is unset. The same rule applies on every OS; on Windows the home directory is `%USERPROFILE%`, so the default is `%USERPROFILE%\.cache\tensoracle`. The cache identity includes the contents of all declared safetensors weight files and optional `plugins` files, the engine name, graph version, input shapes, builder settings, GPU product name, OS, CPU architecture, and TensorRT version. Raise `graph_version` when graph code changes; without a version change, the previous engine is reused. Use a different `name` or `graph_version` for engines with different tensor types, such as fp32 and fp16. Include in `graph_version` any other caller value that changes the engine contents but is not part of the cache identity, such as a plugin manifest or a build recipe. Pass `force_rebuild=True` to replace a cached engine even when it is intact. tensoracle closes a saved engine file after reading it, so another process using an opened engine does not block replacement. On Windows, a process that keeps the saved file itself open prevents replacement or removal with `PermissionError`; after that process closes the file, a file left empty by the failed rebuild is rejected as `CorruptEngineError` and can be rebuilt with `force_rebuild=True`.
|
|
111
115
|
|
|
112
116
|
`plugins` names native TensorRT plugin libraries. Both `open_engine` and `build_serialized_engine` accept it. Each function checks that every library exports `getCreators`, loads it through TensorRT's registry, and retains it for the process before building the graph or deserializing an engine. Repeating the same library does not register it twice. `open_engine` also hashes the loaded library bytes into the engine identity; a changed library selects a different saved engine in a new process. A missing, unreadable, or incompatible library, or a library changed after loading in the same process, raises `PluginLibraryError` before a cached plan is opened. Keep the library and its cubin bundle together in the caller's wheel.
|
|
113
117
|
|
|
@@ -287,7 +291,17 @@ The example was also built with an older compiler and CUDA toolkit:
|
|
|
287
291
|
| --- | --- | --- | --- |
|
|
288
292
|
| gcc 13.3.0 / CMake 4.2.3 / CUDA toolkit 12.9.86 / TensorRT SDK 11.2.1.2 | `build` and a separate-process `reuse` with TensorRT runtime 11.2.1.2 on sm86 succeeded with zero outputs outside tolerance and both variants launched | 3.13.7 | WSL2 / glibc 2.39 |
|
|
289
293
|
|
|
290
|
-
Each GPU subprocess had a 50-second limit. Speed was not measured.
|
|
294
|
+
Each GPU subprocess had a 50-second limit. Speed was not measured.
|
|
295
|
+
|
|
296
|
+
On 2026-10-18, the test suite and a native plugin build were run on Windows:
|
|
297
|
+
|
|
298
|
+
| GPU | Result | Python | OS / NVIDIA driver / build tools / TensorRT |
|
|
299
|
+
| --- | --- | --- | --- |
|
|
300
|
+
| NVIDIA RTX 6000 Ada Generation (sm89) | `pytest`: 246 passed, 20 skipped (Linux-only memory checks and optional `convert` and `producer` dependencies) | 3.12.11 | Windows 11 Pro x64 build 26200 / 596.72 / Visual Studio 2022 (MSVC 19.44), CMake 3.31.6, Ninja, CUDA toolkit 12.8.93 / runtime and SDK 11.4.0.106 |
|
|
301
|
+
|
|
302
|
+
On Windows, the standalone `cpp/` build and CTest passed, the CMake helper built the example plugin DLL against `nvinfer_11.lib`, and the plugin engine was built and reused in a separate process. The `py3-none-any` wheel built on Linux was installed unchanged on Windows. With `PATH` reduced to the Windows system directories and no CUDA or TensorRT SDK paths, the driver's `nvcuda.dll` and the pip-installed TensorRT runtime were sufficient to build and reuse engines.
|
|
303
|
+
|
|
304
|
+
Native Linux, other kernels, Windows with sm86 or sm120, and TensorRT 11 versions not listed here have not been tested.
|
|
291
305
|
|
|
292
306
|
## Compare output arrays
|
|
293
307
|
|
|
@@ -10,7 +10,11 @@ function(tensoracle_add_cubin_plugin)
|
|
|
10
10
|
find_package(CUDAToolkit REQUIRED)
|
|
11
11
|
find_path(TENSORACLE_NVINFER_INCLUDE NvInfer.h
|
|
12
12
|
PATHS "${ARG_TENSORRT_ROOT}/include" NO_DEFAULT_PATH REQUIRED)
|
|
13
|
-
|
|
13
|
+
set(_tensoracle_nvinfer_names nvinfer)
|
|
14
|
+
if(WIN32)
|
|
15
|
+
list(PREPEND _tensoracle_nvinfer_names nvinfer_11)
|
|
16
|
+
endif()
|
|
17
|
+
find_library(TENSORACLE_NVINFER_LIBRARY NAMES ${_tensoracle_nvinfer_names}
|
|
14
18
|
PATHS "${ARG_TENSORRT_ROOT}/lib" "${ARG_TENSORRT_ROOT}/lib64"
|
|
15
19
|
NO_DEFAULT_PATH REQUIRED)
|
|
16
20
|
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# Build a C++ and Python repository
|
|
2
|
+
|
|
3
|
+
Use this guide to organize a repository with a standalone C++ library and a Python package, then build and inspect its Linux wheels. Keep the C++ build independent of Python, put the Python project in its own directory, and make the package build collect the files it needs from those sources. The binding and wheel examples below illustrate a package named `example`; adapt the target names and dependencies to your project.
|
|
4
|
+
|
|
5
|
+
## 1. Separate the language projects
|
|
6
|
+
|
|
7
|
+
Keep `cpp/` and `python/` at the repository root:
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
README.md
|
|
11
|
+
LICENSE
|
|
12
|
+
docs/
|
|
13
|
+
cpp/
|
|
14
|
+
CMakeLists.txt
|
|
15
|
+
include/
|
|
16
|
+
src/
|
|
17
|
+
tests/
|
|
18
|
+
bindings.cpp # Optional Python binding source
|
|
19
|
+
python/
|
|
20
|
+
pyproject.toml
|
|
21
|
+
uv.lock
|
|
22
|
+
CMakeLists.txt # Only when the Python build compiles an extension
|
|
23
|
+
src/
|
|
24
|
+
example/
|
|
25
|
+
__init__.py
|
|
26
|
+
tests/
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`cpp/` owns the C++ sources, headers, CMake targets, and CTest tests. Its standalone configuration builds and tests the C++ library without discovering Python or nanobind. The Python build can compile `bindings.cpp` separately and link it to that library.
|
|
30
|
+
|
|
31
|
+
`python/` is the Python project root. It owns package metadata, runtime dependencies, development dependencies, the lockfile, and the src-layout package. Run the project's uv commands there. Keep repository-wide documentation, the license, and examples at the repository root; keep `pyproject.toml`, Python tests, and the Python environment under `python/`.
|
|
32
|
+
|
|
33
|
+
Keep one editable copy of each source file. Configure the package build to collect C++ sources or compiled libraries into the package as appropriate. A build that uses sibling `cpp/` files must also make those files available when building from a source distribution: stage a complete source tree or arrange an equivalent archive layout, then verify a build from the extracted archive.
|
|
34
|
+
|
|
35
|
+
Tensoracle is an example of source packaging: its wheel includes the shared C++ headers, sources, and CMake helpers under `tensoracle/cpp/`, and the caller compiles its own plugin. Tensoracle's wheel contains no native binaries and uses `py3-none-any`. A repository that builds a Python extension needs the additional steps in sections 4–6.
|
|
36
|
+
|
|
37
|
+
## 2. Keep development tools in the appropriate environment
|
|
38
|
+
|
|
39
|
+
Create the Python environment at `python/.venv`, alongside `python/pyproject.toml` and `python/uv.lock`; keep the repository root free of a second `.venv`. uv uses a project-local [virtual environment](https://docs.astral.sh/uv/concepts/projects/layout/#the-project-environment). For this layout, start from the repository root and run:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
cd python
|
|
43
|
+
uv sync
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Declare Python development tools such as pytest, Ruff, and a type checker in the Python project's development dependency group. Keep runtime dependencies in the `[project]` table's `dependencies` field, optional runtime features in extras, and isolated build requirements in the `[build-system]` table's `requires` field.
|
|
47
|
+
|
|
48
|
+
Keep one CMake and one Ninja installation on the host's `PATH`, shared by standalone C++ builds and tests that invoke CMake. Do not add `cmake` or `ninja` to each project's Python development dependency group. Their installation method can follow the developer's platform and package manager.
|
|
49
|
+
|
|
50
|
+
Each CMake entry point declares its minimum supported version with [`cmake_minimum_required`](https://cmake.org/cmake/help/latest/command/cmake_minimum_required.html). The host installation must meet all projects' minimums. Tensoracle's standalone C++ project requires CMake 3.20; the binding example below uses CMake 3.26 for `Development.SABIModule`. Verify affected C++ builds before upgrading a shared host installation.
|
|
51
|
+
|
|
52
|
+
An isolated wheel build has separate requirements. Keep any CMake requirement it needs in `[build-system] requires`; that environment does not replace the host tools. [scikit-build-core](https://scikit-build-core.readthedocs.io/en/stable/configuration/index.html#cmake-and-ninja-minimum-versions) can obtain CMake or Ninja when the available versions do not satisfy its configuration.
|
|
53
|
+
|
|
54
|
+
## 3. Test each language and identify GPU tests
|
|
55
|
+
|
|
56
|
+
Run pytest from `python/` so that the Python project's configuration and environment apply:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
uv run pytest
|
|
60
|
+
uv run pytest -m "not gpu"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Register a `gpu` marker in the pytest configuration and apply `@pytest.mark.gpu` to tests that need a GPU. Use strict marker checking to catch misspelled markers. The second command selects tests that can run without a GPU; those tests must also avoid GPU initialization during collection. See [pytest's marker documentation](https://docs.pytest.org/en/stable/how-to/mark.html).
|
|
64
|
+
|
|
65
|
+
Register the standalone C++ tests with CTest, using `include(CTest)` and `add_test`. From the repository root, configure and run them without activating the Python environment:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
cmake -S cpp -B build/cpp -G Ninja -DBUILD_TESTING=ON
|
|
69
|
+
cmake --build build/cpp
|
|
70
|
+
ctest --test-dir build/cpp --output-on-failure
|
|
71
|
+
ctest --test-dir build/cpp --output-on-failure -LE gpu
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Supply any required SDK or toolchain paths at configuration time. Python independence does not remove C++ library, header, or CUDA toolkit prerequisites. For TensorRT plugins, follow [Prepare a TensorRT SDK](tensorrt-sdk.md).
|
|
75
|
+
|
|
76
|
+
Give GPU-dependent CTest tests a [`gpu` label](https://cmake.org/cmake/help/latest/prop_test/LABELS.html); the final command excludes that label. A machine without a CUDA toolkit may also need a configuration that omits GPU targets. Test C++ library behavior through CTest and Python imports, bindings, and integration through pytest. Before distributing a wheel, install it into a clean environment and test the installed package outside the source tree.
|
|
77
|
+
|
|
78
|
+
## 4. Install nanobind extensions inside the package
|
|
79
|
+
|
|
80
|
+
Keep the standalone library's CMake configuration independent of the Python bindings. The Python project's CMake configuration discovers Python and nanobind, adds the C++ library, and installs the extension into the Python package. For a C++ target named `example_core`, these fragments illustrate that arrangement.
|
|
81
|
+
|
|
82
|
+
In `python/pyproject.toml`, alongside the project's metadata:
|
|
83
|
+
|
|
84
|
+
```toml
|
|
85
|
+
[build-system]
|
|
86
|
+
requires = ["scikit-build-core", "nanobind"]
|
|
87
|
+
build-backend = "scikit_build_core.build"
|
|
88
|
+
|
|
89
|
+
[tool.scikit-build]
|
|
90
|
+
wheel.packages = ["src/example"]
|
|
91
|
+
wheel.py-api = "cp312"
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
In `python/CMakeLists.txt`:
|
|
95
|
+
|
|
96
|
+
```cmake
|
|
97
|
+
cmake_minimum_required(VERSION 3.26)
|
|
98
|
+
project(example_python LANGUAGES CXX)
|
|
99
|
+
|
|
100
|
+
find_package(Python 3.12 REQUIRED COMPONENTS
|
|
101
|
+
Interpreter Development.Module Development.SABIModule)
|
|
102
|
+
find_package(nanobind CONFIG REQUIRED)
|
|
103
|
+
|
|
104
|
+
add_subdirectory(../cpp cpp)
|
|
105
|
+
set_target_properties(example_core PROPERTIES POSITION_INDEPENDENT_CODE ON)
|
|
106
|
+
nanobind_add_module(_native STABLE_ABI ../cpp/bindings.cpp)
|
|
107
|
+
target_link_libraries(_native PRIVATE example_core)
|
|
108
|
+
install(TARGETS _native LIBRARY DESTINATION example)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The binding source declares `NB_MODULE(_native, m)`, and `python/src/example/__init__.py` imports from `._native`. The installed wheel contains `example/__init__.py` and the extension, such as `example/_native.abi3.so`. The snippets assume that `cpp/CMakeLists.txt` defines `example_core` and that the package build supplies the sibling C++ sources as described in section 1. See [nanobind packaging](https://nanobind.readthedocs.io/en/latest/packaging.html) for the binding build and installation mechanism.
|
|
112
|
+
|
|
113
|
+
For nanobind's linked build mode, [`STABLE_ABI`](https://nanobind.readthedocs.io/en/latest/api_cmake.html#command:nanobind_add_module) requires Python 3.12 or later. Pair it with `wheel.py-api = "cp312"` so that the binary and wheel metadata agree. This produces a CPython stable-ABI extension for compatible GIL-enabled Python versions from that floor, subject to the package's `requires-python`. It does not remove platform, C++ ABI, or runtime library constraints. Free-threaded Python needs its own build and compatibility decisions.
|
|
114
|
+
|
|
115
|
+
Read the three parts of the [wheel compatibility tag](https://packaging.python.org/en/latest/specifications/platform-compatibility-tags/) separately:
|
|
116
|
+
|
|
117
|
+
| Wheel contents | Example Python–ABI–platform tag |
|
|
118
|
+
| --- | --- |
|
|
119
|
+
| Python and source files, without native binaries | `py3-none-any` |
|
|
120
|
+
| Native libraries that do not use the Python C API | `py3-none-manylinux_2_28_x86_64` |
|
|
121
|
+
| A CPython stable-ABI extension with a Python 3.12 floor | `cp312-abi3-manylinux_2_28_x86_64` |
|
|
122
|
+
| A CPython 3.12 extension using its version-specific ABI | `cp312-cp312-manylinux_2_28_x86_64` |
|
|
123
|
+
|
|
124
|
+
These are illustrative targets, not tags to assign before inspecting the binaries. `none` describes the Python ABI; a wheel carrying a shared library still needs a platform tag. `abi3` retains a minimum Python version and does not mean every Python implementation or build is supported.
|
|
125
|
+
|
|
126
|
+
## 5. Check manylinux compatibility before uploading to PyPI
|
|
127
|
+
|
|
128
|
+
For glibc-based Linux x86_64 wheels, use a supported manylinux platform tag. PyPI's [platform validation](https://github.com/pypi/warehouse/blob/main/warehouse/utils/wheel.py) rejects the generic `linux_x86_64` tag. Under [PEP 600](https://peps.python.org/pep-0600/), `manylinux_2_28_x86_64` promises compatibility with mainstream x86_64 Linux systems using glibc 2.28 or later. It does not describe CUDA or GPU driver requirements; document those separately.
|
|
129
|
+
|
|
130
|
+
The actual minimum follows the symbols and libraries used by the built artifacts. Linking on a newer system can introduce newer glibc requirements even when the source code has not changed. The host's glibc version alone is not a measurement of the wheel's minimum, and changing the filename cannot lower that minimum. Inspect required `GLIBC_*` versions, along with `GLIBCXX_*` and `CXXABI_*` requirements for the C++ runtime.
|
|
131
|
+
|
|
132
|
+
Run [`auditwheel show`](https://github.com/pypa/auditwheel) in the build container. Extract the wheel and inspect its ELF files with [`readelf`](https://sourceware.org/binutils/docs/binutils/readelf.html). For example, after setting `WHEEL` to the exact wheel path and choosing a new extraction directory:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
auditwheel show "$WHEEL"
|
|
136
|
+
python -m zipfile -e "$WHEEL" wheel-inspect
|
|
137
|
+
LC_ALL=C readelf --version-info --wide wheel-inspect/example/_native.abi3.so
|
|
138
|
+
LC_ALL=C readelf --dynamic --wide wheel-inspect/example/_native.abi3.so
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Repeat the ELF checks for every shared library and executable in the wheel, including nested dependencies. Inspect the version requirements and `NEEDED`, `RPATH`, and `RUNPATH` entries. Use `LC_ALL=C` when tools or scripts rely on English output headings, and compare numeric version components rather than sorting version strings lexically. Rebuild in an appropriate environment if a required symbol exceeds the intended baseline; auditwheel cannot repair an overly new glibc or libstdc++ dependency.
|
|
142
|
+
|
|
143
|
+
### Shared libraries and search paths
|
|
144
|
+
|
|
145
|
+
Assign each shipped shared library to one package. If another wheel supplies a library, declare that package as a runtime dependency and arrange a documented runtime lookup path. Do not copy its library into the consuming wheel. RAPIDS publishes separate [Python binding](https://pypi.org/project/pylibcudf-cu12/) and [C++ library](https://pypi.org/project/libcudf-cu12/) distributions as `pylibcudf-cu12` and `libcudf-cu12`; their release files illustrate the distinction between Python ABI tags and platform tags.
|
|
146
|
+
|
|
147
|
+
`auditwheel repair` normally copies non-policy shared libraries into the wheel and changes their search paths. Review that list before repair, and exclude libraries owned by dependency wheels and libraries provided by the GPU driver. An exclusion does not establish manylinux compliance. Resolve and test those dependencies separately, and inspect the final wheel again. Also test libraries loaded through `dlopen` or `ctypes`, which auditwheel cannot discover from `NEEDED` entries alone.
|
|
148
|
+
|
|
149
|
+
Use `$ORIGIN`-relative ELF search paths, where `$ORIGIN` is the directory containing the referencing binary. For an extension whose own package contains a `lib/` subdirectory, a CMake target can use:
|
|
150
|
+
|
|
151
|
+
```cmake
|
|
152
|
+
set_target_properties(_native PROPERTIES
|
|
153
|
+
BUILD_RPATH_USE_ORIGIN ON
|
|
154
|
+
INSTALL_RPATH "$ORIGIN/lib")
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
[`INSTALL_RPATH`](https://cmake.org/cmake/help/latest/prop_tgt/INSTALL_RPATH.html) controls the installed artifact; `BUILD_RPATH_USE_ORIGIN` applies to build-tree paths. Select the relative path for the actual installed layout, including dependency packages where needed, and check every library's own dependencies. Remove absolute build-machine paths, including paths introduced by imported CMake targets. Keep `$ORIGIN` literal when passing a value through a shell.
|
|
158
|
+
|
|
159
|
+
Keep NVIDIA driver libraries such as `libcuda.so.1`, `libnvidia-encode.so.1`, and `libnvcuvid.so.1` outside the wheel. The target system supplies the compatible driver. Link-time SDK stubs must also stay out of the runtime package and its search paths; NVIDIA's [Video Codec SDK instructions](https://docs.nvidia.com/video-technologies/video-codec-sdk/13.0/read-me/index.html) distinguish these stubs from the installed driver. Treat CUDA runtime libraries separately, using an appropriate static runtime or declared runtime package dependencies for the chosen toolkit.
|
|
160
|
+
|
|
161
|
+
Check artifact size before upload. PyPI's [default individual file limit](https://docs.pypi.org/project-management/storage-limits/) is 100 MB; projects can have an approved increase. Installing and testing the final wheel with only its declared dependencies is still necessary after tag and ELF inspection.
|
|
162
|
+
|
|
163
|
+
## 6. Record the wheel build environment
|
|
164
|
+
|
|
165
|
+
Build release wheels in a [manylinux container](https://github.com/pypa/manylinux) for the intended architecture and compatibility floor. For example, `quay.io/pypa/manylinux_2_28_x86_64` provides a glibc 2.28 baseline. Select compiler, SDK, CUDA toolkit, and dependency versions that can build and run against that baseline. A recent compiler does not require building against a recent host glibc.
|
|
166
|
+
|
|
167
|
+
Pin the image by an immutable [image digest](https://docs.docker.com/reference/cli/docker/image/pull/#pull-an-image-by-digest-immutable-identifier), using `image@sha256:<digest>` with the actual digest of the selected image. A moving tag is useful for discovering updates but does not identify a repeatable build input. Pin any derived image as well, and record additional package installation inputs.
|
|
168
|
+
|
|
169
|
+
Keep a build record with:
|
|
170
|
+
|
|
171
|
+
- The source revision, patches, and source manifest.
|
|
172
|
+
- The image reference, manifest digest, image configuration digest, and architecture.
|
|
173
|
+
- The compiler, linker, CMake, Ninja, Python interpreter, and build backend versions.
|
|
174
|
+
- The CUDA toolkit and SDK versions, when used, and relevant compiler or CMake options.
|
|
175
|
+
- The resolved build and runtime dependencies, resulting wheel hashes, ELF inspection results, and test results.
|
|
176
|
+
|
|
177
|
+
Build from clean inputs inside that container, inspect the final artifacts there, and test their installation in a separate runtime environment. When native C++ objects cross extension boundaries, align the C++ library ABI, compiler compatibility, debug mode, symbol visibility, and nanobind ABI. Python's stable ABI does not make incompatible C++ objects interchangeable. [nanobind's compatibility requirements](https://nanobind.readthedocs.io/en/latest/faq.html#how-can-i-avoid-conflicts-with-other-projects-using-nanobind) describe those constraints.
|
|
178
|
+
|
|
179
|
+
## 7. Record and reconstruct upstream C++ sources
|
|
180
|
+
|
|
181
|
+
When development needs a local copy of an upstream C++ project, copy the selected files into a dedicated directory and keep the source inputs explicit. Record the upstream URL and immutable revision, the selected paths, the ordered local patches, and the license information. Keep a SHA-256 manifest covering every file in the resulting source tree, including files added by patches.
|
|
182
|
+
|
|
183
|
+
For example, a provenance directory can contain:
|
|
184
|
+
|
|
185
|
+
```text
|
|
186
|
+
upstream/library/
|
|
187
|
+
PROVENANCE.md
|
|
188
|
+
patches/
|
|
189
|
+
MANIFEST.sha256
|
|
190
|
+
fetch-and-verify.py
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
`PROVENANCE.md` identifies the upstream revision and explains the selection and patch order. `MANIFEST.sha256` records the expected relative paths and hashes after patching. The acquisition script reads these recorded inputs, retrieves the exact revision, selects the required files, applies the patches, and verifies the resulting tree. Keep the revision in one authoritative location that the script reads.
|
|
194
|
+
|
|
195
|
+
Use ordinary copied files for the development tree, rather than a Git submodule. A submodule records a commit reference instead of ordinary source files, so a publication process that copies tracked files into a snapshot does not materialize that source tree. An explicit acquisition script also makes the build independent of recursive Git checkout behavior.
|
|
196
|
+
|
|
197
|
+
Keep the copied upstream source tree out of the public repository. Publish the revision record, patches, complete file manifest, acquisition and verification script, and instructions for building and testing from the reconstructed sources. Preserve required license notices in the appropriate distributed artifacts. The public checkout must let another developer reconstruct the inputs and build the native components without access to the development copy.
|
|
198
|
+
|
|
199
|
+
Verify both file contents and the complete set of paths: missing, changed, and unexpected files must fail verification. A checksum command that only checks listed files will not detect extra files by itself. Verify before configuring or compiling, and stop on a download, patch, or manifest failure. When updating upstream, regenerate the manifest from the reviewed source selection and rerun the native build and tests. If publishing a source distribution, ensure its documented acquisition process makes the same verified inputs available for its build.
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
`tensoracle` builds TensorRT engines from model graphs supplied through TensorRT's Python network API. A model package owns its graph, safetensors weights, and output quality limits. `tensoracle` supplies engine building, saved engine reuse, execution, output comparison, and tools for cubin plugins. These workflows do not require torch. Only the optional `.pth` checkpoint converter requires torch, installed with `tensoracle[convert]`.
|
|
4
4
|
|
|
5
|
-
Follow the [four-stage optimization workflow](optimization.md) to establish output quality, evaluate
|
|
5
|
+
Follow the [four-stage optimization workflow](optimization.md) to establish output quality, evaluate lower precision and plugins, and measure speed.
|
|
6
|
+
|
|
7
|
+
Follow [Build a C++ and Python repository](cpp-python-repositories.md) for source layout, development environments, bindings, and Linux wheel packaging.
|
|
6
8
|
|
|
7
9
|
## Terms
|
|
8
10
|
|
|
@@ -29,7 +31,7 @@ Follow the [four-stage optimization workflow](optimization.md) to establish outp
|
|
|
29
31
|
| TensorRT 11 SDK | Not needed | Not needed | Native plugin build |
|
|
30
32
|
| CMake 3.20 or later and C++17 compiler | Not needed | Not needed | Native plugin build |
|
|
31
33
|
|
|
32
|
-
The supported
|
|
34
|
+
The supported platforms are Linux x86_64 (including WSL2) and Windows x86_64 with sm86, sm89, or sm120 GPUs and TensorRT 11. The same `py3-none-any` wheel installs on both operating systems. Cubin production with `tensoracle[producer]` runs on Linux; a plugin DLL built on Windows can embed cubins produced on Linux. The producer and native build tools are separate from the `tensoracle` runtime dependencies. Engine building, engine reuse, output comparison, and cubin production do not require torch. Converting `.pth` checkpoints requires the `convert` extra.
|
|
33
35
|
|
|
34
36
|
### Convert a PyTorch checkpoint
|
|
35
37
|
|
|
@@ -70,7 +72,7 @@ handle = open_engine(
|
|
|
70
72
|
)
|
|
71
73
|
```
|
|
72
74
|
|
|
73
|
-
`my_model` represents the application's model package. On a valid cache hit, `open_engine` does not call `graph`; on a miss, it builds and saves a new engine. Raise `graph_version` when the graph code changes or when any other caller value that changes the engine contents but is not part of the cache identity changes, such as a plugin manifest or a build recipe. `open_engine` itself does not execute the engine or manage GPU buffers. Create an execution unit from its returned handle to enqueue inference. Saved engines live under `XDG_CACHE_HOME/tensoracle`, or `~/.cache/tensoracle` when `XDG_CACHE_HOME` is unset. See the [complete cache example](https://github.com/sync-dev-org/tensoracle#save-and-reopen-an-engine) for the declaration and expected input and output types.
|
|
75
|
+
`my_model` represents the application's model package. On a valid cache hit, `open_engine` does not call `graph`; on a miss, it builds and saves a new engine. Raise `graph_version` when the graph code changes or when any other caller value that changes the engine contents but is not part of the cache identity changes, such as a plugin manifest or a build recipe. `open_engine` itself does not execute the engine or manage GPU buffers. Create an execution unit from its returned handle to enqueue inference. Saved engines live under `XDG_CACHE_HOME/tensoracle`, or `~/.cache/tensoracle` when `XDG_CACHE_HOME` is unset. On Windows, `~` is `%USERPROFILE%`. See the [complete cache example](https://github.com/sync-dev-org/tensoracle#save-and-reopen-an-engine) for the declaration and expected input and output types.
|
|
74
76
|
|
|
75
77
|
### Execute an engine
|
|
76
78
|
|
|
@@ -161,7 +163,7 @@ Networks with outputs marked as shape tensors emit `tensoracle.ShapeTensorOutput
|
|
|
161
163
|
|
|
162
164
|
### Build a standard graph
|
|
163
165
|
|
|
164
|
-
Define `graph(network)` with TensorRT's Python network API and load model weights from safetensors. Describe each graph input with `InputShape.fixed` or `InputShape.ranged`, and pass an `EngineBuild` declaration to `build_serialized_engine`. `EngineBuild` declares input shapes and builder settings; the graph function determines precision through the tensor types it places in the network. The supported precisions are fp32 (with TF32 disabled) and
|
|
166
|
+
Define `graph(network)` with TensorRT's Python network API and load model weights from safetensors. Describe each graph input with `InputShape.fixed` or `InputShape.ranged`, and pass an `EngineBuild` declaration to `build_serialized_engine`. `EngineBuild` declares input shapes and builder settings; the graph function determines precision through the tensor types it places in the network. The supported precisions are fp32 (with TF32 disabled), fp16, bf16, int8, and fp8. The [bf16, int8, and fp8 guide](precision.md) shows tensor casts, explicit Q/DQ layers, and scales supplied through safetensors; fp8 targets sm89 and sm120. `open_engine` uses the same declaration plus a name, graph version, weight files, and `EngineIO` to save and reuse a checked engine. Use a different `name` or `graph_version` for engines with different tensor types, such as fp32 and fp16. The [graph example](https://github.com/sync-dev-org/tensoracle#define-and-build-a-standard-graph) shows the exact calls.
|
|
165
167
|
|
|
166
168
|
The application runs the engine and passes a CPU NumPy candidate output and a corresponding reference output to `compare_outputs`. The application chooses the acceptance limits. See [Compare output arrays](https://github.com/sync-dev-org/tensoracle#compare-output-arrays).
|
|
167
169
|
|
|
@@ -10,9 +10,9 @@ Use the same representative inputs, shapes, and corresponding outputs throughout
|
|
|
10
10
|
|
|
11
11
|
## 2. Accept lower precision by quality
|
|
12
12
|
|
|
13
|
-
- **tensoracle tools:** Build an fp16
|
|
13
|
+
- **tensoracle tools:** Build an fp16, bf16, int8, or fp8 TensorRT engine using the same `EngineBuild` declaration for input shapes and builder settings. The graph function determines precision through its tensor types. For int8 and fp8, the graph places explicit quantize/dequantize layers and reads their scales from safetensors; the application prepares those scales with an external quantization library. Follow [Build bf16, int8, and fp8 engines](precision.md) for a complete graph example and the GPU targets (fp8 requires sm89 or sm120). When saving with `open_engine`, use a different `name` or `graph_version` from the fp32 engine. Use `compare_outputs` with each lower precision output as the candidate and its accepted fp32 TensorRT output as the reference. It reports non-finite counts and, for finite floating point arrays, maximum absolute error, relative L2 error, cosine similarity, and, when both tolerances are supplied, counts and fractions outside tolerance.
|
|
14
14
|
- **You provide:** Choose limits for those model-independent measurements. Compute any model-specific measurements separately, such as PSNR for images or application-specific time-series quality measures, and choose their limits. Run both engines on the same inputs.
|
|
15
|
-
- **Gate:** Accept the
|
|
15
|
+
- **Gate:** Accept the lower precision engine only when its outputs meet all applicable limits, including the model-specific ones. Otherwise keep the accepted fp32 engine or revise the candidate and repeat this stage.
|
|
16
16
|
|
|
17
17
|
## 3. Replace a remaining bottleneck with a plugin
|
|
18
18
|
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# Build bf16, int8, and fp8 engines
|
|
2
|
+
|
|
3
|
+
The graph function selects tensor types. Use the same `EngineBuild`, `open_engine`, and execution API for fp32, fp16, bf16, int8, and fp8. For bf16, cast tensors to `trt.bfloat16`. For int8 and fp8, place explicit quantize/dequantize (Q/DQ) layers and their scales in the graph. TensorRT 11's [`add_quantize` and `add_dequantize` operators](https://docs.nvidia.com/deeplearning/tensorrt/11.4.0/_static/operators/Quantize.html) accept the quantized type and dequantized type explicitly.
|
|
4
|
+
|
|
5
|
+
| Graph precision | Tensor placement in this example | Supported tensoracle GPU targets |
|
|
6
|
+
| --- | --- | --- |
|
|
7
|
+
| fp32 | fp32 matrix multiplication and bias, with TF32 disabled | sm86, sm89, sm120 |
|
|
8
|
+
| bf16 | Cast features, matrix, and bias to bf16; cast the result to fp32 | sm86, sm89, sm120 |
|
|
9
|
+
| int8 | Q/DQ the features and matrix with `trt.int8` and explicit scales | sm86, sm89, sm120 |
|
|
10
|
+
| fp8 | Q/DQ the features and matrix with `trt.fp8` (E4M3) and explicit scales | sm89, sm120 |
|
|
11
|
+
|
|
12
|
+
Do not build the fp8 graph on sm86. The graph keeps fp32 input and output buffers for every precision; the internal tensors determine its arithmetic. TensorRT chooses implementations for the typed graph, so an int8 or fp8 graph can retain higher precision operations such as the bias addition. This example does not establish a speed improvement.
|
|
13
|
+
|
|
14
|
+
## Prepare weights and scales outside tensoracle
|
|
15
|
+
|
|
16
|
+
For a real model, use an external quantization library, such as [NVIDIA Model Optimizer](https://github.com/NVIDIA/Model-Optimizer), to determine scales from representative data or quantization-aware training. The model package translates those results into its safetensors file and graph. tensoracle does not calculate scales, calibrate a model, or translate a quantizer's export format.
|
|
17
|
+
|
|
18
|
+
Store the fp32 weights and positive fp32 scales together in safetensors. In this example, `matrix` has shape `(32, 16)`, `bias` has shape `(1, 16)`, and each scale has shape `(1,)`. The keys `int8.input_scale`, `int8.weight_scale`, `fp8.input_scale`, and `fp8.weight_scale` distinguish the two quantization recipes. Both Q and DQ use the same scale for each tensor.
|
|
19
|
+
|
|
20
|
+
The following small fixture uses maximum absolute values to choose scales. Its inputs and weights match the GPU test; this scale calculation is only a demonstration for that fixture, not a calibration recipe for other models:
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
|
|
25
|
+
import numpy as np
|
|
26
|
+
from safetensors.numpy import save_file
|
|
27
|
+
|
|
28
|
+
path = Path("linear.safetensors")
|
|
29
|
+
features = np.random.default_rng(11).uniform(-1, 1, (16, 32)).astype(np.float32)
|
|
30
|
+
rng = np.random.default_rng(29)
|
|
31
|
+
matrix = rng.uniform(-0.25, 0.25, (32, 16)).astype(np.float32)
|
|
32
|
+
bias = rng.uniform(-0.1, 0.1, (1, 16)).astype(np.float32)
|
|
33
|
+
weights = {"matrix": matrix, "bias": bias}
|
|
34
|
+
for precision, maximum in (("int8", 127), ("fp8", 448)):
|
|
35
|
+
weights[f"{precision}.input_scale"] = np.array(
|
|
36
|
+
[np.max(np.abs(features)) / maximum], dtype=np.float32
|
|
37
|
+
)
|
|
38
|
+
weights[f"{precision}.weight_scale"] = np.array(
|
|
39
|
+
[np.max(np.abs(matrix)) / maximum], dtype=np.float32
|
|
40
|
+
)
|
|
41
|
+
save_file(weights, path)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
[`safetensors.numpy` 0.8.0](https://github.com/huggingface/safetensors/blob/v0.8.0/bindings/python/py_src/safetensors/numpy.py) with NumPy 2.5 does not directly load bf16 or fp8 tensors into NumPy arrays: bf16 loading raises `TypeError`, and FP8 E4M3 loading raises `AttributeError`. A safetensors file can store those types, but its NumPy reader cannot represent them in this environment. Keep this example's weights and scales in fp32, then cast or quantize in TensorRT. If an existing checkpoint stores bf16 or fp8 weights, convert them with a reader that supports that dtype outside this NumPy graph path. Installing torch is not required for this example.
|
|
45
|
+
|
|
46
|
+
## Define the typed graph
|
|
47
|
+
|
|
48
|
+
This graph computes `features @ matrix + bias`. The bf16 branch casts all three operands before arithmetic and returns fp32. The int8/fp8 branch applies per-tensor Q/DQ to the features and matrix; its bias stays fp32. Keeping the stored matrix in fp32 lets TensorRT quantize it with the supplied scale during building.
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
import tensorrt as trt
|
|
52
|
+
from safetensors.numpy import load_file
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def graph(path, precision):
|
|
56
|
+
def define(network):
|
|
57
|
+
weights = load_file(path)
|
|
58
|
+
|
|
59
|
+
def constant(array):
|
|
60
|
+
array = np.ascontiguousarray(array, dtype=np.float32)
|
|
61
|
+
return network.add_constant(array.shape, array).get_output(0)
|
|
62
|
+
|
|
63
|
+
features = network.add_input("features", trt.float32, (16, 32))
|
|
64
|
+
matrix = constant(weights["matrix"])
|
|
65
|
+
bias = constant(weights["bias"])
|
|
66
|
+
if precision == "bf16":
|
|
67
|
+
features = network.add_cast(features, trt.bfloat16).get_output(0)
|
|
68
|
+
matrix = network.add_cast(matrix, trt.bfloat16).get_output(0)
|
|
69
|
+
bias = network.add_cast(bias, trt.bfloat16).get_output(0)
|
|
70
|
+
elif precision in ("int8", "fp8"):
|
|
71
|
+
quantized_type = trt.int8 if precision == "int8" else trt.fp8
|
|
72
|
+
|
|
73
|
+
def qdq(tensor, key):
|
|
74
|
+
scale = constant(weights[f"{precision}.{key}_scale"])
|
|
75
|
+
quantized = network.add_quantize(tensor, scale, quantized_type).get_output(0)
|
|
76
|
+
return network.add_dequantize(quantized, scale, trt.float32).get_output(0)
|
|
77
|
+
|
|
78
|
+
features = qdq(features, "input")
|
|
79
|
+
matrix = qdq(matrix, "weight")
|
|
80
|
+
value = network.add_matrix_multiply(
|
|
81
|
+
features, trt.MatrixOperation.NONE, matrix, trt.MatrixOperation.NONE
|
|
82
|
+
).get_output(0)
|
|
83
|
+
value = network.add_elementwise(value, bias, trt.ElementWiseOperation.SUM).get_output(0)
|
|
84
|
+
if precision == "bf16":
|
|
85
|
+
value = network.add_cast(value, trt.float32).get_output(0)
|
|
86
|
+
value.name = "result"
|
|
87
|
+
network.mark_output(value)
|
|
88
|
+
|
|
89
|
+
return define
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Build, save, and reuse each precision
|
|
93
|
+
|
|
94
|
+
Use a distinct engine name or graph version whenever tensor types or the graph recipe change. The same weight file serves all four graphs here, so its digest cannot distinguish their precision. Scales are part of that file and therefore part of the saved engine identity.
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from tensoracle import (
|
|
98
|
+
BuilderSettings, EngineBuild, EngineIO, InputShape, TensorSpec, open_engine,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
declaration = EngineBuild(
|
|
102
|
+
{"features": InputShape.fixed((16, 32))},
|
|
103
|
+
BuilderSettings(optimization_level=0),
|
|
104
|
+
)
|
|
105
|
+
io = EngineIO(
|
|
106
|
+
{"features": TensorSpec(trt.float32, (16, 32))},
|
|
107
|
+
{"result": TensorSpec(trt.float32, (16, 16))},
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def open_linear(precision):
|
|
112
|
+
return open_engine(
|
|
113
|
+
graph(path, precision),
|
|
114
|
+
declaration,
|
|
115
|
+
name=f"linear-{precision}",
|
|
116
|
+
graph_version="1",
|
|
117
|
+
weights=(path,),
|
|
118
|
+
io=io,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
reference_handle = open_linear("fp32")
|
|
123
|
+
candidate_handle = open_linear("bf16") # Or "int8"; "fp8" on sm89/sm120.
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
TF32 remains disabled by tensoracle, including for the fp32 reference. Engines are saved under `XDG_CACHE_HOME/tensoracle`, or `~/.cache/tensoracle` (`%USERPROFILE%\.cache\tensoracle` on Windows). Repeat the same call with unchanged build conditions in another process to reuse the plan without invoking the graph function. Keep the weight file and the engine names/versions stable across the restart. Follow [Execute an engine](index.md#execute-an-engine) to allocate fp32 input/output buffers and enqueue each handle with the same inputs.
|
|
127
|
+
|
|
128
|
+
## Compare outputs and choose limits
|
|
129
|
+
|
|
130
|
+
First establish the fp32 engine against fixed fp32 outputs from the official model implementation, as described in [stage 1](optimization.md#1-establish-the-fp32-reference). Then run the candidate and accepted fp32 engine with identical inputs. Pass their CPU NumPy outputs to `compare_outputs` and choose quality limits in the application:
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
from tensoracle import compare_outputs
|
|
134
|
+
|
|
135
|
+
# candidate_output and fp32_output are the application's engine outputs.
|
|
136
|
+
comparison = compare_outputs(candidate_output, fp32_output, atol=0.01, rtol=0)
|
|
137
|
+
accepted = (
|
|
138
|
+
comparison.candidate_nonfinite_count == 0
|
|
139
|
+
and comparison.reference_nonfinite_count == 0
|
|
140
|
+
and comparison.outside_tolerance_count == 0
|
|
141
|
+
and comparison.relative_l2_error is not None
|
|
142
|
+
and comparison.relative_l2_error < 0.01
|
|
143
|
+
)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
These example limits belong to the small bf16 fixture. For the fixture above, a TensorRT 11.4 run on sm120 measured:
|
|
147
|
+
|
|
148
|
+
| Candidate | Maximum absolute error | Relative L2 error | Test `atol` (`rtol=0`) | Test relative L2 limit |
|
|
149
|
+
| --- | --- | --- | --- | --- |
|
|
150
|
+
| bf16 | 0.00357 | 0.00294 | 0.01 | 0.01 |
|
|
151
|
+
| int8 | 0.00710 | 0.00572 | 0.02 | 0.02 |
|
|
152
|
+
| fp8 | 0.0501 | 0.0382 | 0.12 | 0.08 |
|
|
153
|
+
|
|
154
|
+
The test allows at least twice the observed errors for differences in GPU tactics. These are not tensoracle defaults or quality guarantees for other models. Choose representative data, per-output limits, and model-specific quality measurements for the real application. The GPU test also checks that the two engines occupy separate cache entries and reproduce their outputs in another process without calling either graph.
|