diff --git a/backends/native/runtime/graph/Node.cpp b/backends/native/runtime/graph/Node.cpp new file mode 100644 index 00000000000..00500562ccd --- /dev/null +++ b/backends/native/runtime/graph/Node.cpp @@ -0,0 +1,202 @@ +// 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 + +#include + +namespace ptn { + +namespace { + +std::string ref_str(ValueRef ref) { + return valid(ref) ? "%" + std::to_string(ref) : "None"; +} + +// Compact one-line rendering of an argument for Node::to_string. Symbolic +// scalars (a valid ref) render as the ref; literals render as their value. +std::string arg_str(const Argument& arg) { + switch (arg.kind()) { + case ArgKind::None: + return "None"; + case ArgKind::Tensor: + return ref_str(arg.as_tensor().ref); + case ArgKind::Int: { + const IntArg& a = arg.as_int(); + return valid(a.ref) ? ref_str(a.ref) : std::to_string(a.value); + } + case ArgKind::Float: { + const FloatArg& a = arg.as_float(); + return valid(a.ref) ? ref_str(a.ref) : format_double(a.value); + } + case ArgKind::Bool: { + const BoolArg& a = arg.as_bool(); + return valid(a.ref) ? ref_str(a.ref) : (a.value ? "true" : "false"); + } + case ArgKind::String: + return "\"" + arg.as_string().value + "\""; + case ArgKind::ScalarType: + return scalar_type_name(arg.as_scalar_type().value); + case ArgKind::IntList: { + const IntListArg& a = arg.as_int_list(); + std::string s = "["; + for (size_t i = 0; i < a.values.size(); ++i) { + if (i) { + s += ", "; + } + const bool sym = i < a.refs.size() && valid(a.refs[i]); + s += sym ? ref_str(a.refs[i]) : std::to_string(a.values[i]); + } + return s + "]"; + } + case ArgKind::FloatList: { + const FloatListArg& a = arg.as_float_list(); + std::string s = "["; + for (size_t i = 0; i < a.values.size(); ++i) { + if (i) { + s += ", "; + } + s += format_double(a.values[i]); + } + return s + "]"; + } + case ArgKind::BoolList: { + const BoolListArg& a = arg.as_bool_list(); + std::string s = "["; + for (size_t i = 0; i < a.values.size(); ++i) { + if (i) { + s += ", "; + } + s += a.values[i] ? "true" : "false"; + } + return s + "]"; + } + case ArgKind::TensorList: + case ArgKind::OptionalTensorList: { + const std::vector& refs = arg.kind() == ArgKind::TensorList + ? arg.as_tensor_list().refs + : arg.as_optional_tensor_list().refs; + std::string s = "["; + for (size_t i = 0; i < refs.size(); ++i) { + if (i) { + s += ", "; + } + s += ref_str(refs[i]); + } + return s + "]"; + } + case ArgKind::Graph: + return "graph(" + arg.as_graph().name + ")"; + } + return "?"; +} + +std::string output_str(const Output& out) { + if (out.kind == OutputValueKind::TensorList) { + std::string s = "["; + for (size_t i = 0; i < out.elem_refs.size(); ++i) { + if (i) { + s += ", "; + } + s += ref_str(out.elem_refs[i]); + } + return s + "]"; + } + return ref_str(out.value_ref); +} + +// Append `ref` to `refs` unless it is kInvalid (an unwired / literal operand). +void push_ref(std::vector& refs, ValueRef ref) { + if (valid(ref)) { + refs.push_back(ref); + } +} + +} // namespace + +std::vector Node::input_value_refs() const { + std::vector refs; + for (const NamedArgument& named : inputs) { + const Argument& arg = named.arg; + switch (arg.kind()) { + case ArgKind::Tensor: + push_ref(refs, arg.as_tensor().ref); + break; + case ArgKind::Int: + push_ref(refs, arg.as_int().ref); + break; + case ArgKind::Float: + push_ref(refs, arg.as_float().ref); + break; + case ArgKind::Bool: + push_ref(refs, arg.as_bool().ref); + break; + case ArgKind::IntList: + for (ValueRef r : arg.as_int_list().refs) { + push_ref(refs, r); + } + break; + case ArgKind::TensorList: + for (ValueRef r : arg.as_tensor_list().refs) { + push_ref(refs, r); + } + break; + case ArgKind::OptionalTensorList: + for (ValueRef r : arg.as_optional_tensor_list().refs) { + push_ref(refs, r); + } + break; + default: + break; // None / String / ScalarType / Float|BoolList / Graph: no refs + } + } + return refs; +} + +std::string Node::to_string() const { + std::string s = name.empty() ? "_" : name; + s += " = "; + switch (op_kind) { + case OpKind::CallFunction: + s += target; + break; + case OpKind::Placeholder: + s += ""; + break; + case OpKind::Output: + s += ""; + break; + } + s += "("; + for (size_t i = 0; i < inputs.size(); ++i) { + if (i) { + s += ", "; + } + const NamedArgument& named = inputs[i]; + if (!named.name.empty()) { + s += named.name + "="; + } + s += arg_str(named.arg); + if (named.mutated) { + s += "!"; + } + } + s += ")"; + if (!outputs.empty()) { + s += " -> "; + for (size_t i = 0; i < outputs.size(); ++i) { + if (i) { + s += ", "; + } + s += output_str(outputs[i]); + } + } + return s; +} + +} // namespace ptn diff --git a/backends/native/runtime/graph/Node.h b/backends/native/runtime/graph/Node.h new file mode 100644 index 00000000000..dc89ab334b0 --- /dev/null +++ b/backends/native/runtime/graph/Node.h @@ -0,0 +1,82 @@ +// 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 + +#include +#include + +namespace ptn { + +// fx node kind (node.op). Pinned to the schema OpKind ids. +enum class OpKind : int8_t { + CallFunction = 0, + Placeholder = 1, + Output = 2, +}; + +// What a single node Output produces. Pinned to the schema OutputValueKind ids; +// named to match the schema (distinct from the graph-level OutputKind — user +// output vs buffer mutation — that lands with Method). +enum class OutputValueKind : int8_t { + Tensor = 0, + TensorList = 1, + Int = 2, + Bool = 3, + Float = 4, +}; + +// One value produced by a node. Tensor / Int / Bool / Float use `value_ref`; +// TensorList (e.g. split) uses `elem_refs`. The return-ABI grouping is +// preserved so engine translation can tell a single result from a tuple / list +// (topk emits two Tensor outputs; split emits one TensorList output). The +// storage-alias fact lives on the produced Value, not here. +struct Output { + OutputValueKind kind = OutputValueKind::Tensor; + ValueRef value_ref = kInvalid; + std::vector elem_refs; +}; + +// One fx node: an op invocation (CallFunction) or a graph-boundary marker +// (Placeholder / Output). For an Output node, `inputs` is the ordered return +// list (tensors and literals alike) and `outputs` is empty; for a Placeholder, +// `target` is empty and it produces a single output. `attrs` is a transient +// scratch map (the fx node.meta analog); it is not serialized. +struct Node { + std::string name; + OpKind op_kind = OpKind::CallFunction; + // fqn, e.g. "torch.ops.aten.addmm.default"; empty for placeholder / output. + std::string target; + std::vector inputs; + std::vector outputs; + std::unordered_map attrs; + + bool is_call() const { + return op_kind == OpKind::CallFunction; + } + bool is_placeholder() const { + return op_kind == OpKind::Placeholder; + } + bool is_output() const { + return op_kind == OpKind::Output; + } + + // Every ValueRef this node consumes: tensor args, tensor-list / optional-list + // elements, and symbolic scalar refs (kInvalid entries skipped). Used to + // (re)build def-use wiring. + std::vector input_value_refs() const; + + // Single-line debug rendering, e.g. "a = aten.add.Tensor(x, y, alpha=1)". + std::string to_string() const; +}; + +} // namespace ptn diff --git a/backends/native/runtime/graph/targets.bzl b/backends/native/runtime/graph/targets.bzl index 15196df7d51..0b54abeb91f 100644 --- a/backends/native/runtime/graph/targets.bzl +++ b/backends/native/runtime/graph/targets.bzl @@ -82,3 +82,19 @@ def define_common_targets(): ], visibility = ["//executorch/backends/native/..."], ) + + # One fx node: an op invocation or graph-boundary marker, with typed Outputs + # preserving the op return ABI (single / tuple / tensor-list / scalar). + runtime.cxx_library( + name = "node", + srcs = ["Node.cpp"], + exported_headers = [ + "Node.h", + ], + exported_deps = [ + ":argument", + ":ids", + ], + deps = [":format"], + visibility = ["//executorch/backends/native/..."], + )