diff --git a/backends/native/runtime/Method.cpp b/backends/native/runtime/Method.cpp new file mode 100644 index 00000000000..f9ff79a4a99 --- /dev/null +++ b/backends/native/runtime/Method.cpp @@ -0,0 +1,61 @@ +// Copyright (c) Meta Platforms, Inc. and affiliates. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. + +#include + +#include + +namespace ptn { + +namespace { + +const char* output_kind_name(OutputKind kind) { + switch (kind) { + case OutputKind::UserOutput: + return "UserOutput"; + case OutputKind::BufferMutation: + return "BufferMutation"; + case OutputKind::UserInputMutation: + return "UserInputMutation"; + } + return "?"; +} + +} // namespace + +std::string Method::to_string() const { + std::string s = "method " + name + "\n"; + s += graph.to_string(); + + s += "data_bindings: ["; + for (size_t i = 0; i < data_bindings.size(); ++i) { + if (i) { + s += ", "; + } + const DataBinding& b = data_bindings[i]; + s += "%" + std::to_string(b.value_ref) + "=" + b.key + "(" + + value_role_name(b.role) + (b.has_data ? "" : ",zero_init") + + (b.mutated ? ",mutated" : "") + ")"; + } + s += "]\n"; + + s += "output_specs: ["; + for (size_t i = 0; i < output_specs.size(); ++i) { + if (i) { + s += ", "; + } + const OutputSpec& o = output_specs[i]; + s += output_kind_name(o.kind); + if (valid(o.target_ref)) { + s += "(%" + std::to_string(o.target_ref) + ")"; + } + } + s += "]\n"; + + return s; +} + +} // namespace ptn diff --git a/backends/native/runtime/Method.h b/backends/native/runtime/Method.h new file mode 100644 index 00000000000..0b57608af2e --- /dev/null +++ b/backends/native/runtime/Method.h @@ -0,0 +1,77 @@ +// Copyright (c) Meta Platforms, Inc. and affiliates. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. + +#pragma once + +#include +#include + +#include +#include +#include + +namespace ptn { + +// Classifies a graph-level output value (schema OutputKind). USER_OUTPUT is a +// real result; the mutation kinds mark a value that writes back into a +// persistent buffer or a user input. Distinct from the node-level +// OutputValueKind (tensor / list / scalar). +enum class OutputKind : int8_t { + UserOutput = 0, + BufferMutation = 1, + UserInputMutation = 2, +}; + +// Binds a method-graph placeholder value to its external storage. Unifies the +// schema's NamedTensorRef (constant-backed: parameter / frozen constant / +// persistent buffer — data shipped under `key`) and MutableBufferSpec +// (non-persistent buffer — no data, zero-initialized at load, e.g. a KV cache). +// `key` is the namespace-3 fully-qualified name: an external-constant-file key +// when `has_data`, else the buffer's cross-method identity. `has_data` selects +// the load path (fetch bytes vs zero-init). `mutated` (schema +// NamedTensorRef.mutated) marks a Buffer written in place, whose state persists +// across executions — parameters / frozen constants / read-only buffers are +// false. `role` mirrors the bound Value's role (kept here too for direct access +// when iterating bindings). Tensor metadata is not duplicated — it lives on the +// bound Value. +struct DataBinding { + ValueRef value_ref = kInvalid; + ValueRole role = ValueRole::Parameter; // Parameter / Buffer / ConstantTensor + // Namespace-3 fqn: the constant-file key if has_data, else the buffer's + // cross-method identity. + std::string key; + bool has_data = true; // bytes shipped under key, else zero-init at load + bool mutated = false; // written in place; state persists across executions +}; + +// Per graph-output classification (schema OutputSpec), aligned to +// graph.output_refs by index. `target_ref` references the mutated placeholder +// value in this method graph — the user input (UserInputMutation) or the buffer +// (BufferMutation), both of which are lifted placeholders here; kInvalid for +// UserOutput. The wire strings are recovered from that Value: its `name` (SSA, +// namespace 2) and, for a buffer, its DataBinding's `key` (fqn, namespace 3). +struct OutputSpec { + OutputKind kind = OutputKind::UserOutput; + ValueRef target_ref = kInvalid; +}; + +// A named method: one top-level pure Graph plus its stateful signature +// bindings. `data_bindings` is the authoritative placeholder→storage table +// (merging the schema's constants + mutable_buffers); the deserializer also +// stamps each bound placeholder Value's role / data_key for O(1) per-value +// queries. HOP subgraphs (inside graph.subgraphs) carry no bindings — their +// params are lifted here and passed as operands. +struct Method { + std::string name; + Graph graph; + std::vector data_bindings; + std::vector output_specs; // aligned to graph.output_refs by index + + // Multi-line debug dump: name, the graph, and the binding tables. + std::string to_string() const; +}; + +} // namespace ptn diff --git a/backends/native/runtime/graph/Value.h b/backends/native/runtime/graph/Value.h index 746ae583ae0..408926bd3e8 100644 --- a/backends/native/runtime/graph/Value.h +++ b/backends/native/runtime/graph/Value.h @@ -28,6 +28,37 @@ enum class ValueKind : int8_t { List = 3, }; +// How a Value's storage is owned / sourced (a method-binding fact stamped by +// the deserializer). Intermediate: produced by a node (a pooled activation). +// The rest classify a placeholder: UserInput (caller-wired each call); +// Parameter / ConstantTensor (frozen external data, located by +// Value::data_key); Buffer (persistent state when data_key is set, else +// zero-initialized at load). Mirrors the schema InputKind, plus Intermediate +// for non-placeholder values. +enum class ValueRole : int8_t { + Intermediate = 0, + UserInput = 1, + Parameter = 2, + Buffer = 3, + ConstantTensor = 4, +}; + +inline const char* value_role_name(ValueRole role) { + switch (role) { + case ValueRole::Intermediate: + return "Intermediate"; + case ValueRole::UserInput: + return "UserInput"; + case ValueRole::Parameter: + return "Parameter"; + case ValueRole::Buffer: + return "Buffer"; + case ValueRole::ConstantTensor: + return "ConstantTensor"; + } + return "?"; +} + // A single SSA value (dataflow edge) in a Graph: its contents plus def-use // wiring, a storage-alias fact, and an open attrs map. // @@ -55,6 +86,12 @@ class Value { std::vector consumer_refs; // Shares storage with this value (a view); fresh if invalid. ValueRef alias_ref = kInvalid; + // Storage ownership / source class, stamped by the deserializer from the + // Method bindings. + ValueRole role = ValueRole::Intermediate; + // External-constant key for Parameter / ConstantTensor / persistent Buffer; + // empty otherwise. + std::string data_key; // Scratch + planner annotations. std::unordered_map attrs; diff --git a/backends/native/runtime/targets.bzl b/backends/native/runtime/targets.bzl index e6b810f845f..7b61f83f44a 100644 --- a/backends/native/runtime/targets.bzl +++ b/backends/native/runtime/targets.bzl @@ -57,3 +57,20 @@ def define_common_targets(): ], visibility = ["PUBLIC"], ) + + # A named method: one top-level Graph plus its stateful signature bindings + # (data bindings + output specs). Sits at the Program level (peer to the reader), + # above the graph/ arena package. + runtime.cxx_library( + name = "method", + srcs = ["Method.cpp"], + exported_headers = [ + "Method.h", + ], + exported_deps = [ + "//executorch/backends/native/runtime/graph:graph", + "//executorch/backends/native/runtime/graph:ids", + "//executorch/backends/native/runtime/graph:value", + ], + visibility = ["//executorch/backends/native/..."], + )