// Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // The file has been adapted from pytorch project // Licensed under BSD-style license - // https://github.com/pytorch/pytorch/blob/main/LICENSE #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ATen/core/function_schema.h" #include "paddle/common/macros.h" // For macro PADDLE_API #include "torch/csrc/jit/function_schema_parser.h" namespace torch { class Library; class FunctionArgs; class FunctionResult; struct arg { explicit arg(std::string name) : name_(std::move(name)), value_(std::nullopt) {} arg& operator=(const IValue& rhs) { value_ = rhs; return *this; } arg& operator=(IValue&& rhs) { value_ = std::move(rhs); return *this; } template < typename T, typename = std::enable_if_t, IValue> && !std::is_same_v, arg>>> arg& operator=(T&& rhs) { if constexpr (std::is_same_v, const char*> || (std::is_array_v> && std::is_same_v>, char>)) { value_ = torch::IValue(std::string(rhs)); } else { value_ = torch::IValue(std::forward(rhs)); } return *this; } static IValue none() { return IValue(); } std::string name_; std::optional value_; }; template struct types { using type = types; }; template struct init_types { using type = init_types; }; template init_types init() { return init_types{}; } class FunctionArgs { public: FunctionArgs() = default; template FunctionArgs(Args&&... args) { // NOLINT (add_arg(std::forward(args)), ...); } static FunctionArgs from_vector(const std::vector& args_vec) { FunctionArgs args; args.args_ = args_vec; return args; } void add_arg(torch::arg keyword) { if (!keyword.value_.has_value()) { throw std::runtime_error("Keyword argument `" + keyword.name_ + "` must be assigned a value"); } auto [it, inserted] = named_args_.emplace(keyword.name_, std::move(*keyword.value_)); if (!inserted) { throw std::runtime_error("Duplicate keyword argument `" + keyword.name_ + "`"); } } template void add_arg(T&& arg) { using Decayed = std::decay_t; if constexpr (std::is_same_v || (std::is_array_v && std::is_same_v, char>)) { args_.emplace_back(torch::IValue(std::string(arg))); return; } if constexpr (std::is_same_v) { args_.emplace_back(std::forward(arg)); return; } args_.emplace_back(torch::IValue(std::forward(arg))); } template auto get(size_t index) const -> std:: conditional_t, std::remove_reference_t, T> { if (index >= args_.size()) { throw std::out_of_range("Argument index out of range"); } const torch::IValue& arg = args_[index]; using ReturnType = std:: conditional_t, std::remove_reference_t, T>; // Handle const references by creating a temporary object if constexpr (std::is_const_v> && std::is_reference_v) { using NonConstType = std::remove_const_t>; NonConstType temp_result; if (arg.template try_convert_to(temp_result)) { return temp_result; } } else if constexpr (std::is_const_v>) { // Handle const types by using underlying non-const type for conversion using NonConstType = std::remove_const_t; NonConstType temp_result; if (arg.template try_convert_to(temp_result)) { return static_cast(temp_result); } } else { ReturnType result; if (arg.template try_convert_to(result)) { return result; } } std::ostringstream oss; oss << "Cannot convert argument " << index << " from " << arg.type_string() << " to " << typeid(T).name(); throw std::runtime_error(oss.str()); } // Convert to a tuple of specified types template std::tuple to_tuple() const { if (sizeof...(Types) != args_.size()) { throw std::runtime_error("Argument count mismatch: expected " + std::to_string(sizeof...(Types)) + ", got " + std::to_string(args_.size())); } return to_tuple_impl( std::make_index_sequence{}); } size_t size() const { return args_.size(); } size_t named_size() const { return named_args_.size(); } bool has_named_args() const { return !named_args_.empty(); } bool empty() const { return args_.empty(); } const IValue& operator[](size_t index) const { return args_[index]; } IValue& operator[](size_t index) { return args_[index]; } const torch::IValue& get_value(size_t index) const { if (index >= args_.size()) { throw std::out_of_range("Argument index out of range"); } return args_[index]; } auto begin() const { return args_.begin(); } auto end() const { return args_.end(); } const std::unordered_map& named_args() const { return named_args_; } std::string to_string() const { std::ostringstream oss; oss << "FunctionArgs["; for (size_t i = 0; i < args_.size(); ++i) { if (i > 0) oss << ", "; oss << args_[i]; } if (!named_args_.empty()) { if (!args_.empty()) { oss << ", "; } oss << "kwargs={"; bool first = true; for (const auto& [name, value] : named_args_) { if (!first) { oss << ", "; } oss << name << ": " << value; first = false; } oss << "}"; } oss << "]"; return oss.str(); } private: template std::tuple to_tuple_impl(std::index_sequence) const { return std::make_tuple(get(I)...); } std::vector args_; std::unordered_map named_args_; }; class FunctionResult { public: FunctionResult() : value_(torch::IValue()) {} template FunctionResult(T&& value) // NOLINT : value_(torch::IValue(std::forward(value))) {} FunctionResult(const torch::IValue& value) : value_(value) {} // NOLINT FunctionResult(torch::IValue&& value) : value_(std::move(value)) {} // NOLINT template T get() const { if (value_.is_none()) { throw std::runtime_error("No return value (void function)"); } T result; if (value_.try_convert_to(result)) { return result; } throw std::runtime_error("Cannot convert result from " + value_.type_string() + " to " + typeid(T).name()); } bool has_value() const { return !value_.is_none(); } const torch::IValue& get_value() const { return value_; } static FunctionResult void_result() { return FunctionResult(); } std::string to_string() const { return "FunctionResult(" + value_.to_repr() + ")"; } private: torch::IValue value_; }; template struct function_traits; // Basic function type template struct function_traits { using return_type = R; static constexpr size_t arity = sizeof...(Args); using ArgsTuple = std::tuple; template struct arg { using type = typename std::tuple_element>::type; }; // Generic function call interface template static IValue call_function(F&& func, const FunctionArgs& args) { if (args.size() != sizeof...(Args)) { throw std::runtime_error( "Function expects " + std::to_string(sizeof...(Args)) + " arguments, got " + std::to_string(args.size())); } return call_function_impl(std::forward(func), args, std::make_index_sequence{}); } private: template static IValue call_function_impl(F&& func, const FunctionArgs& args, std::index_sequence) { auto args_without_ref = std::make_tuple(args.template get>(I)...); if constexpr (std::is_void_v) { func(std::get(args_without_ref)...); return IValue(); } else { auto result = func(std::get(args_without_ref)...); return IValue(result); } } }; // Function pointer specialization template struct function_traits : public function_traits {}; // Reference to function type specialization template struct function_traits : public function_traits {}; // Const function type specialization template struct function_traits : public function_traits { }; // Const function pointer specialization template struct function_traits : public function_traits {}; // Common Reference and Pointer types template struct function_traits : public function_traits> {}; template struct function_traits : public function_traits {}; // Member function pointer specialization template struct function_traits : public function_traits { using class_type = C; static IValue call_method(R (C::*func)(Args...), C* instance, const FunctionArgs& args) { if (args.size() != sizeof...(Args) + 1) { // +1 for this pointer throw std::runtime_error( "Method expects " + std::to_string(sizeof...(Args)) + " arguments (plus this), got " + std::to_string(args.size() - 1)); } return call_method_impl( func, instance, args, std::make_index_sequence{}); } private: template static IValue call_method_impl(R (C::*func)(Args...), C* instance, const FunctionArgs& args, std::index_sequence) { // Skip args[0] which is 'this' auto args_without_ref = std::make_tuple( args.template get>(I + 1)...); if constexpr (std::is_void_v) { (instance->*func)(std::get(args_without_ref)...); return IValue(); } else { auto result = (instance->*func)(std::get(args_without_ref)...); return IValue(result); } } }; // Const member function pointer specialization template struct function_traits : public function_traits { using class_type = C; static IValue call_method(R (C::*func)(Args...) const, C* instance, const FunctionArgs& args) { if (args.size() != sizeof...(Args) + 1) { // +1 for this pointer throw std::runtime_error( "Method expects " + std::to_string(sizeof...(Args)) + " arguments (plus this), got " + std::to_string(args.size() - 1)); } return call_method_impl( func, instance, args, std::make_index_sequence{}); } private: template static IValue call_method_impl(R (C::*func)(Args...) const, C* instance, const FunctionArgs& args, std::index_sequence) { if constexpr (std::is_void_v) { (instance->*func)( args.get(I + 1)...); // Skip args[0] which is 'this' return IValue(); } else { auto result = (instance->*func)(args.get(I + 1)...); return IValue(result); } } }; template IValue invoke_function(Func&& func, const FunctionArgs& args) { using traits = function_traits>>; return traits::call_function(std::forward(func), args); } template IValue invoke_member_function(Func&& func, Class* instance, const FunctionArgs& args) { using traits = function_traits>>; return traits::call_method(func, instance, args); } class CppFunction { public: using CallableFunction = std::function; CppFunction() : func_(nullptr) {} // Constructor for lambda or function object explicit CppFunction(std::function func) : func_([func](const FunctionArgs& args) -> FunctionResult { try { auto result = func(args); return FunctionResult(result); } catch (const std::exception& e) { throw std::runtime_error("Constructor failed: " + std::string(e.what())); } }) {} // Common function pointer or member function pointer constructor template explicit CppFunction( Func&& f, typename std::enable_if_t< std::is_function_v>> || (std::is_pointer_v> && std::is_function_v>>)>* = nullptr) : func_([f = std::forward(f)]( const FunctionArgs& args) -> FunctionResult { try { auto result = invoke_function(f, args); return FunctionResult(result); } catch (const std::exception& e) { throw std::runtime_error("Function call failed: " + std::string(e.what())); } }) {} // Common member function pointer constructor template explicit CppFunction( Func&& f, typename std::enable_if_t< !std::is_function_v>> && !std::is_pointer_v> && std::is_invocable_v>* = nullptr) : func_([f = std::forward(f)]( const FunctionArgs& args) -> FunctionResult { try { auto result = f(args); return FunctionResult(result); } catch (const std::exception& e) { throw std::runtime_error("Lambda execution failed: " + std::string(e.what())); } }) {} CppFunction(CppFunction&& other) noexcept : func_(std::move(other.func_)), schema_(std::move(other.schema_)) {} CppFunction& operator=(CppFunction&& other) noexcept { if (this != &other) { func_ = std::move(other.func_); schema_ = std::move(other.schema_); } return *this; } CppFunction(const CppFunction&) = delete; CppFunction& operator=(const CppFunction&) = delete; FunctionResult call() const { if (!func_) { throw std::runtime_error("CppFunction is not initialized"); } return func_(FunctionArgs{}); } template FunctionResult call(Args&&... args) const { if (!func_) { throw std::runtime_error("CppFunction is not initialized"); } return func_(FunctionArgs{std::forward(args)...}); } FunctionResult call_with_args(const FunctionArgs& args) const { if (!func_) { throw std::runtime_error("CppFunction is not initialized"); } return func_(normalize_args_by_schema(args)); } bool valid() const { return func_ != nullptr; } void bind_schema(const c10::FunctionSchema& schema) { schema_ = schema; } private: FunctionArgs normalize_args_by_schema(const FunctionArgs& args) const { if (!schema_.has_value()) { return args; } const auto& schema = *schema_; const auto& schema_args = schema.arguments(); const size_t schema_arity = schema_args.size(); const size_t positional_count = args.size(); if (!schema.is_vararg() && positional_count > schema_arity) { throw std::runtime_error( "Too many positional arguments: expected at most " + std::to_string(schema_arity) + ", got " + std::to_string(positional_count)); } std::unordered_map arg_name_to_index; arg_name_to_index.reserve(schema_arity); for (size_t i = 0; i < schema_arity; ++i) { arg_name_to_index.emplace(schema_args[i].name(), i); } std::vector resolved_prefix(schema_arity); std::vector assigned(schema_arity, false); const size_t positional_prefix_count = positional_count < schema_arity ? positional_count : schema_arity; for (size_t i = 0; i < positional_prefix_count; ++i) { if (schema_args[i].kwarg_only()) { throw std::runtime_error("Argument `" + schema_args[i].name() + "` is keyword-only"); } resolved_prefix[i] = args.get_value(i); assigned[i] = true; } for (const auto& [name, value] : args.named_args()) { const auto it = arg_name_to_index.find(name); if (it == arg_name_to_index.end()) { throw std::runtime_error("Unknown keyword argument `" + name + "`"); } const size_t idx = it->second; if (assigned[idx]) { throw std::runtime_error("Argument `" + name + "` is already provided"); } resolved_prefix[idx] = value; assigned[idx] = true; } for (size_t i = 0; i < schema_arity; ++i) { if (assigned[i]) { continue; } if (schema_args[i].default_value().has_value()) { resolved_prefix[i] = *schema_args[i].default_value(); assigned[i] = true; continue; } throw std::runtime_error("Missing required argument `" + schema_args[i].name() + "`"); } std::vector normalized; normalized.reserve(schema_arity + (schema.is_vararg() && positional_count > schema_arity ? (positional_count - schema_arity) : 0)); for (auto& value : resolved_prefix) { normalized.emplace_back(std::move(value)); } if (schema.is_vararg() && positional_count > schema_arity) { for (size_t i = schema_arity; i < positional_count; ++i) { normalized.emplace_back(args.get_value(i)); } } return FunctionArgs::from_vector(normalized); } CallableFunction func_; std::optional schema_; }; struct ClassRegistration { std::string namespace_name; std::string class_name; std::string qualified_name; std::vector> constructors; std::unordered_map> methods; std::unordered_map> static_methods; ClassRegistration() = default; ClassRegistration(const std::string& ns, const std::string& name) : namespace_name(ns), class_name(name), qualified_name(ns + "::" + name) {} }; // Global class registry class PADDLE_API ClassRegistry { public: ClassRegistry() = default; static ClassRegistry& instance(); void register_class(const std::string& namespace_name, const std::string& class_name); void register_constructor(const std::string& qualified_name, CppFunction&& func); void register_method(const std::string& qualified_name, const std::string& method_name, CppFunction&& func); void register_static_method(const std::string& qualified_name, const std::string& method_name, CppFunction&& func); bool has_class(const std::string& qualified_name) const { return classes_.find(qualified_name) != classes_.end(); } bool has_method(const std::string& qualified_name, const std::string& method_name) const { auto it = classes_.find(qualified_name); if (it == classes_.end()) return false; return it->second->methods.find(method_name) != it->second->methods.end(); } bool has_static_method(const std::string& qualified_name, const std::string& method_name) const { auto it = classes_.find(qualified_name); if (it == classes_.end()) return false; return it->second->static_methods.find(method_name) != it->second->static_methods.end(); } FunctionResult call_method_with_args(const std::string& qualified_name, const std::string& method_name, const FunctionArgs& args) const; FunctionResult call_method_with_args(const std::string& qualified_name, const std::string& method_name, const IValue& instance, const FunctionArgs& args) const; FunctionResult call_constructor_with_args(const std::string& qualified_name, const FunctionArgs& args) const; FunctionResult call_static_method_with_args(const std::string& qualified_name, const std::string& method_name, const FunctionArgs& args) const; void print_all_classes() const; DISABLE_COPY_AND_ASSIGN(ClassRegistry); private: std::unordered_map> classes_; }; // Class registration API template class class_ { static_assert( std::is_base_of_v, "torch::class_ requires T to inherit from CustomClassHolder"); public: class_(const std::string& namespaceName, const std::string& className) : namespace_name_(namespaceName), class_name_(className), qualified_name_(namespaceName + "::" + className) { ClassRegistry::instance().register_class(namespaceName, className); } // Register constructor template class_& def(torch::init_types) { // Create a lambda for the constructor auto constructor_func = [](const FunctionArgs& args) -> torch::IValue { if constexpr (sizeof...(Types) == 0) { // Default constructor if (args.size() != 0) { throw std::runtime_error( "Default constructor expects 0 arguments, got " + std::to_string(args.size())); } auto instance = torch::make_intrusive(); return torch::IValue(instance); } else { // Parameterized constructor if (args.size() != sizeof...(Types)) { throw std::runtime_error( "Constructor argument count mismatch: expected " + std::to_string(sizeof...(Types)) + ", got " + std::to_string(args.size())); } // Use std::apply to unpack the arguments auto tuple_args = args.to_tuple(); auto instance = std::apply( [](Types... args) { return torch::make_intrusive( std::forward(args)...); }, tuple_args); return torch::IValue(instance); } }; ClassRegistry::instance().register_constructor( qualified_name_, CppFunction(constructor_func)); return *this; } // Register instance method template class_& def(const std::string& name, Func&& f) { // Check if Func is a member function pointer if constexpr (std::is_member_function_pointer_v>) { // Use function_traits to extract class type and method signature auto method_func = [f](const FunctionArgs& args) -> torch::IValue { if (args.size() < 1) { throw std::runtime_error( "Instance method requires at least 1 argument (this pointer)"); } // Get the instance (first argument) auto instance = args.get>(0); // Invoke the member function return invoke_member_function(f, instance.get(), args); }; ClassRegistry::instance().register_method( qualified_name_, name, CppFunction(method_func)); } else { // TODO(SigureMo): Handle generic callable (e.g., lambda, std::function) } return *this; } // Register static method template class_& def_static(const std::string& name, Func&& f) { ClassRegistry::instance().register_static_method( qualified_name_, name, CppFunction(std::forward(f))); return *this; } private: std::string namespace_name_; std::string class_name_; std::string qualified_name_; }; // Operator Registration struct OperatorRegistration { std::string qualified_name; // namespace::op_name std::optional> schemaOrName_; std::unordered_map implementations; OperatorRegistration(const std::string& name, const std::string& schema_str = "") : qualified_name(name) { schemaOrName_ = torch::jit::parseSchemaOrName(schema_str); } }; class PADDLE_API OperatorRegistry { public: OperatorRegistry() = default; static OperatorRegistry& instance(); void register_schema(const std::string& qualified_name, const std::string& schema); void register_implementation(const std::string& qualified_name, c10::DispatchKey key, CppFunction&& func); bool has_operator(const std::string& qualified_name) const { return operators_.find(qualified_name) != operators_.end(); } OperatorRegistration* find_operator(const std::string& qualified_name); std::vector list_all_operators() const { std::vector ops; for (const auto& pair : operators_) { ops.push_back(pair.first); } return ops; } const std::unordered_map& get_operators() const { return operators_; } void print_all_operators() const; DISABLE_COPY_AND_ASSIGN(OperatorRegistry); private: std::unordered_map operators_; OperatorRegistration& get_or_create_operator( const std::string& qualified_name) { auto it = operators_.find(qualified_name); if (it == operators_.end()) { auto [new_it, inserted] = operators_.emplace( qualified_name, OperatorRegistration(qualified_name)); return new_it->second; } return it->second; } }; class PADDLE_API Library { public: enum Kind { DEF, // TORCH_LIBRARY IMPL, // TORCH_LIBRARY_IMPL FRAGMENT // TORCH_LIBRARY_FRAGMENT }; Library(Kind kind, const std::string& ns, std::optional dispatch_key = std::nullopt, const char* file = nullptr, uint32_t line = 0); Library(const std::string& ns); // NOLINT // Define an operator schema (for TORCH_LIBRARY and TORCH_LIBRARY_FRAGMENT) Library& def(const std::string& schema) &; // Define an operator implementation template Library& def(const std::string& name_or_schema, Func&& f) & { if (kind_ == IMPL) { return *this; } auto op_name = extract_op_name(name_or_schema); auto qualified_name = ns_ + "::" + op_name; // If name_or_schema contains '(', treat it as a schema if (name_or_schema.find('(') != std::string::npos) { OperatorRegistry::instance().register_schema(qualified_name, name_or_schema); } // Register implementation auto dispatch_key = dispatch_key_.value_or(c10::DispatchKey::CPU); OperatorRegistry::instance().register_implementation( qualified_name, dispatch_key, CppFunction(std::forward(f))); return *this; } // Implementation of an operator template Library& impl(const std::string& op_name, Func&& f) & { auto qualified_name = ns_ + "::" + op_name; auto dispatch_key = dispatch_key_.value_or(c10::DispatchKey::CPU); OperatorRegistry::instance().register_implementation( qualified_name, dispatch_key, CppFunction(std::forward(f))); return *this; } template ::torch::class_ class_(const std::string& className) { return ::torch::class_(ns_, className); } // Print current library info void print_info() const; private: Kind kind_; std::string ns_; std::optional dispatch_key_; const char* file_; uint32_t line_; std::string extract_op_name(const std::string& name_or_schema) const { // Extract the operator name from the schema string auto pos = name_or_schema.find('('); if (pos != std::string::npos) { return name_or_schema.substr(0, pos); } return name_or_schema; } std::string kind_to_string(Kind kind) const { switch (kind) { case DEF: return "DEF"; case IMPL: return "IMPL"; case FRAGMENT: return "FRAGMENT"; default: return "UNKNOWN"; } } }; namespace detail { class TorchLibraryInit { public: using InitFn = void(Library&); TorchLibraryInit(Library::Kind kind, InitFn* fn, const char* ns, std::optional dispatch_key, const char* file, uint32_t line) { Library lib(kind, ns, dispatch_key, file, line); fn(lib); } }; } // namespace detail // TORCH_LIBRARY #define TORCH_LIBRARY(ns, m) \ static void TORCH_LIBRARY_init_##ns(torch::Library&); \ static const torch::detail::TorchLibraryInit TORCH_LIBRARY_static_init_##ns( \ torch::Library::DEF, \ &TORCH_LIBRARY_init_##ns, \ #ns, \ std::nullopt, \ __FILE__, \ __LINE__); \ void TORCH_LIBRARY_init_##ns(torch::Library& m) // NOLINT // TORCH_LIBRARY_FRAGMENT #define TORCH_LIBRARY_FRAGMENT(ns, m) _TORCH_LIBRARY_FRAGMENT(ns, m, C10_UID) #define _TORCH_LIBRARY_FRAGMENT(ns, m, uid) \ static void C10_CONCATENATE(TORCH_LIBRARY_FRAGMENT_init_##ns##_, \ uid)(torch::Library&); \ static const torch::detail::TorchLibraryInit C10_CONCATENATE( \ TORCH_LIBRARY_FRAGMENT_static_init_##ns##_, uid)( \ torch::Library::FRAGMENT, \ &C10_CONCATENATE(TORCH_LIBRARY_FRAGMENT_init_##ns##_, uid), \ #ns, \ std::nullopt, \ __FILE__, \ __LINE__); \ void C10_CONCATENATE(TORCH_LIBRARY_FRAGMENT_init_##ns##_, \ uid)(torch::Library & m) // NOLINT // TORCH_LIBRARY_IMPL #define TORCH_LIBRARY_IMPL(ns, k, m) _TORCH_LIBRARY_IMPL(ns, k, m, C10_UID) #define _TORCH_LIBRARY_IMPL(ns, k, m, uid) \ static void C10_CONCATENATE(TORCH_LIBRARY_IMPL_init_##ns##_##k##_, \ uid)(torch::Library&); \ static const torch::detail::TorchLibraryInit C10_CONCATENATE( \ TORCH_LIBRARY_IMPL_static_init_##ns##_##k##_, uid)( \ torch::Library::IMPL, \ &C10_CONCATENATE(TORCH_LIBRARY_IMPL_init_##ns##_##k##_, uid), \ #ns, \ std::make_optional(c10::DispatchKey::k), \ __FILE__, \ __LINE__); \ void C10_CONCATENATE(TORCH_LIBRARY_IMPL_init_##ns##_##k##_, \ uid)(torch::Library & m) // NOLINT } // namespace torch