From e1f5c0565708a7825f762ec09752235d91df91e7 Mon Sep 17 00:00:00 2001 From: "Patrick C. Toulme" Date: Sun, 20 Sep 2026 18:30:52 -0700 Subject: [PATCH 1/3] [XLA] Make the LICM size inflation check linear in the loop state width The size inflation check in WhileLoopInvariantCodeMotion ran before the invariance test. Every get-tuple-element of an N element loop state therefore walked the whole tuple shape: Theta(N^2) per loop per run. Test invariance first, so those get-tuple-elements are rejected before any shape is walked. Both filters are pure, so the hoisted set, its order and the resulting HLO are unchanged. Also skip the post order for the domain bail out scan and build the while instruction's debug string only when VLOG reads it. Adds tests for the ratio decision, tuple operand leaf counting and the linear shape size call count. PiperOrigin-RevId: 984946237 --- third_party/xla/xla/service/BUILD | 4 +- .../while_loop_invariant_code_motion.cc | 36 ++- .../while_loop_invariant_code_motion_test.cc | 260 +++++++++++++++++- 3 files changed, 278 insertions(+), 22 deletions(-) diff --git a/third_party/xla/xla/service/BUILD b/third_party/xla/xla/service/BUILD index 9fd8b08de32c83..7ee9978cea8592 100644 --- a/third_party/xla/xla/service/BUILD +++ b/third_party/xla/xla/service/BUILD @@ -4780,8 +4780,7 @@ cc_library( "//xla/hlo/pass:hlo_pass", "//xla/hlo/transforms/simplifiers:hlo_dce", "//xla/hlo/transforms/simplifiers:tuple_simplifier", - "//xla/tsl/platform:errors", - "//xla/tsl/platform:statusor", + "//xla/tsl/platform:logging", "@com_google_absl//absl/algorithm:container", "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/container:flat_hash_set", @@ -4810,6 +4809,7 @@ xla_cc_test( "//xla/tests:xla_internal_test_main", "@com_google_absl//absl/log", "@com_google_absl//absl/status:status_matchers", + "@com_google_absl//absl/status:statusor", "@com_google_googletest//:gtest", ], ) diff --git a/third_party/xla/xla/service/while_loop_invariant_code_motion.cc b/third_party/xla/xla/service/while_loop_invariant_code_motion.cc index 208cd413ef499c..efeb41a17ff8fb 100644 --- a/third_party/xla/xla/service/while_loop_invariant_code_motion.cc +++ b/third_party/xla/xla/service/while_loop_invariant_code_motion.cc @@ -27,6 +27,7 @@ limitations under the License. #include "absl/log/check.h" #include "absl/log/log.h" #include "absl/status/status_macros.h" +#include "absl/status/statusor.h" #include "absl/strings/string_view.h" #include "xla/hlo/analysis/while_loop_analysis.h" #include "xla/hlo/ir/hlo_computation.h" @@ -41,8 +42,7 @@ limitations under the License. #include "xla/service/while_util.h" #include "xla/shape.h" #include "xla/shape_util.h" -#include "xla/tsl/platform/errors.h" -#include "xla/tsl/platform/statusor.h" +#include "xla/tsl/platform/logging.h" #include "xla/util.h" namespace xla { @@ -100,7 +100,11 @@ WhileLoopInvariantCodeMotion::TryHoistingInvariantInstructionsFromWhileBody( return false; } - std::string while_instr_name = while_instr->ToString(print_no_metadata); + // Only used by VLOG, and the string carries the whole loop state shape. + std::string while_instr_name; + if (VLOG_IS_ON(1)) { + while_instr_name = while_instr->ToString(print_no_metadata); + } VLOG(2) << "Trying to hoist from " << while_instr_name; auto maybe_upper_bound = ComputeWhileLoopTripCountUpperBound(while_instr); @@ -146,7 +150,8 @@ WhileLoopInvariantCodeMotion::TryHoistingInvariantInstructionsFromWhileBody( return false; } - for (auto* instruction : while_body->MakeInstructionPostOrder()) { + // This scan bails on any match, so it does not need the post order. + for (const auto* instruction : while_body->instructions()) { // LICM in the presence of domain instructions is complex, bail. if (instruction->opcode() == HloOpcode::kDomain || instruction->IsCustomCall("SPMDFullToShardShape") || @@ -167,6 +172,12 @@ WhileLoopInvariantCodeMotion::TryHoistingInvariantInstructionsFromWhileBody( std::vector instructions_to_replace; std::vector replacement_instructions; + const auto is_invariant = [&](HloInstruction* op) { + return op->opcode() == HloOpcode::kConstant || + hoisted_instructions.contains(op) || + unhoisted_invariant_instructions.contains(op); + }; + for (auto* instruction : while_body->MakeInstructionPostOrder()) { allowance->DeductCost(1); if (!allowance->ContinueAnalysis()) { @@ -185,6 +196,13 @@ WhileLoopInvariantCodeMotion::TryHoistingInvariantInstructionsFromWhileBody( instruction->opcode() != HloOpcode::kReshape) { continue; } + + // Cheap check first. Both checks are pure filters, so their order does not + // change which instructions are hoisted. + if (!absl::c_all_of(instruction->operands(), is_invariant)) { + continue; + } + // Constants don't inflate, so size inflation check doesn't make sense for // constants. if (hoist_size_inflation_ratio_ && @@ -220,16 +238,6 @@ WhileLoopInvariantCodeMotion::TryHoistingInvariantInstructionsFromWhileBody( } } - auto is_invariant = [&](HloInstruction* op) { - return hoisted_instructions.find(op) != hoisted_instructions.end() || - unhoisted_invariant_instructions.contains(op) || - op->opcode() == HloOpcode::kConstant; - }; - - if (!absl::c_all_of(instruction->operands(), is_invariant)) { - continue; - } - if (NotWorthHoistingIndividually(*instruction)) { VLOG(2) << "Adding " << instruction->ToString(print_no_metadata) << " to unhoisted invariant set."; diff --git a/third_party/xla/xla/service/while_loop_invariant_code_motion_test.cc b/third_party/xla/xla/service/while_loop_invariant_code_motion_test.cc index 50ddfab37252d3..6772be7175bc99 100644 --- a/third_party/xla/xla/service/while_loop_invariant_code_motion_test.cc +++ b/third_party/xla/xla/service/while_loop_invariant_code_motion_test.cc @@ -17,10 +17,12 @@ limitations under the License. #include #include +#include #include #include "absl/log/log.h" #include "absl/status/status_matchers.h" +#include "absl/status/statusor.h" #include "xla/hlo/ir/hlo_computation.h" #include "xla/hlo/ir/hlo_instruction.h" #include "xla/hlo/ir/hlo_module.h" @@ -501,7 +503,8 @@ ENTRY entry { )"; TEST_F(WhileLoopInvariantCodeMotionTest, HoistsConstantWhenAsked) { - auto m = ParseAndReturnVerifiedModule(kConstantHoistingTestCase).value(); + ASSERT_OK_AND_ASSIGN(std::unique_ptr m, + ParseAndReturnVerifiedModule(kConstantHoistingTestCase)); ASSERT_OK_AND_ASSIGN( bool simplified_loop, @@ -533,7 +536,8 @@ TEST_F(WhileLoopInvariantCodeMotionTest, HoistsConstantWhenAsked) { } TEST_F(WhileLoopInvariantCodeMotionTest, DoesNotHoistConstantByDefault) { - auto m = ParseAndReturnVerifiedModule(kConstantHoistingTestCase).value(); + ASSERT_OK_AND_ASSIGN(std::unique_ptr m, + ParseAndReturnVerifiedModule(kConstantHoistingTestCase)); ASSERT_OK_AND_ASSIGN(bool simplified_loop, WhileLoopInvariantCodeMotion{}.Run(m.get())); @@ -605,7 +609,8 @@ ENTRY entry { )"; TEST_F(WhileLoopInvariantCodeMotionTest, HoistsInflatingByDefault) { - auto m = ParseAndReturnVerifiedModule(kInflatingTestCase).value(); + ASSERT_OK_AND_ASSIGN(std::unique_ptr m, + ParseAndReturnVerifiedModule(kInflatingTestCase)); ASSERT_OK_AND_ASSIGN( bool simplified_loop, @@ -617,16 +622,259 @@ TEST_F(WhileLoopInvariantCodeMotionTest, HoistsInflatingByDefault) { EXPECT_THAT(while_body->instructions(), Not(Contains(op::Iota()))); } +// The previous call misbound its positional arguments (1.0 landed on +// hoist_other), so the ratio was never set and the check never ran. TEST_F(WhileLoopInvariantCodeMotionTest, NoHoistInflating) { - auto m = ParseAndReturnVerifiedModule(kInflatingTestCase).value(); + ASSERT_OK_AND_ASSIGN(std::unique_ptr m, + ParseAndReturnVerifiedModule(kInflatingTestCase)); + + ASSERT_OK_AND_ASSIGN( + bool simplified_loop, + WhileLoopInvariantCodeMotion(/*hoist_constants=*/true, + /*hoist_reshapes=*/false, + /*hoist_other=*/true, + /*hoist_size_inflation_ratio=*/1.0) + .Run(m.get())); + // The constant is hoisted; the iota inflates and stays in the loop. + EXPECT_TRUE(simplified_loop); + + HloComputation* while_body = m->GetComputationWithName("wide.body"); + ASSERT_NE(while_body, nullptr); + EXPECT_THAT(while_body->instructions(), Contains(op::Iota())); + EXPECT_THAT(while_body->instructions(), Not(Contains(op::Constant()))); +} + +const char* const kInflatingChainTestCase = R"( +HloModule ModuleWithWhile + +body { + p_body = (f32[8], f32[8], f32[8,1024]) parameter(0) + invariant = f32[8] get-tuple-element(p_body), index=0 + negate = f32[8] negate(invariant) + broadcast = f32[8,1024] broadcast(invariant), dimensions={0} + negate_broadcast = f32[8,1024] negate(broadcast) + ROOT root = (f32[8], f32[8], f32[8,1024]) tuple(invariant, negate, negate_broadcast) +} + +condition { + p_cond = (f32[8], f32[8], f32[8,1024]) parameter(0) + ROOT result = pred[] constant(true) +} + +ENTRY entry { + param = (f32[8], f32[8], f32[8,1024]) parameter(0) + ROOT while = (f32[8], f32[8], f32[8,1024]) while(param), condition=condition, body=body +} +)"; + +TEST_F(WhileLoopInvariantCodeMotionTest, HoistsInflatingChainWithoutRatio) { + ASSERT_OK_AND_ASSIGN(std::unique_ptr m, + ParseAndReturnVerifiedModule(kInflatingChainTestCase)); + + ASSERT_OK_AND_ASSIGN(bool simplified_loop, + WhileLoopInvariantCodeMotion{}.Run(m.get())); + EXPECT_TRUE(simplified_loop); + + HloInstruction* transformed_while; + FindOnlyWhileInstruction(m->entry_computation(), &transformed_while); + EXPECT_THAT(transformed_while->while_body()->instructions(), + Each(Not(AnyOf(op::Negate(), op::Broadcast())))); + EXPECT_THAT(m->entry_computation()->instructions(), + Contains(op::Negate(op::Broadcast()))); +} + +TEST_F(WhileLoopInvariantCodeMotionTest, + SizeInflationRatioRejectsOnlyInflatingInstructions) { + ASSERT_OK_AND_ASSIGN(std::unique_ptr m, + ParseAndReturnVerifiedModule(kInflatingChainTestCase)); ASSERT_OK_AND_ASSIGN( bool simplified_loop, WhileLoopInvariantCodeMotion(/*hoist_constants=*/false, - /*hoist_non_constants=*/true, + /*hoist_reshapes=*/false, + /*hoist_other=*/true, /*hoist_size_inflation_ratio=*/1.0) .Run(m.get())); - EXPECT_FALSE(simplified_loop); + EXPECT_TRUE(simplified_loop); + + // negate(invariant) does not inflate and is hoisted. The broadcast inflates + // and stays in the loop, so its negate has an unhoisted operand and stays + // too. + HloInstruction* transformed_while; + FindOnlyWhileInstruction(m->entry_computation(), &transformed_while); + EXPECT_THAT( + m->entry_computation()->instructions(), + AllOf(Contains(op::Negate(op::GetTupleElement(op::Parameter(0), 0))), + Not(Contains(op::Broadcast())))); + EXPECT_THAT(transformed_while->while_body()->instructions(), + AllOf(Contains(op::Negate(op::Broadcast())), + Each(Not(op::Negate(op::GetTupleElement()))))); +} + +TEST_F(WhileLoopInvariantCodeMotionTest, + SizeInflationRatioNeverHoistsLoopVaryingOperands) { + // Both converts shrink their input, so the ratio permits them; only the one + // with an invariant operand may be hoisted. + const char* const kHloModule = R"( +HloModule ModuleWithWhile + +body { + p_body = (f32[8,128], f32[8,128], bf16[8,128], bf16[8,128]) parameter(0) + invariant = f32[8,128] get-tuple-element(p_body), index=0 + varying = f32[8,128] get-tuple-element(p_body), index=1 + convert_invariant = bf16[8,128] convert(invariant) + convert_varying = bf16[8,128] convert(varying) + negate = f32[8,128] negate(varying) + ROOT root = (f32[8,128], f32[8,128], bf16[8,128], bf16[8,128]) tuple(invariant, negate, convert_invariant, convert_varying) +} + +condition { + p_cond = (f32[8,128], f32[8,128], bf16[8,128], bf16[8,128]) parameter(0) + ROOT result = pred[] constant(true) +} + +ENTRY entry { + param = (f32[8,128], f32[8,128], bf16[8,128], bf16[8,128]) parameter(0) + ROOT while = (f32[8,128], f32[8,128], bf16[8,128], bf16[8,128]) while(param), condition=condition, body=body +} +)"; + ASSERT_OK_AND_ASSIGN(std::unique_ptr m, + ParseAndReturnVerifiedModule(kHloModule)); + + ASSERT_OK_AND_ASSIGN( + bool simplified_loop, + WhileLoopInvariantCodeMotion(/*hoist_constants=*/false, + /*hoist_reshapes=*/false, + /*hoist_other=*/true, + /*hoist_size_inflation_ratio=*/1.0) + .Run(m.get())); + EXPECT_TRUE(simplified_loop); + + HloInstruction* transformed_while; + FindOnlyWhileInstruction(m->entry_computation(), &transformed_while); + EXPECT_THAT(m->entry_computation()->instructions(), + Contains(op::Convert(op::GetTupleElement(op::Parameter(0), 0)))); + EXPECT_THAT(transformed_while->while_body()->instructions(), + Contains(op::Convert(op::GetTupleElement(op::Parameter(0), 1)))); + EXPECT_THAT(transformed_while->while_body()->instructions(), + Each(Not(op::Convert(op::GetTupleElement(op::Parameter(0), 0))))); +} + +TEST_F(WhileLoopInvariantCodeMotionTest, + SizeInflationCountsAllLeavesOfTupleOperands) { + // get-tuple-element(custom-call), index=1 produces 2048 bytes out of a 2080 + // byte tuple operand: hoistable at ratio 1.0 only if the operand tuple's + // leaves are summed; a tuple counted as size 0 rejects it. + const char* const kHloModule = R"( +HloModule ModuleWithWhile + +body { + p_body = (f32[8], f32[8,64], f32[8,64]) parameter(0) + invariant_a = f32[8] get-tuple-element(p_body), index=0 + invariant_b = f32[8,64] get-tuple-element(p_body), index=1 + custom_call = (f32[8], f32[8,64]) custom-call(invariant_a, invariant_b), custom_call_target="Foo" + element = f32[8,64] get-tuple-element(custom_call), index=1 + negate = f32[8,64] negate(element) + ROOT root = (f32[8], f32[8,64], f32[8,64]) tuple(invariant_a, invariant_b, negate) +} + +condition { + p_cond = (f32[8], f32[8,64], f32[8,64]) parameter(0) + ROOT result = pred[] constant(true) +} + +ENTRY entry { + param = (f32[8], f32[8,64], f32[8,64]) parameter(0) + ROOT while = (f32[8], f32[8,64], f32[8,64]) while(param), condition=condition, body=body +} +)"; + ASSERT_OK_AND_ASSIGN(std::unique_ptr m, + ParseAndReturnVerifiedModule(kHloModule)); + + ASSERT_OK_AND_ASSIGN( + bool simplified_loop, + WhileLoopInvariantCodeMotion(/*hoist_constants=*/false, + /*hoist_reshapes=*/false, + /*hoist_other=*/true, + /*hoist_size_inflation_ratio=*/1.0) + .Run(m.get())); + EXPECT_TRUE(simplified_loop); + + HloInstruction* transformed_while; + FindOnlyWhileInstruction(m->entry_computation(), &transformed_while); + EXPECT_THAT(m->entry_computation()->instructions(), + Contains(op::Negate(op::GetTupleElement(op::CustomCall(), 1)))); + EXPECT_THAT(transformed_while->while_body()->instructions(), + Each(Not(AnyOf(op::CustomCall(), op::Negate())))); +} + +// Runs the pass with hoist_size_inflation_ratio = 1.0 and counts the +// shape_size_function calls, which is how often the size inflation test +// visits an array leaf. +class ShapeSizeCallCountTest : public WhileLoopInvariantCodeMotionTest { + protected: + static constexpr int kStateWidth = 64; + // Loose linear bound on shape_size_function calls: the fixed pass makes + // 2 * kStateWidth + 2 calls in the test below; a walk of the tuple shape per + // user makes about kStateWidth * kStateWidth. + static constexpr int kMaxLinearCalls = 4 * kStateWidth + 16; + + absl::StatusOr RunWithCountingShapeSizeFunction(HloModule* module) { + return WhileLoopInvariantCodeMotion( + /*hoist_constants=*/false, /*hoist_reshapes=*/false, + /*hoist_other=*/true, /*hoist_size_inflation_ratio=*/1.0, + /*shape_size_function=*/ + [this](const Shape& shape) { + ++shape_size_calls_; + return ShapeUtil::ByteSizeOfElements(shape); + }) + .Run(module); + } + + int64_t shape_size_calls_ = 0; +}; + +TEST_F(ShapeSizeCallCountTest, LinearInLoopStateWidth) { + auto m = CreateNewVerifiedModule(); + Shape array_f32 = ShapeUtil::MakeShape(F32, {8}); + Shape while_shape = + ShapeUtil::MakeTupleShape(std::vector(kStateWidth, array_f32)); + + // All state elements are loop invariant except the last, which receives + // negate(element 0): the single hoisting candidate. + HloComputation* while_body = [&]() { + HloComputation::Builder builder(TestName() + ".while_body"); + HloInstruction* param = builder.AddInstruction( + HloInstruction::CreateParameter(0, while_shape, "param")); + std::vector root_operands; + for (int i = 0; i + 1 < kStateWidth; ++i) { + root_operands.push_back(builder.AddInstruction( + HloInstruction::CreateGetTupleElement(array_f32, param, i))); + } + root_operands.push_back(builder.AddInstruction(HloInstruction::CreateUnary( + array_f32, HloOpcode::kNegate, root_operands.front()))); + builder.AddInstruction(HloInstruction::CreateTuple(root_operands)); + return m->AddEmbeddedComputation(builder.Build()); + }(); + + HloComputation::Builder builder(TestName()); + HloInstruction* init_value = builder.AddInstruction( + HloInstruction::CreateParameter(0, while_shape, "init_value")); + builder.AddInstruction(HloInstruction::CreateWhile( + while_shape, MakeAlwaysTrueComputation(while_shape, m.get()), while_body, + init_value)); + HloComputation* entry_computation = m->AddEntryComputation(builder.Build()); + + ASSERT_OK_AND_ASSIGN(bool simplified_loop, + RunWithCountingShapeSizeFunction(m.get())); + EXPECT_TRUE(simplified_loop); + EXPECT_THAT(entry_computation->instructions(), Contains(op::Negate())); + + // Every get-tuple-element of the state has the parameter as its operand and + // cannot be hoisted. Walking the kStateWidth element parameter shape for + // each of them would cost kStateWidth * kStateWidth calls. + EXPECT_GT(shape_size_calls_, 0); + EXPECT_LE(shape_size_calls_, kMaxLinearCalls); } TEST_F(WhileLoopInvariantCodeMotionTest, DoesNotHoistSPMDFullToShardShape) { From 0181b74d80f2a9aa356e4a9dc9f35f6bc043233e Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Sun, 20 Sep 2026 21:05:46 -0700 Subject: [PATCH 2/3] Automated Code Change PiperOrigin-RevId: 984990786 --- tensorflow/core/kernels/image/extract_volume_patches_op.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/core/kernels/image/extract_volume_patches_op.cc b/tensorflow/core/kernels/image/extract_volume_patches_op.cc index 52774b698afe20..8b4c17f5cb49d0 100644 --- a/tensorflow/core/kernels/image/extract_volume_patches_op.cc +++ b/tensorflow/core/kernels/image/extract_volume_patches_op.cc @@ -130,9 +130,9 @@ class ExtractVolumePatchesOp : public UnaryOp { MultiplyWithoutOverflow(ksize_rows, MultiplyWithoutOverflow(ksize_cols, depth))); OP_REQUIRES(context, patch_size >= 0, - errors::InvalidArgument( + absl::InvalidArgumentError(absl::StrCat( "Output size would overflow: ", ksize_planes, " x ", - ksize_rows, " x ", ksize_cols, " x ", depth)); + ksize_rows, " x ", ksize_cols, " x ", depth))); const std::vector out_sizes = {batch, out_planes, out_rows, out_cols, patch_size}; From 1e77850e2570a5af566fb4a65a8f247835e7f7f7 Mon Sep 17 00:00:00 2001 From: Alexandru Paiu Date: Sun, 20 Sep 2026 22:27:53 -0700 Subject: [PATCH 3/3] Add build targets and allowlist files for LiteRT ML Drift GPU compatibility. PiperOrigin-RevId: 985023382 --- .../acceleration/compatibility/BUILD | 52 ++++++++++++++---- .../compatibility/mldrift_compatibility.bin | Bin 0 -> 85748 bytes 2 files changed, 41 insertions(+), 11 deletions(-) create mode 100755 tensorflow/lite/experimental/acceleration/compatibility/mldrift_compatibility.bin diff --git a/tensorflow/lite/experimental/acceleration/compatibility/BUILD b/tensorflow/lite/experimental/acceleration/compatibility/BUILD index 3d3f23a017b6fd..db4dc4f6a979ef 100644 --- a/tensorflow/lite/experimental/acceleration/compatibility/BUILD +++ b/tensorflow/lite/experimental/acceleration/compatibility/BUILD @@ -100,9 +100,9 @@ genrule( ], outs = ["devicedb-sample.bin"], cmd = """ - $(location :json_to_fb) \ - --fbs=$(location :database.fbs) \ - --json_input=$(location :devicedb-sample.json) \ + $(execpath :json_to_fb) \ + --fbs=$(execpath :database.fbs) \ + --json_input=$(execpath :devicedb-sample.json) \ --fb_output=$(@) """, tools = [":json_to_fb"], @@ -125,10 +125,10 @@ genrule( # convert_file_to_c_source for some reason doesn't define the global with # 'extern', which is needed for global const variables in C++. cmd = """ - $(location :convert_binary_to_cc_source) \ - --input_binary_file $(location :devicedb-sample.bin) \ - --output_header_file $(location :devicedb-sample.h) \ - --output_source_file $(location :devicedb-sample.cc) \ + $(execpath :convert_binary_to_cc_source) \ + --input_binary_file $(execpath :devicedb-sample.bin) \ + --output_header_file $(execpath :devicedb-sample.h) \ + --output_source_file $(execpath :devicedb-sample.cc) \ --array_variable_name g_tflite_acceleration_devicedb_sample_binary """, tools = [":convert_binary_to_cc_source"], @@ -162,6 +162,7 @@ cc_test( exports_files([ "database.fbs", "gpu_compatibility.bin", + "mldrift_compatibility.bin", ]) genrule( @@ -174,15 +175,34 @@ genrule( # convert_file_to_c_source for some reason doesn't define the global with # 'extern', which is needed for global const variables in C++. cmd = """ - $(location :convert_binary_to_cc_source) \ - --input_binary_file $(location :gpu_compatibility.bin) \ - --output_header_file $(location :gpu_compatibility_binary.h) \ - --output_source_file $(location :gpu_compatibility_binary.cc) \ + $(execpath :convert_binary_to_cc_source) \ + --input_binary_file $(execpath :gpu_compatibility.bin) \ + --output_header_file $(execpath :gpu_compatibility_binary.h) \ + --output_source_file $(execpath :gpu_compatibility_binary.cc) \ --array_variable_name g_tflite_acceleration_gpu_compatibility_binary """, tools = [":convert_binary_to_cc_source"], ) +genrule( + name = "mldrift_compatibility_binary", + srcs = ["mldrift_compatibility.bin"], + outs = [ + "mldrift_compatibility_binary.h", + "mldrift_compatibility_binary.cc", + ], + # The array variable name must differ from the gpu_compatibility one above: + # both allowlists may end up linked into the same binary. + cmd = """ + $(execpath :convert_binary_to_cc_source) \ + --input_binary_file $(execpath :mldrift_compatibility.bin) \ + --output_header_file $(execpath :mldrift_compatibility_binary.h) \ + --output_source_file $(execpath :mldrift_compatibility_binary.cc) \ + --array_variable_name g_tflite_acceleration_mldrift_compatibility_binary + """, + tools = [":convert_binary_to_cc_source"], +) + cc_library( name = "android_info", srcs = ["android_info.cc"], @@ -198,6 +218,16 @@ cc_library( hdrs = [":gpu_compatibility_binary.h"], ) +# Deliberately a target of its own rather than an addition to the library above: +# a client that only cares about ML Drift should not link the (much larger) +# TFLite GPU allowlist, and vice versa. Both binaries are consumed through the +# generic GPUCompatibilityList::Create(flatbuffer, length) overload. +cc_library( + name = "mldrift_compatibility_binary_embed", + srcs = [":mldrift_compatibility_binary.cc"], + hdrs = [":mldrift_compatibility_binary.h"], +) + cc_library( name = "gpu_compatibility", hdrs = ["gpu_compatibility.h"], diff --git a/tensorflow/lite/experimental/acceleration/compatibility/mldrift_compatibility.bin b/tensorflow/lite/experimental/acceleration/compatibility/mldrift_compatibility.bin new file mode 100755 index 0000000000000000000000000000000000000000..2d8ae7f3fc66b857e5ccdc98dc3c8d2c82f1f464 GIT binary patch literal 85748 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≺PXG@O?}^t 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