|
8 | 8 | #include "torch_tensorrt/executorch/TensorRTBackend.h" |
9 | 9 | #include "torch_tensorrt/executorch/TensorRTBindingNames.h" |
10 | 10 | #include "torch_tensorrt/executorch/TensorRTBlobHeader.h" |
| 11 | +#include "torch_tensorrt/executorch/WeightStreamingBudget.h" |
11 | 12 |
|
12 | 13 | #include <cstdint> |
13 | 14 | #include <cstring> |
@@ -225,8 +226,6 @@ Result<DelegateHandle*> TensorRTBackend::init( |
225 | 226 | BackendInitContext& context, |
226 | 227 | FreeableBuffer* processed, |
227 | 228 | ArrayRef<CompileSpec> compile_specs) const { |
228 | | - (void)compile_specs; |
229 | | - |
230 | 229 | TORCHTRT_ET_CHECK_NOT_NULL(processed, Error::InvalidArgument, "TensorRTBackend::init: null processed buffer"); |
231 | 230 | TORCHTRT_ET_CHECK_NOT_NULL(processed->data(), Error::InvalidArgument, "TensorRTBackend::init: null processed buffer"); |
232 | 231 |
|
@@ -284,6 +283,130 @@ Result<DelegateHandle*> TensorRTBackend::init( |
284 | 283 | TORCHTRT_ET_CHECK_NOT_NULL( |
285 | 284 | handle->engine, Error::InvalidProgram, "TensorRTBackend::init: failed to deserialize TensorRT engine"); |
286 | 285 |
|
| 286 | + // Apply the weight streaming budget before the execution context is created |
| 287 | + // below: TensorRT forbids changing the budget while a context is active. The |
| 288 | + // budget is a non-negative decimal byte count and may come from two places, in |
| 289 | + // order of precedence: |
| 290 | + // 1. A load-time backend option ("weight_streaming_budget" runtime spec) that |
| 291 | + // the caller passes to Module::load(LoadBackendOptionsMap). This lets a |
| 292 | + // deployment size the budget for its own GPU without re-exporting. |
| 293 | + // 2. The same key baked into the .pte as a compile spec at export, used as a |
| 294 | + // default when no load-time option is given (and the only channel for |
| 295 | + // loaders that cannot pass backend options yet, e.g. Python/Android). |
| 296 | + // When neither is present and the engine supports streaming, we apply |
| 297 | + // TensorRT's automatic budget, mirroring what the PyTorch runtimes do on |
| 298 | + // deserialize. Negative or malformed values are rejected as InvalidProgram. |
| 299 | + WsBudget ws_request; |
| 300 | + bool is_explicit = false; |
| 301 | + |
| 302 | + // (1) A load-time runtime spec takes precedence over the baked compile spec. |
| 303 | + // The value is a decimal byte string; a non-negative int is also accepted for |
| 304 | + // small budgets. A present-but-wrong-type or empty option is handled explicitly |
| 305 | + // so a runtime option is never silently dropped. The const char* returned by |
| 306 | + // get_runtime_spec points into the caller's LoadBackendOptionsMap storage, which |
| 307 | + // outlives init(); we parse it immediately and keep only the int64 result. |
| 308 | + const auto ws_runtime = context.get_runtime_spec<const char*>(kWeightStreamingBudgetKey); |
| 309 | + if (ws_runtime.ok()) { |
| 310 | + const char* const value = ws_runtime.get(); |
| 311 | + // The option array need not be NUL terminated (the struct is public), so |
| 312 | + // bound the scan. An empty value means "unset", so fall through to (2). |
| 313 | + constexpr std::size_t kRuntimeBudgetMaxScan = 256; |
| 314 | + std::size_t len = 0; |
| 315 | + if (value != nullptr) { |
| 316 | + while (len < kRuntimeBudgetMaxScan && value[len] != '\0') { |
| 317 | + ++len; |
| 318 | + } |
| 319 | + } |
| 320 | + if (len > 0) { |
| 321 | + ws_request = parse_weight_streaming_budget(value, len); |
| 322 | + if (!ws_request.valid) { |
| 323 | + ET_LOG(Error, "TensorRTBackend::init: malformed weight_streaming_budget runtime option"); |
| 324 | + return Error::InvalidProgram; |
| 325 | + } |
| 326 | + is_explicit = true; |
| 327 | + } |
| 328 | + } else if (ws_runtime.error() != Error::NotFound) { |
| 329 | + // The key is present but stored as a non-string type. Accept a non-negative |
| 330 | + // int for convenience (its 32-bit range only covers budgets under 2 GB); |
| 331 | + // otherwise reject it so a wrong-typed option is never silently ignored. |
| 332 | + const auto ws_runtime_int = context.get_runtime_spec<int>(kWeightStreamingBudgetKey); |
| 333 | + if (ws_runtime_int.ok() && ws_runtime_int.get() >= 0) { |
| 334 | + ws_request.valid = true; |
| 335 | + ws_request.bytes = ws_runtime_int.get(); |
| 336 | + is_explicit = true; |
| 337 | + } else { |
| 338 | + ET_LOG( |
| 339 | + Error, |
| 340 | + "TensorRTBackend::init: weight_streaming_budget runtime option must be a " |
| 341 | + "non-negative int or a decimal byte string"); |
| 342 | + return Error::InvalidProgram; |
| 343 | + } |
| 344 | + } |
| 345 | + |
| 346 | + // (2) Otherwise fall back to the compile spec baked into the .pte at export. |
| 347 | + if (!is_explicit) { |
| 348 | + const CompileSpec* ws_spec = nullptr; |
| 349 | + for (const auto& spec : compile_specs) { |
| 350 | + if (spec.key != nullptr && std::strcmp(spec.key, kWeightStreamingBudgetKey) == 0) { |
| 351 | + if (ws_spec != nullptr) { |
| 352 | + // The budget must appear at most once; a second match means the spec |
| 353 | + // list is inconsistent, so reject the program instead of guessing. |
| 354 | + ET_LOG(Error, "TensorRTBackend::init: duplicate weight_streaming_budget compile spec"); |
| 355 | + return Error::InvalidProgram; |
| 356 | + } |
| 357 | + ws_spec = &spec; |
| 358 | + } |
| 359 | + } |
| 360 | + if (ws_spec != nullptr) { |
| 361 | + ws_request = parse_weight_streaming_budget(ws_spec->value.buffer, ws_spec->value.nbytes); |
| 362 | + if (!ws_request.valid) { |
| 363 | + ET_LOG(Error, "TensorRTBackend::init: malformed weight_streaming_budget compile spec"); |
| 364 | + return Error::InvalidProgram; |
| 365 | + } |
| 366 | + is_explicit = true; |
| 367 | + } |
| 368 | + } |
| 369 | + |
| 370 | + const int64_t streamable = handle->engine->getStreamableWeightsSize(); |
| 371 | + if (streamable > 0) { |
| 372 | + // getStreamableWeightsSize is > 0 only when the engine was built with |
| 373 | + // BuilderFlag::kWEIGHT_STREAMING. |
| 374 | + int64_t budget; |
| 375 | + if (is_explicit) { |
| 376 | + // An explicit budget is a non-negative byte count, clamped to the |
| 377 | + // streamable size (TensorRT also caps it, but clamp for a clear log). |
| 378 | + budget = ws_request.bytes > streamable ? streamable : ws_request.bytes; |
| 379 | + } else { |
| 380 | + budget = handle->engine->getWeightStreamingAutomaticBudget(); |
| 381 | + } |
| 382 | + if (!handle->engine->setWeightStreamingBudgetV2(budget)) { |
| 383 | + if (!is_explicit && handle->engine->setWeightStreamingBudgetV2(0)) { |
| 384 | + // The automatic budget could not be applied; fall back to budget 0, which |
| 385 | + // streams all weights (minimum resident memory) and always fits. |
| 386 | + ET_LOG(Info, "TensorRTBackend::init: automatic weight streaming budget failed; falling back to budget 0 (stream all weights)"); |
| 387 | + } else { |
| 388 | + ET_LOG( |
| 389 | + Error, |
| 390 | + "TensorRTBackend::init: setWeightStreamingBudgetV2 failed (requested=%lld%s)", |
| 391 | + (long long)budget, |
| 392 | + is_explicit ? "" : ", and fallback to 0 also failed"); |
| 393 | + return Error::InvalidProgram; |
| 394 | + } |
| 395 | + } |
| 396 | + ET_LOG( |
| 397 | + Info, |
| 398 | + "TensorRTBackend::init: weight streaming budget=%lld streamable=%lld scratch=%lld", |
| 399 | + (long long)handle->engine->getWeightStreamingBudgetV2(), |
| 400 | + (long long)streamable, |
| 401 | + (long long)handle->engine->getWeightStreamingScratchMemorySize()); |
| 402 | + } else if (is_explicit) { |
| 403 | + // A budget was requested but the engine has no streamable weights (it was not |
| 404 | + // built with enable_weight_streaming=True, or nothing is streamable). The |
| 405 | + // engine is still valid and runs fully resident, so log and continue rather |
| 406 | + // than fail; failing here would break mixed multi-engine programs. |
| 407 | + ET_LOG(Info, "TensorRTBackend::init: weight_streaming_budget ignored; engine does not support weight streaming (build with enable_weight_streaming=True)"); |
| 408 | + } |
| 409 | + |
287 | 410 | Error err = initialize_engine_io(*handle); |
288 | 411 | if (err != Error::Ok) { |
289 | 412 | return err; |
|
0 commit comments