Skip to content

Commit 053e902

Browse files
committed
Add TensorRT weight streaming support to the ExecuTorch delegate
Apply a TensorRT weight streaming budget in the delegate init(), after the engine is deserialized and before the execution context is created (the budget cannot be changed once a context exists). When the engine was built with weight streaming, the delegate applies TensorRT's automatic budget by default, mirroring the PyTorch runtimes. An explicit non-negative byte budget can be set via torch_tensorrt.save(output_format="executorch", weight_streaming_budget=N) and is carried as an ExecuTorch CompileSpec. Non-streamable engines make no budget call, so existing programs are unchanged. The one intended behavior change is that a streamable engine now applies the automatic budget on load, which enables running models whose weights exceed GPU memory. Ref #4334
1 parent 258a7e2 commit 053e902

11 files changed

Lines changed: 611 additions & 32 deletions

File tree

cpp/BUILD

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,17 @@ cc_library(
9696
strip_include_prefix = "include",
9797
)
9898

99+
cc_library(
100+
name = "tensorrt_executorch_weight_streaming_budget",
101+
srcs = [
102+
"src/torch_tensorrt/executorch/WeightStreamingBudget.cpp",
103+
],
104+
hdrs = [
105+
"include/torch_tensorrt/executorch/WeightStreamingBudget.h",
106+
],
107+
strip_include_prefix = "include",
108+
)
109+
99110
cc_library(
100111
name = "tensorrt_executorch_binding_names",
101112
hdrs = [
@@ -127,6 +138,7 @@ cc_library(
127138
deps = [
128139
":tensorrt_executorch_binding_names",
129140
":tensorrt_executorch_blob_header",
141+
":tensorrt_executorch_weight_streaming_budget",
130142
] + select({
131143
":linux_x86_64": [
132144
"@executorch//:executorch_headers",
@@ -150,6 +162,7 @@ filegroup(
150162
"src/torch_tensorrt/executorch/README.md",
151163
"src/torch_tensorrt/executorch/TensorRTBackend.cpp",
152164
"src/torch_tensorrt/executorch/TensorRTBlobHeader.cpp",
165+
"src/torch_tensorrt/executorch/WeightStreamingBudget.cpp",
153166
],
154167
)
155168

@@ -169,5 +182,6 @@ filegroup(
169182
"include/torch_tensorrt/executorch/TensorRTBackend.h",
170183
"include/torch_tensorrt/executorch/TensorRTBindingNames.h",
171184
"include/torch_tensorrt/executorch/TensorRTBlobHeader.h",
185+
"include/torch_tensorrt/executorch/WeightStreamingBudget.h",
172186
],
173187
)
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#pragma once
2+
3+
#include <cstddef>
4+
#include <cstdint>
5+
6+
namespace torch_tensorrt {
7+
namespace executorch_backend {
8+
9+
// Compile spec key that carries the weight streaming budget from export into the
10+
// delegate. Must match WEIGHT_STREAMING_BUDGET_COMPILE_SPEC_KEY on the Python
11+
// side (py/torch_tensorrt/executorch/partitioner.py).
12+
inline constexpr char kWeightStreamingBudgetKey[] = "weight_streaming_budget";
13+
14+
// Result of parsing a weight streaming budget compile spec value.
15+
//
16+
// The value is a non-negative decimal integer: an explicit GPU budget in bytes.
17+
// The automatic budget is intentionally not encoded here: when no budget spec is
18+
// present, the delegate applies TensorRT's automatic budget itself, mirroring the
19+
// PyTorch runtimes.
20+
//
21+
// valid == false -> a value was present but could not be parsed (or was
22+
// negative); the caller should reject the program.
23+
// valid == true -> bytes holds the parsed non-negative byte budget.
24+
struct WsBudget {
25+
bool valid = false;
26+
int64_t bytes = 0;
27+
};
28+
29+
// Parses a budget from a raw byte range. The value is NOT assumed to be NUL
30+
// terminated; only the first nbytes bytes are read.
31+
WsBudget parse_weight_streaming_budget(const void* value, std::size_t nbytes);
32+
33+
} // namespace executorch_backend
34+
} // namespace torch_tensorrt

cpp/src/torch_tensorrt/executorch/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ find_package(Threads REQUIRED)
2323
set(_torchtrt_executorch_sources
2424
"${CMAKE_CURRENT_LIST_DIR}/TensorRTBackend.cpp"
2525
"${CMAKE_CURRENT_LIST_DIR}/TensorRTBlobHeader.cpp"
26+
"${CMAKE_CURRENT_LIST_DIR}/WeightStreamingBudget.cpp"
2627
)
2728

2829
add_library(executorch_trt_backend STATIC ${_torchtrt_executorch_sources})

cpp/src/torch_tensorrt/executorch/TensorRTBackend.cpp

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "torch_tensorrt/executorch/TensorRTBackend.h"
99
#include "torch_tensorrt/executorch/TensorRTBindingNames.h"
1010
#include "torch_tensorrt/executorch/TensorRTBlobHeader.h"
11+
#include "torch_tensorrt/executorch/WeightStreamingBudget.h"
1112

1213
#include <cstdint>
1314
#include <cstring>
@@ -225,8 +226,6 @@ Result<DelegateHandle*> TensorRTBackend::init(
225226
BackendInitContext& context,
226227
FreeableBuffer* processed,
227228
ArrayRef<CompileSpec> compile_specs) const {
228-
(void)compile_specs;
229-
230229
TORCHTRT_ET_CHECK_NOT_NULL(processed, Error::InvalidArgument, "TensorRTBackend::init: null processed buffer");
231230
TORCHTRT_ET_CHECK_NOT_NULL(processed->data(), Error::InvalidArgument, "TensorRTBackend::init: null processed buffer");
232231

@@ -284,6 +283,71 @@ Result<DelegateHandle*> TensorRTBackend::init(
284283
TORCHTRT_ET_CHECK_NOT_NULL(
285284
handle->engine, Error::InvalidProgram, "TensorRTBackend::init: failed to deserialize TensorRT engine");
286285

286+
// Apply the weight streaming budget. This must run before the execution
287+
// context is created below: TensorRT forbids changing the budget while a
288+
// context is active. The budget is carried from export as an optional
289+
// "weight_streaming_budget" compile spec whose value is a non-negative decimal
290+
// byte budget; negative or malformed values are rejected as InvalidProgram.
291+
// When no spec is present and the engine supports streaming, we apply
292+
// TensorRT's automatic budget, which is what the PyTorch runtimes do on
293+
// deserialize.
294+
const CompileSpec* ws_spec = nullptr;
295+
for (const auto& spec : compile_specs) {
296+
if (spec.key != nullptr && std::strcmp(spec.key, kWeightStreamingBudgetKey) == 0) {
297+
if (ws_spec != nullptr) {
298+
// The budget must appear at most once; a second match means the spec
299+
// list is inconsistent, so reject the program instead of guessing.
300+
ET_LOG(Error, "TensorRTBackend::init: duplicate weight_streaming_budget compile spec");
301+
return Error::InvalidProgram;
302+
}
303+
ws_spec = &spec;
304+
}
305+
}
306+
307+
WsBudget ws_request;
308+
if (ws_spec != nullptr) {
309+
ws_request = parse_weight_streaming_budget(ws_spec->value.buffer, ws_spec->value.nbytes);
310+
if (!ws_request.valid) {
311+
ET_LOG(Error, "TensorRTBackend::init: malformed weight_streaming_budget compile spec");
312+
return Error::InvalidProgram;
313+
}
314+
}
315+
316+
const int64_t streamable = handle->engine->getStreamableWeightsSize();
317+
if (streamable > 0) {
318+
// getStreamableWeightsSize is > 0 only when the engine was built with
319+
// BuilderFlag::kWEIGHT_STREAMING.
320+
const bool is_explicit = ws_spec != nullptr;
321+
int64_t budget;
322+
if (is_explicit) {
323+
// An explicit budget is a non-negative byte count, clamped to the
324+
// streamable size (TensorRT also caps it, but clamp for a clear log).
325+
budget = ws_request.bytes > streamable ? streamable : ws_request.bytes;
326+
} else {
327+
budget = handle->engine->getWeightStreamingAutomaticBudget();
328+
}
329+
if (!handle->engine->setWeightStreamingBudgetV2(budget)) {
330+
if (!is_explicit && handle->engine->setWeightStreamingBudgetV2(0)) {
331+
// The automatic budget could not be applied; fall back to maximum
332+
// streaming (budget 0), which always fits.
333+
ET_LOG(Info, "TensorRTBackend::init: automatic weight streaming budget failed; using maximum streaming");
334+
} else {
335+
ET_LOG(Error, "TensorRTBackend::init: setWeightStreamingBudgetV2(%lld) failed", (long long)budget);
336+
return Error::InvalidProgram;
337+
}
338+
}
339+
ET_LOG(
340+
Info,
341+
"TensorRTBackend::init: weight streaming budget=%lld streamable=%lld scratch=%lld",
342+
(long long)handle->engine->getWeightStreamingBudgetV2(),
343+
(long long)streamable,
344+
(long long)handle->engine->getWeightStreamingScratchMemorySize());
345+
} else if (ws_spec != nullptr) {
346+
// A budget was requested but the engine cannot stream. The engine is still
347+
// valid and runs fully resident, so log and continue rather than fail.
348+
ET_LOG(Info, "TensorRTBackend::init: weight_streaming_budget ignored; engine was not built with enable_weight_streaming=True");
349+
}
350+
287351
Error err = initialize_engine_io(*handle);
288352
if (err != Error::Ok) {
289353
return err;
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#include "torch_tensorrt/executorch/WeightStreamingBudget.h"
2+
3+
#include <charconv>
4+
#include <cstddef>
5+
#include <cstdint>
6+
#include <system_error>
7+
8+
namespace torch_tensorrt {
9+
namespace executorch_backend {
10+
11+
WsBudget parse_weight_streaming_budget(const void* value, std::size_t nbytes) {
12+
WsBudget result; // valid == false until the value is fully parsed
13+
14+
if (value == nullptr || nbytes == 0) {
15+
return result;
16+
}
17+
// The value is a non-negative decimal byte budget and is not NUL terminated.
18+
// std::from_chars consumes only ASCII digits (no leading whitespace, sign, or
19+
// base prefix), so a leftover byte (trailing garbage or an embedded NUL), an
20+
// out-of-range value, or a negative leaves the result invalid.
21+
const char* const first = static_cast<const char*>(value);
22+
const char* const last = first + nbytes;
23+
int64_t parsed = 0;
24+
const std::from_chars_result fc = std::from_chars(first, last, parsed);
25+
if (fc.ec != std::errc() || fc.ptr != last || parsed < 0) {
26+
return result;
27+
}
28+
29+
result.valid = true;
30+
result.bytes = parsed;
31+
return result;
32+
}
33+
34+
} // namespace executorch_backend
35+
} // namespace torch_tensorrt

0 commit comments

Comments
 (0)