Files
wehub-resource-sync 8a852e4b4e
cffconvert / validate (push) Has been skipped
License Check / license-check (push) Failing after 2s
chore: import upstream snapshot with attribution
2026-07-13 12:14:16 +08:00

289 lines
12 KiB
C++

/* Copyright 2020 The TensorFlow 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.
==============================================================================*/
#include <sys/types.h>
#include <cstdint>
#include <memory>
#include <optional>
#include <utility>
#include <vector>
#include "absl/status/statusor.h"
#include "absl/types/span.h"
#include "tensorflow/compiler/tf2xla/type_util.h"
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
#include "xla/comparison_util.h"
#include "xla/hlo/builder/lib/arithmetic.h"
#include "xla/hlo/builder/lib/comparators.h"
#include "xla/hlo/builder/lib/constants.h"
#include "xla/hlo/builder/xla_builder.h"
#include "xla/hlo/builder/xla_computation.h"
#include "xla/shape.h"
#include "xla/shape_util.h"
#include "xla/util.h"
#include "xla/xla_data.pb.h"
#include "tensorflow/core/framework/op_kernel.h"
#include "tensorflow/core/framework/op_requires.h"
#include "tensorflow/core/framework/types.pb.h"
#include "tensorflow/core/platform/errors.h"
namespace tensorflow {
namespace {
class UniqueOpBase : public XlaOpKernel {
public:
explicit UniqueOpBase(OpKernelConstruction* ctx) : XlaOpKernel(ctx) {
DataType dtype;
OP_REQUIRES_OK(ctx, ctx->GetAttr("out_idx", &dtype));
OP_REQUIRES_OK(ctx, DataTypeToPrimitiveType(dtype, &idx_type_));
}
// Transpose a tensor by moving axis `from` into `to`.
xla::XlaOp MoveAxis(xla::XlaOp a, int64_t from, int64_t to,
const xla::Shape& input_shape) {
std::vector<int64_t> permutation;
permutation.reserve(input_shape.dimensions().size());
for (int64_t i = 0; i < input_shape.dimensions().size(); ++i) {
permutation.push_back(i);
}
std::swap(permutation[from], permutation[to]);
return xla::Transpose(a, permutation);
}
xla::XlaOp CumSumR1(XlaOpKernelContext* ctx, xla::XlaOp input, int64_t size) {
auto init = xla::Zero(ctx->builder(), xla::S32);
auto reducer = xla::CreateScalarAddComputation(xla::S32, ctx->builder());
return xla::ReduceWindowWithGeneralPadding(
input, init, reducer, {size}, {1},
/*base_dilations=*/{}, /*window_dilations=*/{}, {{size - 1, 0}});
}
// RollingSelectR1 takes two arrays: `data` and `mask`. It scans this two
// arrays in parallel and accumulates outputs into `accum`.
//
// For each position i, accum[i] = data[i]
// if mask[i] = 1 or accum[i - 1] if mask[i] = 0.
//
// Requires mask[0] = 1, meaning that accum[i - 1] will never be accessed.
//
// This is implemented as an hlo while loop.
xla::XlaOp RollingSelectR1(XlaOpKernelContext* ctx, xla::XlaOp data,
xla::XlaOp mask, int64_t size) {
xla::XlaComputation cond, body;
const xla::Shape r1_shape = xla::ShapeUtil::MakeShape(xla::S32, {size});
const xla::Shape counter_shape = xla::ShapeUtil::MakeScalarShape(xla::S32);
const xla::Shape& single_element_shape = counter_shape;
auto loop_shape = xla::ShapeUtil::MakeTupleShape(
{counter_shape, r1_shape, r1_shape, r1_shape});
{
std::unique_ptr<xla::XlaBuilder> builder =
ctx->builder()->CreateSubBuilder("loop_cond");
auto param = xla::Parameter(builder.get(), 0, loop_shape, "param");
auto counter = xla::GetTupleElement(param, 0);
auto limit = xla::ConstantR0<int32_t>(builder.get(), size);
xla::Lt(counter, limit);
cond = builder->Build().value();
}
{
std::unique_ptr<xla::XlaBuilder> builder =
ctx->builder()->CreateSubBuilder("loop_body");
auto param = xla::Parameter(builder.get(), 0, loop_shape, "param");
auto counter = xla::GetTupleElement(param, 0);
auto data_stack = xla::GetTupleElement(param, 1);
auto data = xla::DynamicSlice(data_stack, {counter}, {1});
data = xla::Reshape(single_element_shape, data);
auto mask_stack = xla::GetTupleElement(param, 2);
auto mask = xla::DynamicSlice(mask_stack, {counter}, {1});
mask = xla::Reshape(single_element_shape, mask);
auto counter_minus = counter - xla::One(builder.get(), xla::S32);
// If counter = 0, then counter_minus = 0.
auto zero = xla::Zero(builder.get(), xla::S32);
counter_minus = xla::Select(xla::Eq(counter, zero), zero, counter_minus);
auto accum_stack = xla::GetTupleElement(param, 3);
auto accum_minus = xla::DynamicSlice(accum_stack, {counter_minus}, {1});
accum_minus = xla::Reshape(single_element_shape, accum_minus);
auto accum = xla::Select(xla::ConvertElementType(mask, xla::PRED), data,
accum_minus);
accum_stack = xla::DynamicUpdateSlice(
accum_stack, xla::Reshape(accum, {1}), {counter});
counter = counter + xla::One(builder.get(), xla::S32);
xla::Tuple(builder.get(), {counter, data_stack, mask_stack, accum_stack});
body = builder->Build().value();
}
auto zero = xla::Zero(ctx->builder(), xla::S32);
auto zero_broadcast = xla::Broadcast(zero, {size});
auto init = xla::Tuple(ctx->builder(), {zero, data, mask, zero_broadcast});
return xla::GetTupleElement(xla::While(cond, body, init), 3);
}
void CompileWithAxis(XlaOpKernelContext* ctx, int64_t axis) {
xla::XlaOp input = ctx->Input(0);
absl::StatusOr<xla::Shape> input_shape_or = ctx->builder()->GetShape(input);
OP_REQUIRES_OK(ctx, input_shape_or.status());
auto input_shape = input_shape_or.value();
axis = axis < 0 ? axis + input_shape.dimensions().size() : axis;
OP_REQUIRES(ctx, 0 <= axis && axis < input_shape.dimensions().size(),
errors::InvalidArgument("axis has to be between [0, ",
input_shape.dimensions().size(), ")"));
auto aux = MoveAxis(input, axis, 0, input_shape);
auto aux_shape = ctx->builder()->GetShape(aux).value();
int64_t leading_size = aux_shape.dimensions(0);
int64_t product = 1;
for (int64_t i = 1; i < aux_shape.dimensions().size(); ++i) {
product *= aux_shape.dimensions(i);
}
aux = xla::Reshape(aux, {leading_size, product});
if (leading_size == 0) {
auto result_data = xla::Reshape(aux, aux_shape.dimensions());
result_data = MoveAxis(result_data, 0, axis, aux_shape);
ctx->SetOutput(0, result_data);
ctx->SetOutput(1, xla::Iota(ctx->builder(), xla::S32, leading_size));
return;
}
std::vector<xla::XlaOp> sort_keys;
sort_keys.reserve(product + 1);
std::vector<xla::PrimitiveType> sort_types;
sort_types.reserve(product + 1);
for (int64_t i = 0; i < product; ++i) {
xla::XlaOp slice = xla::SliceInDim(aux, i, i + 1, 1, 1);
sort_keys.push_back(xla::Reshape(slice, {leading_size}));
sort_types.push_back(input_shape.element_type());
}
auto iota = xla::Iota(ctx->builder(), xla::S32, leading_size);
sort_keys.push_back(iota);
sort_types.push_back(xla::S32);
std::vector<std::optional<xla::XlaOp (*)(xla::XlaOp, xla::XlaOp,
absl::Span<const int64_t>)>>
generators(sort_types.size(), xla::LtTotalOrder);
auto lt_chain = xla::CreateScalarComparisonComputation(
"UniqueV2Lt", sort_types, generators, ctx->builder());
auto sorted = xla::Sort(sort_keys, lt_chain, 0, /*is_stable=*/true);
// Last element is permutation.
xla::XlaOp perm;
if (sort_keys.size() == 1) {
perm = sorted;
} else {
perm = xla::GetTupleElement(sorted, sort_keys.size() - 1);
}
// Use gather to rearrange minor dimension.
xla::GatherDimensionNumbers gather_dim_numbers;
gather_dim_numbers.add_offset_dims(1);
// The dimension to rewrite is the index dim.
gather_dim_numbers.add_start_index_map(0);
gather_dim_numbers.set_index_vector_dim(1);
gather_dim_numbers.add_collapsed_slice_dims(0);
auto permuted = xla::Gather(aux, perm, gather_dim_numbers, {1, product});
// Tail is everything except for first element.
auto tail = xla::SliceInDim(permuted, 1, leading_size, 1, 0);
// Init is everything except for last element.
auto init = xla::SliceInDim(permuted, 0, leading_size - 1, 1, 0);
auto ne = xla::Compare(tail, init, xla::ComparisonDirection::kNe);
auto reduce =
xla::Reduce(ne, xla::ConstantR0(ctx->builder(), false),
CreateScalarOrComputation(xla::PRED, ctx->builder()), {1});
auto mask = xla::ConvertElementType(reduce, xla::S32);
mask = xla::PadInDim(mask, xla::One(ctx->builder(), xla::S32), 0, 1, 0);
auto iperm = RollingSelectR1(ctx, perm, mask, leading_size);
auto sort_by_iperm =
xla::Sort({iperm, mask, perm},
xla::CreateScalarLtComputation({xla::S32, xla::S32, xla::S32},
ctx->builder()),
0,
/*is_stable=*/true);
mask = xla::GetTupleElement(sort_by_iperm, 1);
// perm_sort is used later to revert the indices back to input order.
auto perm_sort = xla::GetTupleElement(sort_by_iperm, 2);
auto dynamic_size = xla::ReduceAll(
mask, xla::Zero(ctx->builder(), xla::S32),
xla::CreateScalarAddComputation(xla::S32, ctx->builder()));
auto mask_sort = xla::Sort(
{mask, perm_sort},
xla::CreateScalarGtComputation({xla::S32, xla::S32}, ctx->builder()), 0,
/*is_stable=*/true);
auto mask_permute = xla::GetTupleElement(mask_sort, 1);
permuted = xla::Gather(aux, mask_permute, gather_dim_numbers, {1, product});
auto result_data = xla::Reshape(permuted, aux_shape.dimensions());
result_data = MoveAxis(result_data, 0, axis, aux_shape);
result_data = xla::SetDimensionSize(result_data, dynamic_size, axis);
ctx->SetOutput(0, result_data);
auto imask = CumSumR1(ctx, mask, leading_size);
imask = xla::Sub(imask, xla::One(ctx->builder(), xla::S32), {});
auto idx = xla::GetTupleElement(
xla::Sort({perm_sort, imask},
xla::CreateScalarLtComputation({xla::S32, xla::S32},
ctx->builder())),
1);
idx = xla::ConvertElementType(idx, idx_type_);
ctx->SetOutput(1, idx);
}
private:
xla::PrimitiveType idx_type_;
};
class UniqueOp : public UniqueOpBase {
public:
explicit UniqueOp(OpKernelConstruction* ctx) : UniqueOpBase(ctx) {}
void Compile(XlaOpKernelContext* ctx) override {
CompileWithAxis(ctx, /*axis=*/0);
}
};
REGISTER_XLA_OP(Name("Unique"), UniqueOp);
class UniqueV2Op : public UniqueOpBase {
public:
explicit UniqueV2Op(OpKernelConstruction* ctx) : UniqueOpBase(ctx) {}
void Compile(XlaOpKernelContext* ctx) override {
std::vector<int64_t> axises;
OP_REQUIRES_OK(ctx, ctx->ConstantInputAsIntVector(1, &axises));
OP_REQUIRES(
ctx, axises.size() <= 1,
xla::InvalidArgument("Only single axis unique op is supported"));
int64_t axis;
if (axises.empty()) {
axis = 0;
} else {
axis = axises.front();
}
CompileWithAxis(ctx, /*axis=*/axis);
}
};
REGISTER_XLA_OP(Name("UniqueV2").CompileTimeConstantInput("axis"), UniqueV2Op);
} // namespace
} // namespace tensorflow