Skip to content

Commit 983583d

Browse files
committed
Add TensorRT weight streaming support to the ExecuTorch delegate
Set the weight streaming budget in the delegate init, after the engine is deserialized and before the execution context is created. The budget is carried from export as a weight_streaming_budget compile spec with values auto, disabled, or a byte count. There is no change to the pte format and no change to ExecuTorch. When no budget value is present the runtime makes no budget call, so existing programs behave exactly as before. Ref #4334
1 parent 258a7e2 commit 983583d

11 files changed

Lines changed: 625 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: 65 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,70 @@ 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 decimal int64
290+
// (negative disables streaming, non-negative is an explicit byte budget). When
291+
// no spec is present and the engine supports streaming, we apply TensorRT's
292+
// automatic budget, which is what the PyTorch runtimes do on deserialize.
293+
const CompileSpec* ws_spec = nullptr;
294+
for (const auto& spec : compile_specs) {
295+
if (spec.key != nullptr && std::strcmp(spec.key, kWeightStreamingBudgetKey) == 0) {
296+
if (ws_spec != nullptr) {
297+
// The budget must appear at most once; a second match means the spec
298+
// list is inconsistent, so reject the program instead of guessing.
299+
ET_LOG(Error, "TensorRTBackend::init: duplicate weight_streaming_budget compile spec");
300+
return Error::InvalidProgram;
301+
}
302+
ws_spec = &spec;
303+
}
304+
}
305+
306+
WsBudget ws_request;
307+
if (ws_spec != nullptr) {
308+
ws_request = parse_weight_streaming_budget(ws_spec->value.buffer, ws_spec->value.nbytes);
309+
if (!ws_request.valid) {
310+
ET_LOG(Error, "TensorRTBackend::init: malformed weight_streaming_budget compile spec");
311+
return Error::InvalidProgram;
312+
}
313+
}
314+
315+
const int64_t streamable = handle->engine->getStreamableWeightsSize();
316+
if (streamable > 0) {
317+
// getStreamableWeightsSize is > 0 only when the engine was built with
318+
// BuilderFlag::kWEIGHT_STREAMING.
319+
const bool is_explicit = ws_spec != nullptr;
320+
int64_t budget;
321+
if (is_explicit) {
322+
// An explicit budget is a non-negative byte count, clamped to the
323+
// streamable size (TensorRT also caps it, but clamp for a clear log).
324+
budget = ws_request.bytes > streamable ? streamable : ws_request.bytes;
325+
} else {
326+
budget = handle->engine->getWeightStreamingAutomaticBudget();
327+
}
328+
if (!handle->engine->setWeightStreamingBudgetV2(budget)) {
329+
if (!is_explicit && handle->engine->setWeightStreamingBudgetV2(0)) {
330+
// The automatic budget could not be applied; fall back to maximum
331+
// streaming (budget 0), which always fits.
332+
ET_LOG(Info, "TensorRTBackend::init: automatic weight streaming budget failed; using maximum streaming");
333+
} else {
334+
ET_LOG(Error, "TensorRTBackend::init: setWeightStreamingBudgetV2(%lld) failed", (long long)budget);
335+
return Error::InvalidProgram;
336+
}
337+
}
338+
ET_LOG(
339+
Info,
340+
"TensorRTBackend::init: weight streaming budget=%lld streamable=%lld scratch=%lld",
341+
(long long)handle->engine->getWeightStreamingBudgetV2(),
342+
(long long)streamable,
343+
(long long)handle->engine->getWeightStreamingScratchMemorySize());
344+
} else if (ws_spec != nullptr) {
345+
// A budget was requested but the engine cannot stream. The engine is still
346+
// valid and runs fully resident, so log and continue rather than fail.
347+
ET_LOG(Info, "TensorRTBackend::init: weight_streaming_budget ignored; engine not built with WEIGHT_STREAMING");
348+
}
349+
287350
Error err = initialize_engine_io(*handle);
288351
if (err != Error::Ok) {
289352
return err;
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#include "torch_tensorrt/executorch/WeightStreamingBudget.h"
2+
3+
#include <cerrno>
4+
#include <cstddef>
5+
#include <cstdint>
6+
#include <cstdlib>
7+
#include <cstring>
8+
#include <limits>
9+
10+
namespace torch_tensorrt {
11+
namespace executorch_backend {
12+
13+
WsBudget parse_weight_streaming_budget(const void* value, std::size_t nbytes) {
14+
WsBudget result; // valid == false until the value is fully parsed
15+
16+
// The value is a decimal int64 and is not NUL terminated, so copy it into a
17+
// bounded stack buffer and terminate it ourselves. The widest int64 is 20
18+
// characters plus an optional sign, so anything that does not fit is broken
19+
// rather than truncated. An empty value (nbytes == 0) is rejected: producers
20+
// never emit an empty budget.
21+
constexpr std::size_t kBufSize = 32;
22+
if (value == nullptr || nbytes == 0 || nbytes >= kBufSize) {
23+
return result;
24+
}
25+
char buf[kBufSize];
26+
std::memcpy(buf, value, nbytes);
27+
buf[nbytes] = '\0';
28+
29+
errno = 0;
30+
char* end = nullptr;
31+
const long long parsed = std::strtoll(buf, &end, 10);
32+
// Require the entire value to be one integer with no leftover characters.
33+
// Because the buffer is exactly nbytes long, this single check also rejects an
34+
// embedded NUL (e.g. "12\0x") and any trailing garbage (e.g. "12x" or "12 34").
35+
if (end != buf + nbytes) {
36+
return result;
37+
}
38+
if (errno == ERANGE) {
39+
return result;
40+
}
41+
// The budget is a non-negative byte count. Reject negatives, and on platforms
42+
// where long long is wider than 64 bits reject anything above the int64_t range.
43+
if (parsed < 0 || parsed > std::numeric_limits<int64_t>::max()) {
44+
return result;
45+
}
46+
47+
result.valid = true;
48+
result.bytes = static_cast<int64_t>(parsed);
49+
return result;
50+
}
51+
52+
} // namespace executorch_backend
53+
} // namespace torch_tensorrt

0 commit comments

Comments
 (0)