From ce078f575a794f3dba5e9945f1d75109583569dc Mon Sep 17 00:00:00 2001 From: Siva Teja Patibandla Date: Tue, 27 Oct 2026 17:33:47 -0700 Subject: [PATCH] Give backends the method's metadata at init (#24220) Summary: `BackendInitContext::method_meta()` gives a delegate's `init` the `MethodMeta` of the method being loaded, or null when the context is built without one. `Method::init` passes its own; the view is borrowed and valid only during `init`. With it, a delegate can check the method around it, such as its delegates, its instruction count and whether its inputs and outputs are memory planned, before it accepts options that depend on them. The Core ML delegate uses this later in the stack to accept IOSurface load options only for a method that is just its own call on unplanned inputs and outputs. The context carries the metadata instead of a `has_verified_single_delegate_io()` flag computed by the runtime, so each delegate applies its own rule. Differential Revision: D127716377 --- runtime/backend/backend_init_context.h | 20 ++++++++++-- .../test/backend_init_context_test.cpp | 32 +++++++++++++++++++ runtime/backend/test/targets.bzl | 2 ++ runtime/executor/method.cpp | 4 ++- .../test/backend_integration_test.cpp | 28 ++++++++++++++++ schema/targets.bzl | 3 +- 6 files changed, 85 insertions(+), 4 deletions(-) diff --git a/runtime/backend/backend_init_context.h b/runtime/backend/backend_init_context.h index 0f649b3733f..4c1a5ca2bfb 100644 --- a/runtime/backend/backend_init_context.h +++ b/runtime/backend/backend_init_context.h @@ -25,6 +25,9 @@ namespace executorch { namespace ET_RUNTIME_NAMESPACE { + +class MethodMeta; + /** * BackendInitContext will be used to inject runtime info for to initialize * delegate. @@ -36,7 +39,8 @@ class BackendInitContext final { EventTracer* event_tracer = nullptr, const char* method_name = nullptr, const NamedDataMap* named_data_map = nullptr, - Span runtime_specs = {}) + Span runtime_specs = {}, + const MethodMeta* method_meta = nullptr) : runtime_allocator_(runtime_allocator), #ifdef ET_EVENT_TRACER_ENABLED event_tracer_(event_tracer), @@ -45,7 +49,8 @@ class BackendInitContext final { #endif method_name_(method_name), named_data_map_(named_data_map), - runtime_specs_(runtime_specs) { + runtime_specs_(runtime_specs), + method_meta_(method_meta) { } /** Get the runtime allocator passed from Method. It's the same runtime @@ -94,6 +99,16 @@ class BackendInitContext final { return runtime_specs_; } + /** + * Returns metadata for the enclosing method, or nullptr if not supplied. + * The runtime provides a borrowed view valid during backend initialization. + * Other callers must keep the view and its backing program alive for the + * lifetime of this context. + */ + const MethodMeta* method_meta() const { + return method_meta_; + } + /** * Get a runtime spec value by key and type. * @@ -135,6 +150,7 @@ class BackendInitContext final { const char* method_name_ = nullptr; const NamedDataMap* named_data_map_ = nullptr; Span runtime_specs_; + const MethodMeta* method_meta_ = nullptr; }; } // namespace ET_RUNTIME_NAMESPACE diff --git a/runtime/backend/test/backend_init_context_test.cpp b/runtime/backend/test/backend_init_context_test.cpp index 8d74b9361fc..25e6c5f055e 100644 --- a/runtime/backend/test/backend_init_context_test.cpp +++ b/runtime/backend/test/backend_init_context_test.cpp @@ -8,7 +8,12 @@ #include #include +#include #include +#include + +#include +#include #include @@ -17,6 +22,7 @@ using executorch::runtime::BackendInitContext; using executorch::runtime::BackendOption; using executorch::runtime::BackendOptions; using executorch::runtime::Error; +using executorch::runtime::MethodMeta; using executorch::runtime::Span; class BackendInitContextTest : public ::testing::Test { @@ -32,6 +38,32 @@ TEST_F(BackendInitContextTest, DefaultConstructorNoRuntimeSpecs) { auto specs = context.runtime_specs(); EXPECT_EQ(specs.size(), 0); + EXPECT_EQ(context.method_meta(), nullptr); +} + +TEST_F(BackendInitContextTest, ConstructorPreservesMethodMeta) { + namespace fb = executorch_flatbuffer; + flatbuffers::FlatBufferBuilder builder; + const auto plan = fb::CreateExecutionPlan( + builder, + builder.CreateString("forward"), + /*container_meta_type=*/0, + builder.CreateVector(std::vector>{}), + builder.CreateVector(std::vector{}), + builder.CreateVector(std::vector{}), + /*chains=*/0, + /*operators=*/0, + builder.CreateVector( + std::vector>{})); + builder.Finish(plan); + const auto meta = MethodMeta::from_serialized_execution_plan( + builder.GetBufferPointer(), builder.GetSize()); + ASSERT_TRUE(meta.ok()); + + BackendInitContext context( + nullptr, nullptr, "forward", nullptr, {}, &meta.get()); + + EXPECT_EQ(context.method_meta(), &meta.get()); } // Test constructor with runtime specs diff --git a/runtime/backend/test/targets.bzl b/runtime/backend/test/targets.bzl index 5ce89cba9d0..0859cdddc86 100644 --- a/runtime/backend/test/targets.bzl +++ b/runtime/backend/test/targets.bzl @@ -31,6 +31,8 @@ def define_common_targets(): deps = [ "//executorch/runtime/core:core", "//executorch/runtime/backend:interface", + "//executorch/runtime/executor:program", + "//executorch/schema:program", ], ) diff --git a/runtime/executor/method.cpp b/runtime/executor/method.cpp index 9b8b9e34008..5f6d75d13a6 100644 --- a/runtime/executor/method.cpp +++ b/runtime/executor/method.cpp @@ -1019,6 +1019,7 @@ Error Method::init( // makes it safe for errors to return without updating any state. n_delegate_ = 0; + const auto method_meta = this->method_meta(); for (size_t i = 0; i < n_delegate; ++i) { const auto& delegate = *delegates->Get(i); @@ -1035,7 +1036,8 @@ Error Method::init( /*event_tracer=*/event_tracer_, /*method_name=*/serialization_plan_->name()->c_str(), /*named_data_map=*/named_data_map, - /*runtime_specs=*/delegate_runtime_specs); + /*runtime_specs=*/delegate_runtime_specs, + /*method_meta=*/&method_meta); Error err = BackendDelegate::Init( delegate, program_, backend_init_context, &delegates_[i]); if (err != Error::Ok) { diff --git a/runtime/executor/test/backend_integration_test.cpp b/runtime/executor/test/backend_integration_test.cpp index e395b23bf2d..594f7070120 100644 --- a/runtime/executor/test/backend_integration_test.cpp +++ b/runtime/executor/test/backend_integration_test.cpp @@ -610,6 +610,34 @@ TEST_P(BackendIntegrationTest, GetMethodNameDuringInitSuccess) { ASSERT_EQ(program.error(), Error::Ok); } +TEST_P(BackendIntegrationTest, MethodMetaPassedToBackendInitContext) { + Result loader = FileDataLoader::from(program_path()); + ASSERT_EQ(loader.error(), Error::Ok); + Result program = Program::load(&loader.get()); + ASSERT_EQ(program.error(), Error::Ok); + + bool init_called = false; + StubBackend::singleton().install_init( + [&](FreeableBuffer* processed, + ET_UNUSED ArrayRef compile_specs, + BackendInitContext& context) -> Result { + init_called = true; + const auto* meta = context.method_meta(); + EXPECT_NE(meta, nullptr); + if (meta != nullptr) { + EXPECT_STREQ(meta->name(), "forward"); + EXPECT_TRUE(meta->uses_backend(StubBackend::kName)); + } + processed->Free(); + return nullptr; + }); + + ManagedMemoryManager mmm(kDefaultNonConstMemBytes, kDefaultRuntimeMemBytes); + Result method = program->load_method("forward", &mmm.get()); + EXPECT_TRUE(method.ok()); + EXPECT_TRUE(init_called); +} + TEST_P(BackendIntegrationTest, GetMethodNameDuringExecuteSuccess) { Result loader = FileDataLoader::from(program_path()); ASSERT_EQ(loader.error(), Error::Ok); diff --git a/schema/targets.bzl b/schema/targets.bzl index 0528f54536d..7f8d66f7a42 100644 --- a/schema/targets.bzl +++ b/schema/targets.bzl @@ -80,9 +80,10 @@ def define_common_targets(): "//executorch/runtime/executor/...", "//executorch/backends/native/extension/module/...", # Tests have a set up which uses raw flatbuffer. - # TODO will refactor these setup steps into + # TODO will refactor these setup steps into # testing utils in runtime/executor/... path "//executorch/backends/xnnpack/test/...", + "//executorch/runtime/backend/test/...", ], exported_headers = { OUTPUT_PROGRAM_HEADER: ":{}[{}]".format(PROGRAM_GEN_RULE_NAME, OUTPUT_PROGRAM_HEADER),