tensoracle 0.3.1__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.
tensoracle/convert.py ADDED
@@ -0,0 +1,71 @@
1
+ """Convert PyTorch tensor checkpoints to safetensors with optional torch dependencies."""
2
+
3
+ import argparse
4
+ import os
5
+ import pickle
6
+ from collections.abc import Mapping
7
+
8
+
9
+ def pth_to_safetensors(source: str | os.PathLike[str], destination: str | os.PathLike[str]) -> None:
10
+ """Write a tensor mapping to safetensors without unrestricted pickle loading.
11
+
12
+ Nested string keys are joined with ``.``. Invalid mapping keys or leaves
13
+ raise ``TypeError``; duplicate flattened names raise ``ValueError``.
14
+ Both are rejected before the destination is written.
15
+ Torch is required only when this function is called.
16
+ """
17
+ try:
18
+ import torch
19
+ except ImportError as exc:
20
+ raise ImportError(
21
+ "Checkpoint conversion requires torch; install it with pip install 'tensoracle[convert]'"
22
+ ) from exc
23
+ from safetensors.torch import save_file
24
+
25
+ try:
26
+ checkpoint = torch.load(source, map_location="cpu", weights_only=True)
27
+ except pickle.UnpicklingError as exc:
28
+ raise ValueError(f"Cannot safely load tensor checkpoint: {source}") from exc
29
+ if not isinstance(checkpoint, Mapping):
30
+ raise TypeError("Checkpoint must be a mapping of string keys to tensors or nested mappings")
31
+
32
+ tensors = {}
33
+ active_mappings = set()
34
+
35
+ def flatten(mapping: Mapping, prefix: tuple[str, ...]) -> None:
36
+ if id(mapping) in active_mappings:
37
+ raise ValueError("Checkpoint contains a cyclic mapping")
38
+ active_mappings.add(id(mapping))
39
+ for key, value in mapping.items():
40
+ if not isinstance(key, str):
41
+ raise TypeError("Checkpoint mapping keys must be strings")
42
+ parts = (*prefix, key)
43
+ name = ".".join(parts)
44
+ if isinstance(value, Mapping):
45
+ flatten(value, parts)
46
+ elif isinstance(value, torch.Tensor):
47
+ if name in tensors:
48
+ raise ValueError(f"Duplicate flattened tensor name: {name}")
49
+ # Separate shared storage and make views contiguous without changing values.
50
+ tensors[name] = value.detach().clone(memory_format=torch.contiguous_format)
51
+ else:
52
+ raise TypeError(f"Checkpoint value at {name!r} must be a tensor or mapping")
53
+ active_mappings.remove(id(mapping))
54
+
55
+ flatten(checkpoint, ())
56
+ save_file(tensors, destination)
57
+
58
+
59
+ def _main() -> None:
60
+ parser = argparse.ArgumentParser(description="Convert a PyTorch checkpoint to safetensors")
61
+ parser.add_argument("source", help="input .pth checkpoint")
62
+ parser.add_argument("destination", help="output .safetensors file")
63
+ args = parser.parse_args()
64
+ try:
65
+ pth_to_safetensors(args.source, args.destination)
66
+ except (ImportError, OSError, TypeError, ValueError, RuntimeError) as exc:
67
+ parser.exit(1, f"{parser.prog}: {exc}\n")
68
+
69
+
70
+ if __name__ == "__main__":
71
+ _main()
@@ -0,0 +1,29 @@
1
+ include_guard(GLOBAL)
2
+
3
+ function(tensoracle_add_cubin_plugin)
4
+ cmake_parse_arguments(ARG "" "TARGET;TENSORRT_ROOT;BUNDLE_ROOT" "SOURCES" ${ARGN})
5
+ if(NOT ARG_TARGET OR NOT ARG_TENSORRT_ROOT OR NOT ARG_BUNDLE_ROOT OR NOT ARG_SOURCES)
6
+ message(FATAL_ERROR "TARGET, TENSORRT_ROOT, BUNDLE_ROOT, and SOURCES are required")
7
+ endif()
8
+
9
+ get_filename_component(_tensoracle_cpp "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/.." ABSOLUTE)
10
+ find_package(CUDAToolkit REQUIRED)
11
+ find_path(TENSORACLE_NVINFER_INCLUDE NvInfer.h
12
+ PATHS "${ARG_TENSORRT_ROOT}/include" NO_DEFAULT_PATH REQUIRED)
13
+ find_library(TENSORACLE_NVINFER_LIBRARY NAMES nvinfer
14
+ PATHS "${ARG_TENSORRT_ROOT}/lib" "${ARG_TENSORRT_ROOT}/lib64"
15
+ NO_DEFAULT_PATH REQUIRED)
16
+
17
+ add_library(${ARG_TARGET} SHARED
18
+ ${ARG_SOURCES}
19
+ "${_tensoracle_cpp}/src/cubin_plugin.cpp"
20
+ "${ARG_BUNDLE_ROOT}/bundle.cpp")
21
+ target_compile_features(${ARG_TARGET} PRIVATE cxx_std_17)
22
+ target_compile_options(${ARG_TARGET} PRIVATE
23
+ $<$<CXX_COMPILER_ID:GNU,Clang>:-Wno-deprecated-declarations>)
24
+ target_include_directories(${ARG_TARGET} PRIVATE
25
+ "${_tensoracle_cpp}/include" "${TENSORACLE_NVINFER_INCLUDE}" ${CUDAToolkit_INCLUDE_DIRS})
26
+ target_link_libraries(${ARG_TARGET} PRIVATE "${TENSORACLE_NVINFER_LIBRARY}" CUDA::cuda_driver)
27
+ set_target_properties(${ARG_TARGET} PROPERTIES
28
+ CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN YES)
29
+ endfunction()
@@ -0,0 +1,156 @@
1
+ #pragma once
2
+
3
+ #include "NvInfer.h"
4
+ #include <cuda.h>
5
+
6
+ #include <cstddef>
7
+ #include <cstdint>
8
+ #include <map>
9
+ #include <mutex>
10
+ #include <string>
11
+ #include <utility>
12
+ #include <vector>
13
+
14
+ namespace tensoracle::cubin {
15
+
16
+ struct FieldValue {
17
+ std::string name;
18
+ nvinfer1::PluginFieldType type;
19
+ std::int32_t length;
20
+ std::vector<std::uint8_t> bytes;
21
+ };
22
+
23
+ // Own every byte. TensorRT may discard its input PluginFieldCollection after createPlugin.
24
+ class Fields {
25
+ public:
26
+ static bool parse(nvinfer1::PluginFieldCollection const* source, Fields& result) noexcept;
27
+ Fields() = default;
28
+ Fields(Fields const& other);
29
+ Fields& operator=(Fields const& other);
30
+ Fields(Fields&& other);
31
+ Fields& operator=(Fields&& other);
32
+
33
+ FieldValue const* find(char const* name) const noexcept;
34
+ std::vector<FieldValue> const& values() const noexcept { return values_; }
35
+ nvinfer1::PluginFieldCollection const* collection() noexcept;
36
+
37
+ private:
38
+ void rebuild();
39
+ std::vector<FieldValue> values_;
40
+ std::vector<nvinfer1::PluginField> fields_;
41
+ nvinfer1::PluginFieldCollection collection_{};
42
+ };
43
+
44
+ // Implements TensorRT V3 lifecycle and the format/serialization boilerplate.
45
+ class PluginBase : public nvinfer1::IPluginV3,
46
+ public nvinfer1::IPluginV3OneCore,
47
+ public nvinfer1::IPluginV3OneBuild,
48
+ public nvinfer1::IPluginV3OneRuntime {
49
+ public:
50
+ PluginBase(char const* name, char const* plugin_namespace, Fields fields);
51
+ ~PluginBase() noexcept override = default;
52
+
53
+ nvinfer1::IPluginCapability* getCapabilityInterface(nvinfer1::PluginCapabilityType type) noexcept override;
54
+ char const* getPluginName() const noexcept override;
55
+ char const* getPluginVersion() const noexcept override;
56
+ char const* getPluginNamespace() const noexcept override;
57
+ std::int32_t getNbOutputs() const noexcept override;
58
+ bool supportsFormatCombination(std::int32_t position,
59
+ nvinfer1::DynamicPluginTensorDesc const* in_out,
60
+ std::int32_t input_count, std::int32_t output_count) noexcept override;
61
+ std::int32_t getOutputDataTypes(nvinfer1::DataType* output_types, std::int32_t output_count,
62
+ nvinfer1::DataType const* input_types, std::int32_t input_count) const noexcept override;
63
+ std::int32_t getOutputShapes(nvinfer1::DimsExprs const* inputs, std::int32_t input_count,
64
+ nvinfer1::DimsExprs const*, std::int32_t shape_input_count,
65
+ nvinfer1::DimsExprs* outputs, std::int32_t output_count,
66
+ nvinfer1::IExprBuilder& builder) noexcept override;
67
+ std::int32_t configurePlugin(nvinfer1::DynamicPluginTensorDesc const* inputs,
68
+ std::int32_t input_count, nvinfer1::DynamicPluginTensorDesc const* outputs,
69
+ std::int32_t output_count) noexcept override;
70
+ std::size_t getWorkspaceSize(nvinfer1::DynamicPluginTensorDesc const*, std::int32_t,
71
+ nvinfer1::DynamicPluginTensorDesc const*, std::int32_t) const noexcept override;
72
+ char const* getTimingCacheID() noexcept override;
73
+ char const* getMetadataString() noexcept override;
74
+ std::int32_t onShapeChange(nvinfer1::PluginTensorDesc const* inputs,
75
+ std::int32_t input_count, nvinfer1::PluginTensorDesc const* outputs,
76
+ std::int32_t output_count) noexcept override;
77
+ nvinfer1::IPluginV3* attachToContext(nvinfer1::IPluginResourceContext*) noexcept override;
78
+ nvinfer1::PluginFieldCollection const* getFieldsToSerialize() noexcept override;
79
+
80
+ protected:
81
+ Fields const& fields() const noexcept { return fields_; }
82
+ virtual std::int32_t validate_shape(nvinfer1::Dims const& input,
83
+ nvinfer1::Dims const& output) const noexcept = 0;
84
+
85
+ private:
86
+ char const* name_;
87
+ char const* namespace_;
88
+ Fields fields_;
89
+ };
90
+
91
+ enum class ArgumentKind : std::uint8_t { kPointer, kInt32, kFloat32 };
92
+
93
+ struct ArgumentRule {
94
+ ArgumentKind kind;
95
+ std::uint32_t divisible_by;
96
+ };
97
+
98
+ struct KernelImage {
99
+ unsigned char const* bytes;
100
+ std::size_t size;
101
+ char const* sha256;
102
+ char const* entry_symbol;
103
+ std::int32_t target_sm;
104
+ std::int32_t registers_per_thread;
105
+ std::int32_t dynamic_shared_bytes;
106
+ std::int32_t global_scratch_bytes;
107
+ std::int32_t profile_scratch_bytes;
108
+ // The producer derives these rules from the manifest's ordered signature.
109
+ std::size_t public_argument_count;
110
+ ArgumentRule const* argument_rules;
111
+ };
112
+
113
+ // Implemented by the producer-generated bundle.cpp linked into a caller's plugin.
114
+ KernelImage const* find_bundle_image(char const* id) noexcept;
115
+
116
+ std::string sha256(void const* bytes, std::size_t size);
117
+
118
+ // Each CUDA context owns its own module; destruction unloads in the owning context.
119
+ class Kernel {
120
+ public:
121
+ explicit Kernel(KernelImage image) noexcept : image_(image) {}
122
+ Kernel(Kernel const&) = delete;
123
+ Kernel& operator=(Kernel const&) = delete;
124
+ ~Kernel() noexcept;
125
+
126
+ // Check the generated public ABI and divisibility rules before choosing a variant.
127
+ bool accepts(void* const* arguments, std::size_t argument_count) const noexcept;
128
+ CUresult launch(unsigned int grid, unsigned int block, void** arguments,
129
+ std::size_t argument_count, CUstream stream) noexcept;
130
+
131
+ private:
132
+ struct Loaded {
133
+ CUmodule module{};
134
+ CUfunction function{};
135
+ };
136
+ CUresult function_for_current_context(CUfunction& function) noexcept;
137
+ KernelImage image_;
138
+ std::mutex mutex_;
139
+ std::map<CUcontext, Loaded> loaded_;
140
+ };
141
+
142
+ class DeviceBytes {
143
+ public:
144
+ explicit DeviceBytes(std::vector<std::uint8_t> bytes) : bytes_(std::move(bytes)) {}
145
+ DeviceBytes(DeviceBytes const&) = delete;
146
+ DeviceBytes& operator=(DeviceBytes const&) = delete;
147
+ ~DeviceBytes() noexcept;
148
+ CUresult for_current_context(CUdeviceptr& pointer) noexcept;
149
+
150
+ private:
151
+ std::vector<std::uint8_t> bytes_;
152
+ std::mutex mutex_;
153
+ std::map<CUcontext, CUdeviceptr> pointers_;
154
+ };
155
+
156
+ } // namespace tensoracle::cubin
@@ -0,0 +1,376 @@
1
+ #include "tensoracle/cubin_plugin.hpp"
2
+
3
+ #include <algorithm>
4
+ #include <array>
5
+ #include <cstring>
6
+ #include <limits>
7
+ #include <utility>
8
+
9
+ namespace tensoracle::cubin {
10
+ namespace {
11
+
12
+ std::size_t field_size(nvinfer1::PluginFieldType type) noexcept
13
+ {
14
+ using nvinfer1::PluginFieldType;
15
+ switch (type) {
16
+ case PluginFieldType::kFLOAT32:
17
+ case PluginFieldType::kINT32: return 4;
18
+ case PluginFieldType::kFLOAT16:
19
+ case PluginFieldType::kINT16: return 2;
20
+ case PluginFieldType::kINT8:
21
+ case PluginFieldType::kCHAR: return 1;
22
+ default: return 0;
23
+ }
24
+ }
25
+
26
+ std::uint32_t rotate_right(std::uint32_t value, unsigned int count) noexcept
27
+ {
28
+ return (value >> count) | (value << (32U - count));
29
+ }
30
+
31
+ template <typename Handle, typename Release>
32
+ void release_in_context(CUcontext context, Handle handle, Release release) noexcept
33
+ {
34
+ if (handle == 0 || context == nullptr) return;
35
+ if (cuCtxPushCurrent(context) == CUDA_SUCCESS) {
36
+ release(handle);
37
+ CUcontext previous{};
38
+ static_cast<void>(cuCtxPopCurrent(&previous));
39
+ }
40
+ // A destroyed CUDA context has already released its allocations and modules.
41
+ }
42
+
43
+ } // namespace
44
+
45
+ bool Fields::parse(nvinfer1::PluginFieldCollection const* source, Fields& result) noexcept
46
+ {
47
+ if (source == nullptr || source->nbFields < 0 ||
48
+ (source->nbFields != 0 && source->fields == nullptr)) return false;
49
+ try {
50
+ Fields parsed;
51
+ parsed.values_.reserve(static_cast<std::size_t>(source->nbFields));
52
+ for (std::int32_t index = 0; index < source->nbFields; ++index) {
53
+ auto const& field = source->fields[index];
54
+ std::size_t const item_size = field_size(field.type);
55
+ if (field.name == nullptr || item_size == 0 || field.length < 0 ||
56
+ (field.length != 0 && field.data == nullptr)) return false;
57
+ if (parsed.find(field.name) != nullptr) return false;
58
+ std::size_t const length = static_cast<std::size_t>(field.length) * item_size;
59
+ if (length > 128U * 1024U * 1024U) return false;
60
+ auto const* begin = static_cast<std::uint8_t const*>(field.data);
61
+ FieldValue value{field.name, field.type, field.length, {}};
62
+ if (length != 0) value.bytes.assign(begin, begin + length);
63
+ parsed.values_.push_back(std::move(value));
64
+ }
65
+ parsed.rebuild();
66
+ result = std::move(parsed);
67
+ return true;
68
+ } catch (...) {
69
+ return false;
70
+ }
71
+ }
72
+
73
+ Fields::Fields(Fields const& other) : values_(other.values_) { rebuild(); }
74
+ Fields& Fields::operator=(Fields const& other)
75
+ {
76
+ if (this != &other) { values_ = other.values_; rebuild(); }
77
+ return *this;
78
+ }
79
+ Fields::Fields(Fields&& other) : values_(std::move(other.values_)) { rebuild(); }
80
+ Fields& Fields::operator=(Fields&& other)
81
+ {
82
+ if (this != &other) { values_ = std::move(other.values_); rebuild(); }
83
+ return *this;
84
+ }
85
+
86
+ FieldValue const* Fields::find(char const* name) const noexcept
87
+ {
88
+ auto found = std::find_if(values_.begin(), values_.end(),
89
+ [name](FieldValue const& value) { return value.name == name; });
90
+ return found == values_.end() ? nullptr : &*found;
91
+ }
92
+
93
+ void Fields::rebuild()
94
+ {
95
+ fields_.clear();
96
+ fields_.reserve(values_.size());
97
+ for (auto& value : values_)
98
+ fields_.emplace_back(value.name.c_str(), value.bytes.data(), value.type, value.length);
99
+ collection_.nbFields = static_cast<std::int32_t>(fields_.size());
100
+ collection_.fields = fields_.data();
101
+ }
102
+
103
+ nvinfer1::PluginFieldCollection const* Fields::collection() noexcept { return &collection_; }
104
+
105
+ PluginBase::PluginBase(char const* name, char const* plugin_namespace, Fields fields)
106
+ : name_(name), namespace_(plugin_namespace), fields_(std::move(fields)) {}
107
+
108
+ nvinfer1::IPluginCapability* PluginBase::getCapabilityInterface(
109
+ nvinfer1::PluginCapabilityType type) noexcept
110
+ {
111
+ using nvinfer1::PluginCapabilityType;
112
+ if (type == PluginCapabilityType::kCORE) return static_cast<nvinfer1::IPluginV3OneCore*>(this);
113
+ if (type == PluginCapabilityType::kBUILD) return static_cast<nvinfer1::IPluginV3OneBuild*>(this);
114
+ if (type == PluginCapabilityType::kRUNTIME) return static_cast<nvinfer1::IPluginV3OneRuntime*>(this);
115
+ return nullptr;
116
+ }
117
+
118
+ char const* PluginBase::getPluginName() const noexcept { return name_; }
119
+ char const* PluginBase::getPluginVersion() const noexcept { return "1"; }
120
+ char const* PluginBase::getPluginNamespace() const noexcept { return namespace_; }
121
+ std::int32_t PluginBase::getNbOutputs() const noexcept { return 1; }
122
+
123
+ bool PluginBase::supportsFormatCombination(std::int32_t position,
124
+ nvinfer1::DynamicPluginTensorDesc const* in_out,
125
+ std::int32_t input_count, std::int32_t output_count) noexcept
126
+ {
127
+ if (in_out == nullptr || input_count != 1 || output_count != 1 || position < 0 || position > 1)
128
+ return false;
129
+ auto const& descriptor = in_out[position].desc;
130
+ return descriptor.type == nvinfer1::DataType::kFLOAT &&
131
+ descriptor.format == nvinfer1::TensorFormat::kLINEAR;
132
+ }
133
+
134
+ std::int32_t PluginBase::getOutputDataTypes(nvinfer1::DataType* output_types,
135
+ std::int32_t output_count, nvinfer1::DataType const* input_types,
136
+ std::int32_t input_count) const noexcept
137
+ {
138
+ if (output_types == nullptr || input_types == nullptr || output_count != 1 || input_count != 1 ||
139
+ input_types[0] != nvinfer1::DataType::kFLOAT) return -1;
140
+ output_types[0] = nvinfer1::DataType::kFLOAT;
141
+ return 0;
142
+ }
143
+
144
+ std::int32_t PluginBase::getOutputShapes(nvinfer1::DimsExprs const* inputs,
145
+ std::int32_t input_count, nvinfer1::DimsExprs const*, std::int32_t shape_input_count,
146
+ nvinfer1::DimsExprs* outputs, std::int32_t output_count,
147
+ nvinfer1::IExprBuilder&) noexcept
148
+ {
149
+ if (inputs == nullptr || outputs == nullptr || input_count != 1 ||
150
+ shape_input_count != 0 || output_count != 1) return -1;
151
+ outputs[0] = inputs[0];
152
+ return 0;
153
+ }
154
+
155
+ std::int32_t PluginBase::configurePlugin(nvinfer1::DynamicPluginTensorDesc const* inputs,
156
+ std::int32_t input_count, nvinfer1::DynamicPluginTensorDesc const* outputs,
157
+ std::int32_t output_count) noexcept
158
+ {
159
+ if (inputs == nullptr || outputs == nullptr || input_count != 1 || output_count != 1) return -1;
160
+ return validate_shape(inputs[0].desc.dims, outputs[0].desc.dims);
161
+ }
162
+
163
+ std::size_t PluginBase::getWorkspaceSize(nvinfer1::DynamicPluginTensorDesc const*, std::int32_t,
164
+ nvinfer1::DynamicPluginTensorDesc const*, std::int32_t) const noexcept { return 0; }
165
+ char const* PluginBase::getTimingCacheID() noexcept { return name_; }
166
+ char const* PluginBase::getMetadataString() noexcept { return name_; }
167
+
168
+ std::int32_t PluginBase::onShapeChange(nvinfer1::PluginTensorDesc const* inputs,
169
+ std::int32_t input_count, nvinfer1::PluginTensorDesc const* outputs,
170
+ std::int32_t output_count) noexcept
171
+ {
172
+ if (inputs == nullptr || outputs == nullptr || input_count != 1 || output_count != 1) return -1;
173
+ return validate_shape(inputs[0].dims, outputs[0].dims);
174
+ }
175
+
176
+ nvinfer1::IPluginV3* PluginBase::attachToContext(nvinfer1::IPluginResourceContext*) noexcept
177
+ {
178
+ return clone();
179
+ }
180
+
181
+ nvinfer1::PluginFieldCollection const* PluginBase::getFieldsToSerialize() noexcept
182
+ {
183
+ return fields_.collection();
184
+ }
185
+
186
+ std::string sha256(void const* bytes, std::size_t size)
187
+ {
188
+ // SHA-256 is kept local so a caller's native plugin has no crypto dependency.
189
+ constexpr std::array<std::uint32_t, 64> constants{{
190
+ 0x428a2f98U,0x71374491U,0xb5c0fbcfU,0xe9b5dba5U,0x3956c25bU,0x59f111f1U,0x923f82a4U,0xab1c5ed5U,
191
+ 0xd807aa98U,0x12835b01U,0x243185beU,0x550c7dc3U,0x72be5d74U,0x80deb1feU,0x9bdc06a7U,0xc19bf174U,
192
+ 0xe49b69c1U,0xefbe4786U,0x0fc19dc6U,0x240ca1ccU,0x2de92c6fU,0x4a7484aaU,0x5cb0a9dcU,0x76f988daU,
193
+ 0x983e5152U,0xa831c66dU,0xb00327c8U,0xbf597fc7U,0xc6e00bf3U,0xd5a79147U,0x06ca6351U,0x14292967U,
194
+ 0x27b70a85U,0x2e1b2138U,0x4d2c6dfcU,0x53380d13U,0x650a7354U,0x766a0abbU,0x81c2c92eU,0x92722c85U,
195
+ 0xa2bfe8a1U,0xa81a664bU,0xc24b8b70U,0xc76c51a3U,0xd192e819U,0xd6990624U,0xf40e3585U,0x106aa070U,
196
+ 0x19a4c116U,0x1e376c08U,0x2748774cU,0x34b0bcb5U,0x391c0cb3U,0x4ed8aa4aU,0x5b9cca4fU,0x682e6ff3U,
197
+ 0x748f82eeU,0x78a5636fU,0x84c87814U,0x8cc70208U,0x90befffaU,0xa4506cebU,0xbef9a3f7U,0xc67178f2U,
198
+ }};
199
+ std::array<std::uint32_t, 8> state{{
200
+ 0x6a09e667U,0xbb67ae85U,0x3c6ef372U,0xa54ff53aU,
201
+ 0x510e527fU,0x9b05688cU,0x1f83d9abU,0x5be0cd19U,
202
+ }};
203
+ auto const* begin = static_cast<std::uint8_t const*>(bytes);
204
+ std::vector<std::uint8_t> message(begin, begin + size);
205
+ message.push_back(0x80U);
206
+ while (message.size() % 64U != 56U) message.push_back(0U);
207
+ auto const bit_count = static_cast<std::uint64_t>(size) * 8U;
208
+ for (int shift = 56; shift >= 0; shift -= 8)
209
+ message.push_back(static_cast<std::uint8_t>((bit_count >> shift) & 0xffU));
210
+ for (std::size_t offset = 0; offset < message.size(); offset += 64U) {
211
+ std::array<std::uint32_t, 64> words{};
212
+ for (std::size_t index = 0; index < 16; ++index) {
213
+ auto const position = offset + index * 4U;
214
+ words[index] = (static_cast<std::uint32_t>(message[position]) << 24U) |
215
+ (static_cast<std::uint32_t>(message[position + 1U]) << 16U) |
216
+ (static_cast<std::uint32_t>(message[position + 2U]) << 8U) |
217
+ message[position + 3U];
218
+ }
219
+ for (std::size_t index = 16; index < 64; ++index) {
220
+ auto const a = words[index - 15U];
221
+ auto const b = words[index - 2U];
222
+ words[index] = words[index - 16U] +
223
+ (rotate_right(a, 7U) ^ rotate_right(a, 18U) ^ (a >> 3U)) +
224
+ words[index - 7U] +
225
+ (rotate_right(b, 17U) ^ rotate_right(b, 19U) ^ (b >> 10U));
226
+ }
227
+ auto a = state[0], b = state[1], c = state[2], d = state[3];
228
+ auto e = state[4], f = state[5], g = state[6], h = state[7];
229
+ for (std::size_t index = 0; index < 64; ++index) {
230
+ auto const s1 = rotate_right(e, 6U) ^ rotate_right(e, 11U) ^ rotate_right(e, 25U);
231
+ auto const choice = (e & f) ^ (~e & g);
232
+ auto const t1 = h + s1 + choice + constants[index] + words[index];
233
+ auto const s0 = rotate_right(a, 2U) ^ rotate_right(a, 13U) ^ rotate_right(a, 22U);
234
+ auto const majority = (a & b) ^ (a & c) ^ (b & c);
235
+ auto const t2 = s0 + majority;
236
+ h = g; g = f; f = e; e = d + t1; d = c; c = b; b = a; a = t1 + t2;
237
+ }
238
+ state[0] += a; state[1] += b; state[2] += c; state[3] += d;
239
+ state[4] += e; state[5] += f; state[6] += g; state[7] += h;
240
+ }
241
+ constexpr char hex[] = "0123456789abcdef";
242
+ std::string result(64U, '0');
243
+ for (std::size_t word = 0; word < 8; ++word)
244
+ for (std::size_t nibble = 0; nibble < 8; ++nibble)
245
+ result[word * 8U + nibble] = hex[(state[word] >> (4U * (7U - nibble))) & 15U];
246
+ return result;
247
+ }
248
+
249
+ Kernel::~Kernel() noexcept
250
+ {
251
+ std::lock_guard<std::mutex> guard(mutex_);
252
+ for (auto const& [context, loaded] : loaded_)
253
+ release_in_context(context, loaded.module, [](CUmodule module) { static_cast<void>(cuModuleUnload(module)); });
254
+ }
255
+
256
+ bool Kernel::accepts(void* const* arguments, std::size_t argument_count) const noexcept
257
+ {
258
+ if (arguments == nullptr || image_.argument_rules == nullptr || argument_count == 0 ||
259
+ argument_count > 14 || argument_count != image_.public_argument_count) return false;
260
+ for (std::size_t index = 0; index < argument_count; ++index) {
261
+ if (arguments[index] == nullptr) return false;
262
+ ArgumentRule const& rule = image_.argument_rules[index];
263
+ std::uint32_t const divisor = rule.divisible_by;
264
+ if (divisor == 0 || (divisor & (divisor - 1U)) != 0) return false;
265
+ switch (rule.kind) {
266
+ case ArgumentKind::kPointer: {
267
+ CUdeviceptr value{};
268
+ std::memcpy(&value, arguments[index], sizeof(value));
269
+ if (divisor > 1 && (value == 0 || value % divisor != 0)) return false;
270
+ break;
271
+ }
272
+ case ArgumentKind::kInt32: {
273
+ std::int32_t value{};
274
+ std::memcpy(&value, arguments[index], sizeof(value));
275
+ if (divisor > 1 && value % static_cast<std::int32_t>(divisor) != 0) return false;
276
+ break;
277
+ }
278
+ case ArgumentKind::kFloat32:
279
+ if (divisor != 1) return false;
280
+ break;
281
+ default: return false;
282
+ }
283
+ }
284
+ return true;
285
+ }
286
+
287
+ CUresult Kernel::function_for_current_context(CUfunction& function) noexcept
288
+ {
289
+ CUcontext context{};
290
+ auto status = cuCtxGetCurrent(&context);
291
+ if (status != CUDA_SUCCESS || context == nullptr) return CUDA_ERROR_INVALID_CONTEXT;
292
+ std::lock_guard<std::mutex> guard(mutex_);
293
+ auto found = loaded_.find(context);
294
+ if (found != loaded_.end()) { function = found->second.function; return CUDA_SUCCESS; }
295
+ if (image_.bytes == nullptr || image_.size == 0 || image_.sha256 == nullptr ||
296
+ image_.entry_symbol == nullptr || image_.global_scratch_bytes != 0 ||
297
+ image_.profile_scratch_bytes != 0) return CUDA_ERROR_INVALID_VALUE;
298
+ try {
299
+ if (sha256(image_.bytes, image_.size) != image_.sha256) return CUDA_ERROR_INVALID_IMAGE;
300
+ } catch (...) { return CUDA_ERROR_OUT_OF_MEMORY; }
301
+ CUdevice device{};
302
+ int major{}, minor{};
303
+ status = cuCtxGetDevice(&device);
304
+ if (status == CUDA_SUCCESS)
305
+ status = cuDeviceGetAttribute(&major, CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MAJOR, device);
306
+ if (status == CUDA_SUCCESS)
307
+ status = cuDeviceGetAttribute(&minor, CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MINOR, device);
308
+ if (status != CUDA_SUCCESS) return status;
309
+ if (major * 10 + minor != image_.target_sm) return CUDA_ERROR_INVALID_IMAGE;
310
+ Loaded loaded{};
311
+ status = cuModuleLoadData(&loaded.module, image_.bytes);
312
+ if (status == CUDA_SUCCESS)
313
+ status = cuModuleGetFunction(&loaded.function, loaded.module, image_.entry_symbol);
314
+ if (status == CUDA_SUCCESS && image_.dynamic_shared_bytes > 0)
315
+ status = cuFuncSetAttribute(loaded.function,
316
+ CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES, image_.dynamic_shared_bytes);
317
+ int registers{};
318
+ if (status == CUDA_SUCCESS)
319
+ status = cuFuncGetAttribute(&registers, CU_FUNC_ATTRIBUTE_NUM_REGS, loaded.function);
320
+ if (status == CUDA_SUCCESS && registers != image_.registers_per_thread)
321
+ status = CUDA_ERROR_INVALID_IMAGE;
322
+ if (status != CUDA_SUCCESS) {
323
+ if (loaded.module != nullptr) static_cast<void>(cuModuleUnload(loaded.module));
324
+ return status;
325
+ }
326
+ function = loaded.function;
327
+ try { loaded_.emplace(context, loaded); }
328
+ catch (...) { static_cast<void>(cuModuleUnload(loaded.module)); return CUDA_ERROR_OUT_OF_MEMORY; }
329
+ return CUDA_SUCCESS;
330
+ }
331
+
332
+ CUresult Kernel::launch(unsigned int grid, unsigned int block,
333
+ void** arguments, std::size_t argument_count, CUstream stream) noexcept
334
+ {
335
+ if (grid == 0 || block == 0 || !accepts(arguments, argument_count))
336
+ return CUDA_ERROR_INVALID_VALUE;
337
+ CUfunction function{};
338
+ auto const status = function_for_current_context(function);
339
+ if (status != CUDA_SUCCESS) return status;
340
+ CUdeviceptr global_scratch{}, profile_scratch{};
341
+ std::array<void*, 16> parameters{};
342
+ std::copy(arguments, arguments + argument_count, parameters.begin());
343
+ parameters[argument_count] = &global_scratch;
344
+ parameters[argument_count + 1] = &profile_scratch;
345
+ return cuLaunchKernel(function, grid, 1U, 1U, block, 1U, 1U,
346
+ static_cast<unsigned int>(image_.dynamic_shared_bytes), stream, parameters.data(), nullptr);
347
+ }
348
+
349
+ DeviceBytes::~DeviceBytes() noexcept
350
+ {
351
+ std::lock_guard<std::mutex> guard(mutex_);
352
+ for (auto const& [context, pointer] : pointers_)
353
+ release_in_context(context, pointer, [](CUdeviceptr value) { static_cast<void>(cuMemFree(value)); });
354
+ }
355
+
356
+ CUresult DeviceBytes::for_current_context(CUdeviceptr& pointer) noexcept
357
+ {
358
+ CUcontext context{};
359
+ auto status = cuCtxGetCurrent(&context);
360
+ if (status != CUDA_SUCCESS || context == nullptr) return CUDA_ERROR_INVALID_CONTEXT;
361
+ std::lock_guard<std::mutex> guard(mutex_);
362
+ auto found = pointers_.find(context);
363
+ if (found != pointers_.end()) { pointer = found->second; return CUDA_SUCCESS; }
364
+ if (bytes_.empty()) return CUDA_ERROR_INVALID_VALUE;
365
+ CUdeviceptr allocated{};
366
+ status = cuMemAlloc(&allocated, bytes_.size());
367
+ if (status != CUDA_SUCCESS) return status;
368
+ status = cuMemcpyHtoD(allocated, bytes_.data(), bytes_.size());
369
+ if (status != CUDA_SUCCESS) { static_cast<void>(cuMemFree(allocated)); return status; }
370
+ try { pointers_.emplace(context, allocated); }
371
+ catch (...) { static_cast<void>(cuMemFree(allocated)); return CUDA_ERROR_OUT_OF_MEMORY; }
372
+ pointer = allocated;
373
+ return CUDA_SUCCESS;
374
+ }
375
+
376
+ } // namespace tensoracle::cubin