# 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. # pylint: disable=invalid-name """Legalization functions for DLTensor inspection.""" import enum from tvm.ir import Call from tvm.script import tirx as T from ... import op from ...block_builder import BlockBuilder from ...expr import Expr from .common import register_legalize class TVMStructFieldKind(enum.IntEnum): """Equivalent to tvm::tirx::builtin::TVMStructFieldKind This does not use `enum.auto()` to define the values, because `enum.auto()` starts from 1, and this must match the C++ definition which starts from 0. """ kDLTensorAddr = 0 kDLTensorData = 1 kDLTensorShape = 2 kDLTensorStrides = 3 kDLTensorNDim = 4 kDLTensorTypeCode = 5 kDLTensorTypeBits = 6 kDLTensorTypeLanes = 7 kDLTensorByteOffset = 8 kDLTensorDeviceId = 9 kDLTensorDeviceType = 10 kDLTensorKindBound_ = 11 kTVMValueContent = 12 kTVMValueKindBound_ = 13 @register_legalize("relax.inspect.tensor_stride_i") def _tensor_stride_i(bb: BlockBuilder, call: Call) -> Expr: @T.prim_func(private=True, s_tir=True) def _get_tensor_stride_i(dlpack_handle: T.handle, axis: T.int64) -> T.int64: T.func_attr({"tirx.is_host_func": True, "tirx.is_scheduled": True}) assert T.int64(0) <= axis, "Specified axis may not be negative" ndim: T.let[T.int32] = T.tvm_struct_get( dlpack_handle, 0, int(TVMStructFieldKind.kDLTensorNDim), "int32" ) assert axis < T.Cast("int64", ndim), ( "Specified axis may not be larger than the tensor's dimensionality" ) stride_ptr: T.let[T.handle("int64")] = T.tvm_struct_get( dlpack_handle, 0, int(TVMStructFieldKind.kDLTensorStrides), T.handle("int64").ty ) if T.isnullptr(stride_ptr): shape_ptr: T.let[T.handle("int64")] = T.tvm_struct_get( dlpack_handle, 0, int(TVMStructFieldKind.kDLTensorShape), T.handle("int64").ty ) shape = T.decl_buffer(ndim, "int64", data=shape_ptr) product = T.decl_buffer([], "int64") product[()] = 1 # TODO(Lunderberg): Add a TIR lowering pass to allow # ranges to start somewhere other than zero. This loop # could then iterate on `range(axis+1, ndim)`. for dim_offset in range(ndim - (axis + 1)): dim: T.let[T.int64] = dim_offset + (axis + 1) product[()] = product[()] * shape[dim] return product[()] else: strides = T.decl_buffer(ndim, "int64", data=stride_ptr) stride: T.let[T.int64] = strides[axis] return stride gvar = bb.add_func(_get_tensor_stride_i, "_get_tensor_stride_i") return Call(gvar, call.args) @register_legalize("relax.inspect.tensor_byte_offset") def _tensor_byte_offset(bb: BlockBuilder, call: Call) -> Expr: @T.prim_func(private=True, s_tir=True) def _get_tensor_byte_offset(dlpack_handle: T.handle) -> T.int64: T.func_attr({"tirx.is_host_func": True, "tirx.is_scheduled": True}) byte_offset: T.let[T.uint64] = T.tvm_struct_get( dlpack_handle, 0, int(TVMStructFieldKind.kDLTensorByteOffset), "uint64" ) return byte_offset gvar = bb.add_func(_get_tensor_byte_offset, "_get_tensor_byte_offset") return Call(gvar, call.args) @register_legalize("relax.inspect.tensor_elem_offset") def _tensor_elem_offset(bb: BlockBuilder, call: Call) -> Expr: @T.prim_func(private=True, s_tir=True) def _get_tensor_elem_offset(dlpack_handle: T.handle) -> T.int64: T.func_attr({"tirx.is_host_func": True, "tirx.is_scheduled": True}) byte_offset: T.let[T.uint64] = T.tvm_struct_get( dlpack_handle, 0, int(TVMStructFieldKind.kDLTensorByteOffset), "uint64" ) scalar_bits: T.let[T.uint8] = T.tvm_struct_get( dlpack_handle, 0, int(TVMStructFieldKind.kDLTensorTypeBits), "uint8" ) lanes: T.let[T.uint16] = T.tvm_struct_get( dlpack_handle, 0, int(TVMStructFieldKind.kDLTensorTypeLanes), "uint16" ) bytes_per_element: T.let[T.uint64] = T.ceildiv( scalar_bits.astype("uint64") * lanes.astype("uint64"), 8 ) elem_offset: T.let[T.uint64] = byte_offset // bytes_per_element return elem_offset gvar = bb.add_func(_get_tensor_elem_offset, "_get_tensor_elem_offset") return Call(gvar, call.args) @register_legalize("relax.size") def _size(_bb: BlockBuilder, call: Call) -> Expr: return op.prod(op.shape_to_tensor(op.shape_of(call.args[0])))