|
| 1 | +#include "core/runtime/RuntimeSettings.h" |
| 2 | + |
| 3 | +#include <array> |
| 4 | +#include <cstring> |
| 5 | +#include <iterator> |
| 6 | +#include <sstream> |
| 7 | +#include <tuple> |
| 8 | +#include <type_traits> |
| 9 | + |
| 10 | +// ``at::empty`` (factory function) lives in ``ATen/Functions.h``. Some bundled |
| 11 | +// torch builds (e.g. torch_l4t on Jetpack) only ship the minimal |
| 12 | +// ``ATen/core/Tensor.h`` transitively, so include the full ATen surface here. |
| 13 | +#include "ATen/ATen.h" |
| 14 | +#include "core/util/prelude.h" |
| 15 | + |
| 16 | +namespace torch_tensorrt { |
| 17 | +namespace core { |
| 18 | +namespace runtime { |
| 19 | + |
| 20 | +namespace { |
| 21 | + |
| 22 | +// Reverse-lookup tables. Indices match the enum integer values (which mirror |
| 23 | +// the nvinfer1 enums). Out-of-range -> "<unknown>". |
| 24 | +constexpr std::array<std::string_view, 3> kDsStrategyNames = {"lazy", "eager", "none"}; |
| 25 | +constexpr std::array<std::string_view, 2> kCgStrategyNames = {"disabled", "whole_graph_capture"}; |
| 26 | + |
| 27 | +// "a|b|c" from {"a", "b", "c"}. Used to render the accepted-names tail of the |
| 28 | +// validator error messages. |
| 29 | +template <size_t N> |
| 30 | +std::string join_string_views(std::string_view sep, std::array<std::string_view, N> const& parts) { |
| 31 | + if (N == 0) { |
| 32 | + return {}; |
| 33 | + } |
| 34 | + std::ostringstream os; |
| 35 | + os << parts.front(); |
| 36 | + for (auto it = std::next(std::cbegin(parts)); it != std::cend(parts); ++it) { |
| 37 | + os << sep << *it; |
| 38 | + } |
| 39 | + return os.str(); |
| 40 | +} |
| 41 | + |
| 42 | +// "(expected 0..N-1 mapping to a|b|c)" -- the common error-message tail. |
| 43 | +template <size_t N> |
| 44 | +std::string format_expected_strategy(std::array<std::string_view, N> const& names) { |
| 45 | + std::ostringstream os; |
| 46 | + os << "(expected 0.." << (N - 1) << " mapping to " << join_string_views("|", names) << ')'; |
| 47 | + return os.str(); |
| 48 | +} |
| 49 | + |
| 50 | +// "(expected a|b|c)" -- name-only variant for ``from_string`` errors. |
| 51 | +template <size_t N> |
| 52 | +std::string format_expected_name(std::array<std::string_view, N> const& names) { |
| 53 | + std::ostringstream os; |
| 54 | + os << "(expected " << join_string_views("|", names) << ')'; |
| 55 | + return os.str(); |
| 56 | +} |
| 57 | + |
| 58 | +} // namespace |
| 59 | + |
| 60 | +// ---- DynamicShapesKernelSpecializationStrategy ----------------------------- |
| 61 | + |
| 62 | +std::string_view DynamicShapesKernelSpecializationStrategy::to_string() const noexcept { |
| 63 | + // Negative underlying values wrap to a huge ``size_t``, so a single bounds |
| 64 | + // check from the top covers both ends without needing ``std::clamp``. |
| 65 | + auto const i = static_cast<size_t>(v_); |
| 66 | + return i < std::size(kDsStrategyNames) ? kDsStrategyNames[i] : std::string_view{"<unknown>"}; |
| 67 | +} |
| 68 | + |
| 69 | +DynamicShapesKernelSpecializationStrategy DynamicShapesKernelSpecializationStrategy::from_underlying(int64_t v) { |
| 70 | + TORCHTRT_CHECK( |
| 71 | + v >= 0 && static_cast<size_t>(v) < std::size(kDsStrategyNames), |
| 72 | + "Invalid dynamic_shapes_kernel_specialization_strategy int: " << v << " " |
| 73 | + << format_expected_strategy(kDsStrategyNames)); |
| 74 | + return DynamicShapesKernelSpecializationStrategy(static_cast<Value>(v)); |
| 75 | +} |
| 76 | + |
| 77 | +DynamicShapesKernelSpecializationStrategy DynamicShapesKernelSpecializationStrategy::from_string( |
| 78 | + std::string_view name) { |
| 79 | + for (size_t i = 0; i < std::size(kDsStrategyNames); ++i) { |
| 80 | + if (kDsStrategyNames[i] == name) { |
| 81 | + return DynamicShapesKernelSpecializationStrategy(static_cast<Value>(i)); |
| 82 | + } |
| 83 | + } |
| 84 | + TORCHTRT_CHECK( |
| 85 | + false, |
| 86 | + "Invalid dynamic_shapes_kernel_specialization_strategy name: " << name << " " |
| 87 | + << format_expected_name(kDsStrategyNames)); |
| 88 | +} |
| 89 | + |
| 90 | +// ---- CudaGraphStrategy ----------------------------------------------------- |
| 91 | + |
| 92 | +std::string_view CudaGraphStrategy::to_string() const noexcept { |
| 93 | + auto const i = static_cast<size_t>(v_); |
| 94 | + return i < std::size(kCgStrategyNames) ? kCgStrategyNames[i] : std::string_view{"<unknown>"}; |
| 95 | +} |
| 96 | + |
| 97 | +CudaGraphStrategy CudaGraphStrategy::from_underlying(int64_t v) { |
| 98 | + TORCHTRT_CHECK( |
| 99 | + v >= 0 && static_cast<size_t>(v) < std::size(kCgStrategyNames), |
| 100 | + "Invalid cuda_graph_strategy int: " << v << " " << format_expected_strategy(kCgStrategyNames)); |
| 101 | + return CudaGraphStrategy(static_cast<Value>(v)); |
| 102 | +} |
| 103 | + |
| 104 | +CudaGraphStrategy CudaGraphStrategy::from_string(std::string_view name) { |
| 105 | + for (size_t i = 0; i < std::size(kCgStrategyNames); ++i) { |
| 106 | + if (kCgStrategyNames[i] == name) { |
| 107 | + return CudaGraphStrategy(static_cast<Value>(i)); |
| 108 | + } |
| 109 | + } |
| 110 | + TORCHTRT_CHECK(false, "Invalid cuda_graph_strategy name: " << name << " " << format_expected_name(kCgStrategyNames)); |
| 111 | +} |
| 112 | + |
| 113 | +// ---- RuntimeCacheHandle methods --------------------------------------------- |
| 114 | +// |
| 115 | +// The ``#ifdef TRT_MAJOR_RTX`` is intentionally confined to this translation |
| 116 | +// unit: the public header advertises a uniform interface (always-callable |
| 117 | +// methods that simply degrade to no-ops on non-RTX builds), and the JIT-binding |
| 118 | +// registration file (``register_jit_hooks.cpp``) calls these as plain member |
| 119 | +// references with zero conditional compilation. |
| 120 | + |
| 121 | +at::Tensor RuntimeCacheHandle::serialize() const { |
| 122 | + auto const opts = at::TensorOptions().dtype(at::kByte); |
| 123 | + auto const empty = [&]() { return at::empty({0}, opts); }; |
| 124 | +#ifdef TRT_MAJOR_RTX |
| 125 | + std::lock_guard<std::mutex> lock(state_mu_); |
| 126 | + if (!trt_handle_) { |
| 127 | + LOG_WARNING( |
| 128 | + "RuntimeCacheHandle::serialize() called before the IRuntimeCache was materialized; returning empty bytes."); |
| 129 | + return empty(); |
| 130 | + } |
| 131 | + auto host_mem = make_trt(trt_handle_->serialize()); |
| 132 | + TORCHTRT_CHECK(host_mem, "IRuntimeCache::serialize() returned null host memory; cannot serialize cache bytes."); |
| 133 | + auto tensor = at::empty({static_cast<int64_t>(host_mem->size())}, opts); |
| 134 | + std::memcpy(tensor.data_ptr(), host_mem->data(), host_mem->size()); |
| 135 | + return tensor; |
| 136 | +#else |
| 137 | + LOG_WARNING("RuntimeCacheHandle::serialize() invoked on a non-RTX build; returning empty bytes."); |
| 138 | + return empty(); |
| 139 | +#endif |
| 140 | +} |
| 141 | + |
| 142 | +void RuntimeCacheHandle::deserialize(TORCHTRT_UNUSED at::Tensor data) { |
| 143 | +#ifdef TRT_MAJOR_RTX |
| 144 | + if (data.numel() == 0) { |
| 145 | + LOG_WARNING("RuntimeCacheHandle::deserialize() called with an empty tensor; nothing to load."); |
| 146 | + return; |
| 147 | + } |
| 148 | + auto contig = data.contiguous().to(at::kCPU); |
| 149 | + std::lock_guard<std::mutex> lock(state_mu_); |
| 150 | + if (trt_handle_) { |
| 151 | + // Live cache -- write through directly. |
| 152 | + trt_handle_->deserialize(contig.data_ptr(), static_cast<size_t>(contig.numel())); |
| 153 | + } else { |
| 154 | + // No live cache yet -- stash bytes for the next ``ensure_materialized`` |
| 155 | + // call to drain. Fixes the cpp-rt warm-start path where ``wrapper.load()`` |
| 156 | + // fires before any engine has materialized ``trt_handle_``. |
| 157 | + auto const* p = static_cast<uint8_t const*>(contig.data_ptr()); |
| 158 | + pending_warm_bytes_.assign(p, p + contig.numel()); |
| 159 | + } |
| 160 | +#else |
| 161 | + LOG_WARNING("RuntimeCacheHandle::deserialize() invoked on a non-RTX build; bytes ignored."); |
| 162 | +#endif |
| 163 | +} |
| 164 | + |
| 165 | +bool RuntimeCacheHandle::has_cache() const { |
| 166 | +#ifdef TRT_MAJOR_RTX |
| 167 | + std::lock_guard<std::mutex> lock(state_mu_); |
| 168 | + return trt_handle_ != nullptr; |
| 169 | +#else |
| 170 | + return false; |
| 171 | +#endif |
| 172 | +} |
| 173 | + |
| 174 | +#ifdef TRT_MAJOR_RTX |
| 175 | +std::shared_ptr<nvinfer1::IRuntimeCache> RuntimeCacheHandle::ensure_materialized(nvinfer1::IRuntimeConfig* config) { |
| 176 | + std::lock_guard<std::mutex> lock(state_mu_); |
| 177 | + if (!trt_handle_) { |
| 178 | + trt_handle_ = make_trt(config->createRuntimeCache()); |
| 179 | + if (!trt_handle_) { |
| 180 | + return nullptr; |
| 181 | + } |
| 182 | + // Drain any bytes that ``deserialize`` stashed pre-materialization. |
| 183 | + // This is the cpp-rt warm-start path: ``load()`` fired before the first |
| 184 | + // ``ensure_materialized``, bytes parked in ``pending_warm_bytes_``, |
| 185 | + // first engine to ``ensure_materialized`` creates the cache and drains. |
| 186 | + if (!std::empty(pending_warm_bytes_)) { |
| 187 | + trt_handle_->deserialize(pending_warm_bytes_.data(), std::size(pending_warm_bytes_)); |
| 188 | + pending_warm_bytes_.clear(); |
| 189 | + pending_warm_bytes_.shrink_to_fit(); |
| 190 | + LOG_DEBUG("Drained pending warm-start bytes into IRuntimeCache."); |
| 191 | + } |
| 192 | + } |
| 193 | + return trt_handle_; |
| 194 | +} |
| 195 | +#endif |
| 196 | + |
| 197 | +// ---- RuntimeSettings methods ------------------------------------------------ |
| 198 | + |
| 199 | +bool RuntimeSettings::operator==(RuntimeSettings const& other) const noexcept { |
| 200 | + // ``runtime_cache`` compares by pointer identity: passing the same handle |
| 201 | + // twice through the settings setter is a no-op. Hoisted into locals because |
| 202 | + // ``std::tie`` requires lvalues. |
| 203 | + auto* this_cache = runtime_cache.get(); |
| 204 | + auto* other_cache = other.runtime_cache.get(); |
| 205 | + return std::tie(dynamic_shapes_kernel_specialization_strategy, cuda_graph_strategy, this_cache) == |
| 206 | + std::tie(other.dynamic_shapes_kernel_specialization_strategy, other.cuda_graph_strategy, other_cache); |
| 207 | +} |
| 208 | + |
| 209 | +std::string RuntimeSettings::to_str() const { |
| 210 | + std::ostringstream os; |
| 211 | + os << "RuntimeSettings{" << std::endl; |
| 212 | + os << " Dynamic Shapes Kernel Strategy: " << dynamic_shapes_kernel_specialization_strategy << std::endl; |
| 213 | + os << " CUDA Graph Strategy: " << cuda_graph_strategy << std::endl; |
| 214 | + if (runtime_cache) { |
| 215 | + auto const& p = runtime_cache->path; |
| 216 | + os << " Runtime Cache: " << (p.empty() ? "<in-memory shared>" : p) << std::endl; |
| 217 | + } else { |
| 218 | + os << " Runtime Cache: <engine-local, in-memory>" << std::endl; |
| 219 | + } |
| 220 | + os << "}"; |
| 221 | + return os.str(); |
| 222 | +} |
| 223 | + |
| 224 | +std::ostream& operator<<(std::ostream& os, RuntimeSettings const& rs) { |
| 225 | + os << rs.to_str(); |
| 226 | + return os; |
| 227 | +} |
| 228 | + |
| 229 | +} // namespace runtime |
| 230 | +} // namespace core |
| 231 | +} // namespace torch_tensorrt |
0 commit comments