/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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. */ /*! * \file tvm/tirx/function.h * \brief TIR Function. */ #ifndef TVM_TIR_FUNCTION_H_ #define TVM_TIR_FUNCTION_H_ #include #include #include #include #include #include #include #include #include namespace tvm { namespace tirx { /*! * \brief Primitive functions that contains TIR statements. * * The PrimFunc provides low-level code representation does not * automatically manage * * \sa PrimFunc */ class PrimFuncNode : public BaseFuncNode { public: /*! \brief Function parameters */ ffi::Array params; /*! \brief The return type of the function. */ Type ret_type = Type::Missing(); /*! * \brief Maps some parameters to specific Buffer data structures. * * buffer_map provides a way to express data structure's field and shape * constraints. The provided information is used in the program analysis * and the code generation. * * - It defines the vars in the Buffer (m, n) in the cases below when * they appears in the buffer_map for the first time. * - When a var appears multiple times, they translate into runtime * assertion to check the field constraint. * * \code * * # The corresponding fields of f are as follows * # * # - f.params = [a, b] * # - f.buffer_map = {a: A, b: B} * # - A = decl_buffer(shape=[m, n]) * # - B = decl_buffer(shape=[m, n]) * * def f(a, b): * m, n = var(), var() * A = bind_buffer(a, shape=[m, n]) * B = bind_buffer(b, shape=[m, n]) * # body * * \endcode * * buffer_map is a sugar to express: * - Parameter unpacking: e.g. I can load a.shape[0] to get value of m * - Constraint checking: a.shape[0] must equal b.shape[0] because they * both corresponds to m. * While we could have express parameter unpacking and constraint using * normal statements, making buffer_map as first class citizen of PrimFunc * will make program analysis much easier. * * Prior to buffer flattening, which is performed FlattenBuffer for * TIR-based schedules, these buffer objects are used directly in * the body of the function. After buffer flattening, these buffer * objects remain unflattened for use in argument validation, but * all usage in the body of the function is done through a * flattened alias of the buffer. */ ffi::Map buffer_map; /*! \brief The body of the function */ tirx::Stmt body; static void RegisterReflection() { namespace refl = tvm::ffi::reflection; refl::ObjectDef() .def_ro("params", &PrimFuncNode::params, refl::AttachFieldFlag::SEqHashDefRecursive()) .def_ro("ret_type", &PrimFuncNode::ret_type) .def_ro("buffer_map", &PrimFuncNode::buffer_map) .def_ro("body", &PrimFuncNode::body); refl::TypeAttrDef() .def("__s_equal__", &PrimFuncNode::SEqual) .def("__s_hash__", &PrimFuncNode::SHash); } bool SEqual(const PrimFuncNode* other, ffi::TypedFunction equal) const { // `ty` is derived from the fields below. PrimFunc transformations update // those source fields without maintaining this redundant cache eagerly. // Remove this exception once all PrimFunc mutation paths recompute `ty`. return equal(attrs, other->attrs, false, "attrs") && equal(params, other->params, true, "params") && equal(ret_type, other->ret_type, false, "ret_type") && equal(buffer_map, other->buffer_map, false, "buffer_map") && equal(body, other->body, false, "body"); } int64_t SHash(int64_t init_hash, ffi::TypedFunction hash) const { int64_t hash_value = init_hash; hash_value = hash(attrs, hash_value, false); hash_value = hash(params, hash_value, true); hash_value = hash(ret_type, hash_value, false); hash_value = hash(buffer_map, hash_value, false); hash_value = hash(body, hash_value, false); return hash_value; } /*! * \brief Return the derived function annotation of this function. * * \return The function type annotation. * \note The function type annotation of PrimExpr is * directly derived from the Vars without the need of type inference. */ TVM_DLL FuncType func_type_annotation() const; TVM_FFI_DECLARE_OBJECT_INFO_FINAL("tirx.PrimFunc", PrimFuncNode, BaseFuncNode); }; /*! * \brief Managed reference to PrimFuncNode. * \sa PrimFuncNode */ class PrimFunc : public BaseFunc { public: /*! * \brief Constructor * * \param params The parameters of the function. * * \param body The body of the function. * * \param ret_type The return type of the function. * * \param buffer_map The buffer map for parameter buffer unpacking. * This contains buffer objects as they appear in the body of the * PrimFunc. (e.g. a buffer of shape ``[1024]`` originally * generated as a tensor of shape ``[32, 32]``) * * \param attrs Additional function attributes. * * \param span The location of this object in the source code. */ TVM_DLL PrimFunc(ffi::Array params, Stmt body, Type ret_type = VoidType(), ffi::Map buffer_map = ffi::Map(), DictAttrs attrs = DictAttrs(), Span span = Span()); TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(PrimFunc, BaseFunc, PrimFuncNode); TVM_DEFINE_OBJECT_REF_COW_METHOD(PrimFuncNode); }; /*! * \brief Tensor intrinsics for tensorization */ class TensorIntrinNode : public ffi::Object { public: /*! \brief The function to describe the computation. */ PrimFunc desc; /*! \brief The function of the implementation for the execution. */ PrimFunc impl; static void RegisterReflection() { namespace refl = tvm::ffi::reflection; refl::ObjectDef() .def_ro("desc", &TensorIntrinNode::desc) .def_ro("impl", &TensorIntrinNode::impl); } TVM_FFI_DECLARE_OBJECT_INFO_FINAL("tirx.TensorIntrin", TensorIntrinNode, ffi::Object); }; /*! * \brief Managed reference to TensorIntrinNode. */ class TensorIntrin : public ffi::ObjectRef { public: /*! * \brief Constructor * \param desc The function to describe the computation. * \param impl The function of the implementation for the execution. */ TVM_DLL explicit TensorIntrin(PrimFunc desc, PrimFunc impl); /*! * \brief Create and register a TensorIntrin. After registration, the TensorIntrin can be looked * up with its name. * \param name The name of the TensorIntrin to register * \param intrin The TensorIntrin to register. * \param override Whether override existing intrinsic. * \throws This method throws an exception if the TensorIntrin with the specified name already * exists. */ TVM_DLL static void Register(ffi::String name, TensorIntrin intrin, bool override = false); /*! * \brief Look up TensorIntrin by name. Raises an exception if not found. * \param name The name of the TensorIntrin. * \param allow_missing Whether to allow missing tensor intrin. If false, an exception is raised * if the tensor intrin is not found. * \return The TensorIntrin with the specified name. * \throws This method throws an exception if the TensorIntrin does not exist and allow_missing is * false. */ TVM_DLL static ffi::Optional Get(ffi::String name, bool allow_missing = false); TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(TensorIntrin, ffi::ObjectRef, TensorIntrinNode); }; /*! * \brief Specialize parameters of PrimFunc. * \param func The PrimFunc to be specialized. * \param param_map The mapping from function params to the instance. * \return The new function with parameter specialized. * \note We can define a Meta TIR function with symbolic shape: * * \code{.py} * @T.prim_func * def mem_copy(a: T.handle, b: T.handle, m: T.int32, n: T.int32) -> None: * A = T.match_buffer(a, (m, n), "float32") * B = T.match_buffer(b, (m, n), "float32") * for i, j in T.grid(m, n): * with T.sblock(): * vi, vj = T.axis.remap("SS", [i, j]) * B[vi, vj] = A[vi, vj] * \endcode * * Then we can make it specialized with given shapes or buffers. * * \code{.py} * a, _, m, n = mem_copy.params * func = mem_copy.specialize({a: tirx.decl_buffer((16, 16))}) * # or * func = mem_copy.specialize({n: 16, m: 16}) * \endcode * * \code{.py} * @T.prim_func * def mem_copy_16_16(a: T.handle, b: T.handle) -> None: * A = T.match_buffer(a, (16, 16), "float32") * B = T.match_buffer(b, (16, 16), "float32") * for i, j in T.grid(16, 16): * with T.sblock(): * vi, vj = T.axis.remap("SS", [i, j]) * B[vi, vj] = A[vi, vj] * \endcode */ PrimFunc Specialize(PrimFunc func, const ffi::Map>& param_map); /*! * \brief PrimFunc specific attribute names. * * \sa tvm::attr */ namespace attr { /*! * \brief List of thread IterVar that a DeviceLaunch function corresponds to. * * Type: ffi::Array * * We call a device kernel launch function f using the following convention: * * Call(f, * [arg1, arg2, ..., arg_n, * work_size_1, work_size_2, ... work_size_m, dyn_shmem_size]) * * Here n = len(arg), m = len(work_size) = len(launch_params)-1. * * The list of kernel launch params indicates which additional * parameters will be provided to the ffi::Function by the calling * scope. * * - "threadIdx.x", "threadIdx.y", "threadIdx.z" * * The extent of the thread count in x/y/z, to be used when * launching the compute kernel on the device. For example, the * gridDimX/Y/Z parameters passed to cuLaunchKernel when launching a * CUDA kernel, or the groupCountX/Y/Z parameters passed to * vkCmdDispatch when dispatching a compute pipeline to Vulkan. * * - "blockIdx.x", "blockIdx.y", "blockIdx.z" * * The extent of the block iterators, to be used when launching the * compute kernel on the device. For example, the blockDimX/Y/Z * parameters passed to cuLaunchKernel when launching a CUDA kernel. * For runtimes that do not require the block to be provided * externally, this parameter is ignored. For example, the * spv::ExecutionModeLocalSize for SPIR-V shaders on Vulkan, where * this parameter is defined in the shader. * * - tvm::runtime::launch_param::kUseDynamicSharedMemoryTag * * The size of the shared memory that may be allocated internally by * the kernel. For example, exposed as the * CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES attribute in * CUDA. * * Defined as "tirx.use_dyn_shared_memory". * * \sa tvm::CallingConv::kDeviceKernelLaunch */ constexpr const char* kKernelLaunchParams = "tirx.kernel_launch_params"; /*! * \brief CUDA launch bound minimum CTAs per SM. * * Type: IntImm */ constexpr const char* kLaunchBoundsMinBlocksPerSM = "tirx.launch_bounds_min_blocks_per_sm"; /*! * \brief Whether to set noalias rule on the function arguments. * * Type: IntImm */ constexpr const char* kNoAlias = "tirx.noalias"; /*! * \brief Mark the function as the entry function of * the final generated runtime module. * * Type: IntImm * * \note There can only be one entry function per module. */ constexpr const char* kIsEntryFunc = "tirx.is_entry_func"; /*! * \brief Mark the function as the global function called from the host. * * Type: IntImm */ constexpr const char* kIsGlobalFunc = "tirx.is_global_func"; /*! * \brief Mark the function as run on the host, mutually exclusive with kTarget. * * Type: IntImm */ constexpr const char* kIsHostFunc = "tirx.is_host_func"; /*! * \brief Mark the function as scheduled, so the default schedule will pass will skip it. * * Type: IntImm */ constexpr const char* kIsScheduled = "tirx.is_scheduled"; } // namespace attr } // namespace tirx } // namespace tvm #endif // TVM_TIR_FUNCTION_H_