mirror of
https://github.com/tensorflow/tensorflow.git
synced 2026-10-26 00:09:51 +08:00
Constant-fold FLOOR_DIV.
tfl.floor_div had no folder, so a FLOOR_DIV on constant operands survived conversion as a runtime op. GPU delegates that reject FLOOR_DIV then see a CPU-only op in the middle of an otherwise delegable graph. The fold matches the TFLite kernel: f32 operands are divided and floored in double, and integer division rounds toward negative infinity. Division by zero (for any element type) is left to the runtime, which reports it as an error. HF DETR models hit this in the sine position embedding (dim_t // 2 on a constant arange); it is their only FLOOR_DIV. Tests: const-fold.mlir adds f32 (with broadcast, and a 1.0 // 0.1 case that needs the double division) and i32 (all sign combinations) folds, plus divide-by-zero (i32 and f32) and runtime-operand negatives. PiperOrigin-RevId: 1004127675
This commit is contained in:
1 parent
fd30f24418
commit
72e90de7db
3 files changed
+126
No files matched your search
@@ -3510,6 +3510,55 @@ int64_t DivOp::GetArithmeticCount(Operation* op) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// FloorDivOp
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
OpFoldResult FloorDivOp::fold(FoldAdaptor adaptor) {
|
||||
if (!ShouldFoldOperation(this->getOperation())) return {};
|
||||
|
||||
if (llvm::isa<quant::QuantizedType>(getType().getElementType())) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto lhs = llvm::dyn_cast_or_null<ElementsAttr>(adaptor.getLhs());
|
||||
auto rhs = llvm::dyn_cast_or_null<ElementsAttr>(adaptor.getRhs());
|
||||
if (!lhs || !rhs) return {};
|
||||
|
||||
// The TFLite kernel fails on a zero divisor for every element type; leave
|
||||
// such ops to the runtime instead of folding them to a value (or inf/NaN).
|
||||
const Type element_type = getType().getElementType();
|
||||
if (element_type.isSignlessInteger()) {
|
||||
auto int_rhs = llvm::dyn_cast<DenseIntElementsAttr>(rhs);
|
||||
if (!int_rhs) return {};
|
||||
for (const APInt& v : int_rhs.getValues<APInt>()) {
|
||||
if (v.isZero()) return {};
|
||||
}
|
||||
} else if (element_type.isF32()) {
|
||||
auto float_rhs = llvm::dyn_cast<DenseFPElementsAttr>(rhs);
|
||||
if (!float_rhs) return {};
|
||||
for (const APFloat& v : float_rhs.getValues<APFloat>()) {
|
||||
if (v.isZero()) return {};
|
||||
}
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Matches the TFLite kernel: floats are divided and floored in double (see
|
||||
// reference_ops::FloorDiv), and integer division rounds toward negative
|
||||
// infinity.
|
||||
return ConstFoldBinaryOp(
|
||||
getType(), getOperands(),
|
||||
[](APFloat a, APFloat b) {
|
||||
const double q = std::floor(static_cast<double>(a.convertToFloat()) /
|
||||
static_cast<double>(b.convertToFloat()));
|
||||
return APFloat(static_cast<float>(q));
|
||||
},
|
||||
[](APInt a, APInt b) {
|
||||
return llvm::APIntOps::RoundingSDiv(a, b, APInt::Rounding::DOWN);
|
||||
});
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// PackOp
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
@@ -1996,6 +1996,8 @@ def TFL_FloorDivOp : TFL_Op<"floor_div", [
|
||||
|
||||
let builders = [TFL_BroadcastableBinaryBuilder];
|
||||
|
||||
let hasFolder = 1;
|
||||
|
||||
let hasCustomAssemblyFormat = 1;
|
||||
|
||||
let extraClassDefinition = [{
|
||||
|
||||
@@ -67,6 +67,81 @@ func.func @cumsum_chained_pipeline() -> tensor<3xf32> {
|
||||
func.return %res_pipeline : tensor<3xf32>
|
||||
}
|
||||
|
||||
// The DETR sine position embedding pattern: dim_t // 2 on a constant arange.
|
||||
// CHECK-LABEL: @floor_div_f32
|
||||
func.func @floor_div_f32() -> tensor<4xf32> {
|
||||
%lhs = arith.constant dense<[0.0, 3.0, -3.0, 7.5]> : tensor<4xf32>
|
||||
%rhs = arith.constant dense<2.0> : tensor<f32>
|
||||
|
||||
// CHECK: %[[CST:.*]] = arith.constant dense<[0.000000e+00, 1.000000e+00, -2.000000e+00, 3.000000e+00]> : tensor<4xf32>
|
||||
// CHECK-NOT: tfl.floor_div
|
||||
// CHECK: return %[[CST]]
|
||||
|
||||
%0 = "tfl.floor_div"(%lhs, %rhs) : (tensor<4xf32>, tensor<f32>) -> tensor<4xf32>
|
||||
func.return %0 : tensor<4xf32>
|
||||
}
|
||||
|
||||
// Rounds toward negative infinity, not toward zero.
|
||||
// CHECK-LABEL: @floor_div_i32
|
||||
func.func @floor_div_i32() -> tensor<4xi32> {
|
||||
%lhs = arith.constant dense<[7, -7, 7, -7]> : tensor<4xi32>
|
||||
%rhs = arith.constant dense<[2, 2, -2, -2]> : tensor<4xi32>
|
||||
|
||||
// CHECK: %[[CST:.*]] = arith.constant dense<[3, -4, -4, 3]> : tensor<4xi32>
|
||||
// CHECK-NOT: tfl.floor_div
|
||||
// CHECK: return %[[CST]]
|
||||
|
||||
%0 = "tfl.floor_div"(%lhs, %rhs) : (tensor<4xi32>, tensor<4xi32>) -> tensor<4xi32>
|
||||
func.return %0 : tensor<4xi32>
|
||||
}
|
||||
|
||||
// CHECK-LABEL: @floor_div_i32_by_zero_not_folded
|
||||
func.func @floor_div_i32_by_zero_not_folded() -> tensor<2xi32> {
|
||||
%lhs = arith.constant dense<[4, 5]> : tensor<2xi32>
|
||||
%rhs = arith.constant dense<[2, 0]> : tensor<2xi32>
|
||||
|
||||
// CHECK: tfl.floor_div
|
||||
|
||||
%0 = "tfl.floor_div"(%lhs, %rhs) : (tensor<2xi32>, tensor<2xi32>) -> tensor<2xi32>
|
||||
func.return %0 : tensor<2xi32>
|
||||
}
|
||||
|
||||
// Like the kernel, divides in double: 1.0f / 0.1f rounds to exactly 10.0f in
|
||||
// f32, but is 9.99999985 in double, so the floor is 9.
|
||||
// CHECK-LABEL: @floor_div_f32_divides_in_double
|
||||
func.func @floor_div_f32_divides_in_double() -> tensor<f32> {
|
||||
%lhs = arith.constant dense<1.0> : tensor<f32>
|
||||
%rhs = arith.constant dense<0.1> : tensor<f32>
|
||||
|
||||
// CHECK: %[[CST:.*]] = arith.constant dense<9.000000e+00> : tensor<f32>
|
||||
// CHECK-NOT: tfl.floor_div
|
||||
// CHECK: return %[[CST]]
|
||||
|
||||
%0 = "tfl.floor_div"(%lhs, %rhs) : (tensor<f32>, tensor<f32>) -> tensor<f32>
|
||||
func.return %0 : tensor<f32>
|
||||
}
|
||||
|
||||
// CHECK-LABEL: @floor_div_f32_by_zero_not_folded
|
||||
func.func @floor_div_f32_by_zero_not_folded() -> tensor<2xf32> {
|
||||
%lhs = arith.constant dense<[4.0, 5.0]> : tensor<2xf32>
|
||||
%rhs = arith.constant dense<[2.0, -0.0]> : tensor<2xf32>
|
||||
|
||||
// CHECK: tfl.floor_div
|
||||
|
||||
%0 = "tfl.floor_div"(%lhs, %rhs) : (tensor<2xf32>, tensor<2xf32>) -> tensor<2xf32>
|
||||
func.return %0 : tensor<2xf32>
|
||||
}
|
||||
|
||||
// CHECK-LABEL: @floor_div_runtime_not_folded
|
||||
func.func @floor_div_runtime_not_folded(%arg0: tensor<4xf32>) -> tensor<4xf32> {
|
||||
%rhs = arith.constant dense<2.0> : tensor<f32>
|
||||
|
||||
// CHECK: tfl.floor_div
|
||||
|
||||
%0 = "tfl.floor_div"(%arg0, %rhs) : (tensor<4xf32>, tensor<f32>) -> tensor<4xf32>
|
||||
func.return %0 : tensor<4xf32>
|
||||
}
|
||||
|
||||
// CHECK-LABEL: @rank
|
||||
func.func @rank() -> tensor<1xi32> {
|
||||
%cst = arith.constant dense<[[1], [2]]> : tensor<2x1xi32>
|
||||
|
||||
Reference in new issue
Block a user