From 85f4302c4bf1d796ef40d9c3506b08f3aa1d58a5 Mon Sep 17 00:00:00 2001 From: Stephen Jia Date: Mon, 24 Aug 2026 11:42:13 -0700 Subject: [PATCH] Update [ghstack-poisoned] --- backends/native/runtime/graph/ScalarType.h | 96 ++++++++++++++++++++ backends/native/runtime/graph/TensorMeta.cpp | 75 +++++++++++++++ backends/native/runtime/graph/TensorMeta.h | 77 ++++++++++++++++ backends/native/runtime/graph/targets.bzl | 22 +++++ 4 files changed, 270 insertions(+) create mode 100644 backends/native/runtime/graph/ScalarType.h create mode 100644 backends/native/runtime/graph/TensorMeta.cpp create mode 100644 backends/native/runtime/graph/TensorMeta.h diff --git a/backends/native/runtime/graph/ScalarType.h b/backends/native/runtime/graph/ScalarType.h new file mode 100644 index 00000000000..3987094274b --- /dev/null +++ b/backends/native/runtime/graph/ScalarType.h @@ -0,0 +1,96 @@ +// 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 + +namespace ptn { + +// X-macro table of scalar types: (CPP_TYPE, NAME, ID). One row per supported +// element type; the row drives the enum, the k constants, the +// ScalarType -> C++ type trait, element_size(), and scalar_type_name(). +// +// IDs are pinned to ExecuTorch's ScalarType (runtime/core/portable_type/ +// scalar_type.h) and the native_graph.fbs ScalarType enum, so a deserializer +// maps the serialized byte straight to this enum. The ids are therefore NOT +// sequential (complex / quantized-int ids are reserved and omitted). +// +// Half and BFloat16 have no standalone C++ 16-bit-float type in this +// dependency-free header; they map to uint16_t as a raw storage stand-in, which +// is correct for size / layout purposes. +#define PTN_FORALL_SCALAR_TYPES(_) \ + _(uint8_t, Byte, 0) \ + _(int8_t, Char, 1) \ + _(int16_t, Short, 2) \ + _(int32_t, Int, 3) \ + _(int64_t, Long, 4) \ + _(uint16_t, Half, 5) \ + _(float, Float, 6) \ + _(double, Double, 7) \ + _(bool, Bool, 11) \ + _(uint16_t, BFloat16, 15) \ + _(uint16_t, UInt16, 16) \ + _(uint32_t, UInt32, 17) \ + _(uint64_t, UInt64, 18) + +enum class ScalarType : int8_t { +#define PTN_DEFINE_ENUM(cpp_type, name, id) name = id, + PTN_FORALL_SCALAR_TYPES(PTN_DEFINE_ENUM) +#undef PTN_DEFINE_ENUM +}; + +// Shorthand constants: kFloat, kLong, ... +#define PTN_DEFINE_CONSTANT(cpp_type, name, id) \ + constexpr ScalarType k##name = ScalarType::name; +PTN_FORALL_SCALAR_TYPES(PTN_DEFINE_CONSTANT) +#undef PTN_DEFINE_CONSTANT + +// ScalarType -> C++ type. Use as `ptn::cpp_type_t` (== float). +// Forward mapping only: a reverse C++-type -> ScalarType trait is +// intentionally omitted, since uint16_t would collide across Half / BFloat16 / +// UInt16. +template +struct ScalarTypeToCppType; +#define PTN_SPECIALIZE_S2C(cpp_type, name, id) \ + template <> \ + struct ScalarTypeToCppType { \ + using type = cpp_type; \ + }; +PTN_FORALL_SCALAR_TYPES(PTN_SPECIALIZE_S2C) +#undef PTN_SPECIALIZE_S2C + +template +using cpp_type_t = typename ScalarTypeToCppType::type; + +// Size in bytes of one element. Throws std::runtime_error on an unrecognized +// value (e.g. a bad cast from an out-of-range serialized byte). +inline size_t element_size(ScalarType t) { + switch (t) { +#define PTN_CASE_ELEMSIZE(cpp_type, name, id) \ + case ScalarType::name: \ + return sizeof(cpp_type); + PTN_FORALL_SCALAR_TYPES(PTN_CASE_ELEMSIZE) +#undef PTN_CASE_ELEMSIZE + } + throw std::runtime_error("element_size: unrecognized ScalarType"); +} + +// Human-readable enumerator name (e.g. "Float"). Throws on unrecognized value. +inline const char* scalar_type_name(ScalarType t) { + switch (t) { +#define PTN_CASE_NAME(cpp_type, name, id) \ + case ScalarType::name: \ + return #name; + PTN_FORALL_SCALAR_TYPES(PTN_CASE_NAME) +#undef PTN_CASE_NAME + } + throw std::runtime_error("scalar_type_name: unrecognized ScalarType"); +} + +} // namespace ptn diff --git a/backends/native/runtime/graph/TensorMeta.cpp b/backends/native/runtime/graph/TensorMeta.cpp new file mode 100644 index 00000000000..5a347241a2a --- /dev/null +++ b/backends/native/runtime/graph/TensorMeta.cpp @@ -0,0 +1,75 @@ +// 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 +#include +#include + +namespace ptn { + +Dim::Dim(int64_t min_v, int64_t max_v) : min(min_v), max(max_v) { + if (min_v < 0 || (max_v >= 0 && max_v < min_v)) { + throw std::runtime_error( + "Dim: no shape has the range " + std::to_string(min_v) + ".." + + std::to_string(max_v)); + } +} + +bool TensorMeta::is_static() const { + return std::ranges::all_of(sizes, &Dim::is_static); +} + +bool TensorMeta::is_contiguous() const { + if (dim_order_hint.empty()) { + return true; + } + // A length mismatch makes this unequal, so it needs no separate check. + return std::ranges::equal( + dim_order_hint, + std::views::iota(int32_t{0}, static_cast(sizes.size()))); +} + +int64_t TensorMeta::numel() const { + int64_t n = 1; + for (const Dim& d : sizes) { + const int64_t extent = d.is_static() ? d.min : d.max; + if (extent < 0) { + throw std::runtime_error("TensorMeta::numel: unbounded dynamic dim"); + } + // Signed overflow is UB, so the product has to be checked before it + // happens: a malformed shape must not silently plan a smaller buffer. + if (extent != 0 && n > std::numeric_limits::max() / extent) { + throw std::runtime_error("TensorMeta::numel: element count overflows"); + } + n *= extent; + } + return n; +} + +std::string TensorMeta::to_string() const { + std::string s = scalar_type_name(dtype); + s += "["; + for (size_t i = 0; i < sizes.size(); ++i) { + if (i != 0) { + s += ","; + } + const Dim& d = sizes[i]; + if (d.is_static()) { + s += std::to_string(d.min); + } else if (d.max < 0) { + s += std::to_string(d.min) + "..?"; + } else { + s += std::to_string(d.min) + ".." + std::to_string(d.max); + } + } + s += "]"; + return s; +} + +} // namespace ptn diff --git a/backends/native/runtime/graph/TensorMeta.h b/backends/native/runtime/graph/TensorMeta.h new file mode 100644 index 00000000000..06bf832046a --- /dev/null +++ b/backends/native/runtime/graph/TensorMeta.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 { + +// One tensor dimension as an inclusive range. Static: min == max. Dynamic: +// min < max, or max < 0 for unbounded. Memory is planned from the upper bound. +struct Dim { + int64_t min = 0; + int64_t max = -1; + + Dim() = default; + // A static dimension: min == max == extent. Implicit so a shape can be + // written as a plain int list, e.g. sizes = {16, 8}. + // cppcheck-suppress noExplicitConstructor + /* implicit */ Dim(int64_t extent) : Dim(extent, extent) {} + // A range [min_v, max_v] (dynamic when min_v < max_v; max_v < 0 unbounded). + // Throws std::runtime_error on a range no shape can have: a negative lower + // bound, or a bounded upper bound below it. This catches a malformed + // serialized shape at the point it enters the IR rather than letting it + // surface as a wrong numel() later. It is a funnel, not an invariant -- + // min / max stay public and assignable. + Dim(int64_t min_v, int64_t max_v); + + bool is_static() const { + return min == max; + } + + bool operator==(const Dim&) const = default; +}; + +// Logical tensor metadata: element type and per-dim size ranges. No storage and +// no quant scheme (deferred). dim_order_hint is a *suggested* memory layout — a +// permutation of dim indices, outermost first; empty means contiguous +// ([0, 1, ..., n-1]). It is advisory only: engines choose their own physical +// layout and may ignore it. TensorMeta stays non-prescriptive about layout. +struct TensorMeta { + ScalarType dtype = ScalarType::Float; + std::vector sizes; + std::vector dim_order_hint; + + size_t ndim() const { + return sizes.size(); + } + + // True if every dimension is static (min == max). + bool is_static() const; + + // True if dim_order_hint is empty or the identity permutation + // [0, 1, ..., n-1] (i.e. the hint suggests a contiguous layout). + bool is_contiguous() const; + + // Element count using each dim's upper bound (its size when static). This is + // the memory-planning extent. Throws std::runtime_error on an unbounded + // dynamic dim (max < 0), which has no finite element count. + int64_t numel() const; + + // e.g. "Float[16,16]" (static), "Float[1..8,16]" (bounded dynamic), or + // "Float[0..?,16]" (unbounded). Debug aid. + std::string to_string() const; + + bool operator==(const TensorMeta&) const = default; +}; + +} // namespace ptn diff --git a/backends/native/runtime/graph/targets.bzl b/backends/native/runtime/graph/targets.bzl index 306231ec0b4..33a14571450 100644 --- a/backends/native/runtime/graph/targets.bzl +++ b/backends/native/runtime/graph/targets.bzl @@ -9,3 +9,25 @@ def define_common_targets(): ], visibility = ["//executorch/backends/native/..."], ) + + # Scalar element type + C++-type mapping (header-only; macro-driven, standalone). + runtime.cxx_library( + name = "scalar_type", + exported_headers = [ + "ScalarType.h", + ], + visibility = ["//executorch/backends/native/..."], + ) + + # Concrete in-memory IR value types (pure std; no ExecuTorch, no flatbuffers). + runtime.cxx_library( + name = "tensor_meta", + srcs = ["TensorMeta.cpp"], + exported_headers = [ + "TensorMeta.h", + ], + exported_deps = [ + ":scalar_type", + ], + visibility = ["//executorch/backends/native/..."], + )