Files
wehub-resource-sync 26446540fa
Lint / lint (push) Has been cancelled
CI / MacOS (push) Has been cancelled
CI / Windows (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:36:25 +08:00

158 lines
6.3 KiB
C++

/*
* 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.
*/
/*!
* \brief Detail broadcast.
* \file topi/detail/broadcast.h
*/
#ifndef TVM_TOPI_DETAIL_BROADCAST_H_
#define TVM_TOPI_DETAIL_BROADCAST_H_
#include <tvm/te/operation.h>
#include <tvm/topi/detail/constant_utils.h>
#include <algorithm>
#include <deque>
#include <string>
namespace tvm {
namespace topi {
namespace detail {
struct BroadcastHelper {
std::deque<tvm::PrimExpr> common_shape;
std::deque<tvm::tirx::PrimVar> all_vars;
std::deque<tvm::tirx::PrimVar> vars1;
std::deque<tvm::tirx::PrimVar> vars2;
};
static inline PrimType CommonType(const PrimType& type1, const PrimType& type2) {
TVM_FFI_ICHECK(type1.IsScalar() && type2.IsScalar());
TVM_FFI_ICHECK(type1.code() == type2.code());
return type1.bits() < type2.bits() ? type1.WithBits(type2.bits()) : type1;
}
inline BroadcastHelper BroadcastShape(const tvm::ffi::Array<tvm::PrimExpr>& shape1,
const tvm::ffi::Array<tvm::PrimExpr>& shape2) {
BroadcastHelper bh;
int s1_size = shape1.size();
int s2_size = shape2.size();
tvm::PrimExpr one(1);
int i;
auto cast_if_needed = [](PrimType to_type, PrimExpr expr) {
return to_type == expr.ty() ? expr : cast(to_type, expr);
};
for (i = 1; i <= std::min(s1_size, s2_size); ++i) {
// TODO(@icemelon9): Need to revisit this part
const IntImmNode* static_size1 = shape1[s1_size - i].as<IntImmNode>();
const IntImmNode* static_size2 = shape2[s2_size - i].as<IntImmNode>();
PrimType common_type = CommonType(shape1[s1_size - i].ty(), shape2[s2_size - i].ty());
bh.all_vars.push_front(tvm::tirx::PrimVar("dim", common_type));
if (topi::detail::EqualCheck(shape1[s1_size - i], shape2[s2_size - i])) {
bh.common_shape.push_front(cast_if_needed(common_type, shape1[s1_size - i]));
bh.vars1.push_front(bh.all_vars[0]);
bh.vars2.push_front(bh.all_vars[0]);
} else if (topi::detail::EqualCheck(one, shape1[s1_size - i])) {
TVM_FFI_ICHECK(!topi::detail::EqualCheck(one, shape2[s2_size - i]));
bh.common_shape.push_front(cast_if_needed(common_type, shape2[s2_size - i]));
bh.vars2.push_front(bh.all_vars[0]);
} else if (topi::detail::EqualCheck(one, shape2[s2_size - i])) {
bh.common_shape.push_front(cast_if_needed(common_type, shape1[s1_size - i]));
bh.vars1.push_front(bh.all_vars[0]);
} else if (!static_size1 && !static_size2) {
bh.common_shape.push_front(
cast_if_needed(common_type, max(shape1[s1_size - i], shape2[s2_size - i])));
bh.vars1.push_front(bh.all_vars[0]);
bh.vars2.push_front(bh.all_vars[0]);
} else if (!static_size1) {
bh.common_shape.push_front(cast_if_needed(common_type, shape2[s2_size - i]));
bh.vars2.push_front(bh.all_vars[0]);
bh.vars1.push_front(bh.all_vars[0]);
} else if (!static_size2) {
bh.common_shape.push_front(cast_if_needed(common_type, shape1[s1_size - i]));
bh.vars1.push_front(bh.all_vars[0]);
bh.vars2.push_front(bh.all_vars[0]);
} else {
TVM_FFI_ICHECK(false) << "Incompatible broadcast dims: " << shape1[s1_size - i] << " and "
<< shape2[s2_size - i] << " in: "
<< tvm::ffi::Array<tvm::PrimExpr>(shape1.begin(), shape1.end())
<< " and "
<< tvm::ffi::Array<tvm::PrimExpr>(shape2.begin(), shape2.end());
}
}
// Remaining dimensions whether on shape1 or shape2 can always be completed
auto max_size = std::max(s1_size, s2_size);
auto& shape = (s1_size > s2_size) ? shape1 : shape2;
auto& vars = (s1_size > s2_size) ? bh.vars1 : bh.vars2;
for (; i <= max_size; ++i) {
bh.all_vars.push_front(tvm::tirx::PrimVar("v", shape[max_size - 1].ty()));
bh.common_shape.push_front(shape[max_size - i]);
vars.push_front(bh.all_vars[0]);
}
return bh;
}
inline tvm::ffi::Array<tvm::PrimExpr> InputIndexFromBroadcast(
const tvm::ffi::Array<tvm::tirx::PrimVar>& ovars, const tvm::te::Tensor& T,
const std::deque<tvm::tirx::PrimVar>& my_vars, const std::deque<tvm::tirx::PrimVar>& all_vars) {
tvm::ffi::Array<tvm::PrimExpr> ivars;
TVM_FFI_ICHECK_EQ(ovars.size(), all_vars.size());
// N^2, could use a map but NBD.
size_t expected_dims = T->shape.size();
for (size_t i = 0; i < ovars.size(); ++i) {
bool found = false;
for (size_t j = 0; j < my_vars.size(); ++j) {
if (all_vars[i].same_as(my_vars[j])) {
ivars.push_back(ovars[i]);
found = true;
break;
}
}
// Only inject 0 here if we have not yet reached the dimension of I
// (i.e. this must be a 1)
if (!found && (ovars.size() - i) <= expected_dims) {
ivars.push_back(tvm::IntImm(ovars[i].ty(), 0));
}
}
TVM_FFI_ICHECK(expected_dims == ivars.size());
return ivars;
}
template <typename FBinaryExpr>
inline tvm::te::Tensor WithBroadcast(FBinaryExpr op, const tvm::te::Tensor& A,
const tvm::te::Tensor& B, const std::string& name = "tensor",
const std::string& tag = "") {
auto bh = BroadcastShape(A->shape, B->shape);
auto l = [&](tvm::ffi::Array<tvm::tirx::PrimVar> ovars) {
return op(A(InputIndexFromBroadcast(ovars, A, bh.vars1, bh.all_vars)),
B(InputIndexFromBroadcast(ovars, B, bh.vars2, bh.all_vars)));
};
return tvm::te::compute(
tvm::ffi::Array<tvm::PrimExpr>(bh.common_shape.begin(), bh.common_shape.end()), l, name, tag);
}
} // namespace detail
} // namespace topi
} // namespace tvm
#endif // TVM_TOPI_DETAIL_BROADCAST_H_