From f81173ac6fa173a4006fda5b9e1f605904f0e9ef Mon Sep 17 00:00:00 2001 From: ddavis-2015 Date: Mon, 31 Aug 2026 03:18:17 -0700 Subject: [PATCH 1/4] Application custom DECODE operator support (part 1) @tensorflow/micro This is part 1 of 2 to add support for custom DECODE operators and their registration. Part 2 will add this support to the Python MicroInterpreter wrapper. Add unit tests for custom DECODE operator registration. Re-enable -Werror in the Makefile. Fix minor comment typos. bug=fixes #3215 --- tensorflow/lite/micro/kernels/decode.cc | 44 ++++- tensorflow/lite/micro/kernels/decode_state.h | 3 +- .../kernels/decode_state_huffman_test.cc | 2 +- .../micro/kernels/decode_state_prune_test.cc | 2 +- tensorflow/lite/micro/kernels/decode_test.cc | 183 ++++++++++++++++++ .../lite/micro/kernels/decode_test_helpers.h | 11 +- tensorflow/lite/micro/micro_context.cc | 10 + tensorflow/lite/micro/micro_context.h | 28 ++- tensorflow/lite/micro/micro_interpreter.cc | 5 + tensorflow/lite/micro/micro_interpreter.h | 12 ++ .../lite/micro/micro_interpreter_context.cc | 8 + .../lite/micro/micro_interpreter_context.h | 5 + .../micro/micro_interpreter_context_test.cc | 52 +++++ tensorflow/lite/micro/tools/make/Makefile | 1 + 14 files changed, 350 insertions(+), 16 deletions(-) diff --git a/tensorflow/lite/micro/kernels/decode.cc b/tensorflow/lite/micro/kernels/decode.cc index 9f4d34cff15..92391c6d24e 100644 --- a/tensorflow/lite/micro/kernels/decode.cc +++ b/tensorflow/lite/micro/kernels/decode.cc @@ -13,6 +13,8 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ +#include + #include "tensorflow/lite/c/common.h" #include "tensorflow/lite/kernels/internal/compatibility.h" #include "tensorflow/lite/kernels/kernel_util.h" @@ -49,6 +51,27 @@ TfLiteStatus SetOutputTensorData(TfLiteContext* context, const TfLiteNode* node, return kTfLiteOk; } +DecodeState* GetDecodeStateFromCustomRegistration(const TfLiteContext* context, + uint8_t type) { + const MicroContext* mc = GetMicroContext(context); + const MicroContext::CustomDecodeRegistration* registrations; + size_t registrations_count; + std::tie(registrations, registrations_count) = + mc->GetCustomDecodeRegistrations(); + if (registrations == nullptr) { + return nullptr; + } + + for (size_t i = 0; i < registrations_count; i++) { + auto& reg = registrations[i]; + if (reg.type == type && reg.create_state != nullptr) { + return reg.create_state(reg, *context, mc->GetAlternateProfiler()); + } + } + + return nullptr; +} + TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { const size_t num_inputs = NumInputs(node); const size_t num_outputs = NumOutputs(node); @@ -113,21 +136,22 @@ TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { dsp = DecodeState::CreateDecodeStateHuffman( context, micro_context->GetAlternateProfiler()); break; - case DecodeState::kDcmTypeCustom: - MicroPrintf("Custom decode type not yet supported"); - break; default: - MicroPrintf("unsupported decode type %u", - DecodeState::Type(*ancillary)); + uint32_t type = DecodeState::Type(*ancillary); + if (type >= DecodeState::kDcmTypeCustomFirst && + type <= DecodeState::kDcmTypeCustomLast) { + dsp = GetDecodeStateFromCustomRegistration(context, type); + } else { + MicroPrintf("unsupported decode type %u", type); + } break; } - status = SetOutputTensorData(context, node, i / 2, output); - if (status != kTfLiteOk) { - break; - } - if (dsp != nullptr) { + status = SetOutputTensorData(context, node, i / 2, output); + if (status != kTfLiteOk) { + break; + } status = dsp->Setup(*input, *ancillary, *output); if (status != kTfLiteOk) { break; diff --git a/tensorflow/lite/micro/kernels/decode_state.h b/tensorflow/lite/micro/kernels/decode_state.h index 06f821dbc3c..9be36e32de3 100644 --- a/tensorflow/lite/micro/kernels/decode_state.h +++ b/tensorflow/lite/micro/kernels/decode_state.h @@ -72,7 +72,8 @@ class DecodeState { static constexpr uint8_t kDcmTypeLUT = 0; static constexpr uint8_t kDcmTypeHuffman = 1; static constexpr uint8_t kDcmTypePrune = 2; - static constexpr uint8_t kDcmTypeCustom = 127; + static constexpr uint8_t kDcmTypeCustomFirst = 128; + static constexpr uint8_t kDcmTypeCustomLast = 255; static constexpr size_t kDcmSizeInBytes = 16; diff --git a/tensorflow/lite/micro/kernels/decode_state_huffman_test.cc b/tensorflow/lite/micro/kernels/decode_state_huffman_test.cc index 089414e2184..feebca24e48 100644 --- a/tensorflow/lite/micro/kernels/decode_state_huffman_test.cc +++ b/tensorflow/lite/micro/kernels/decode_state_huffman_test.cc @@ -269,7 +269,7 @@ TEST(DecodeStateHuffmanTest, DecodeHuffmanTable16BitsInt16Fail) { tflite::testing::TestDecode( encodes, ancillaries, outputs, expected, tflite::Register_DECODE(), - nullptr, kTfLiteError); + nullptr, nullptr, kTfLiteError); } TEST(DecodeStateHuffmanTest, DecodeHuffmanTable32BitsInt8) { diff --git a/tensorflow/lite/micro/kernels/decode_state_prune_test.cc b/tensorflow/lite/micro/kernels/decode_state_prune_test.cc index 1144913fa64..d268bd6369e 100644 --- a/tensorflow/lite/micro/kernels/decode_state_prune_test.cc +++ b/tensorflow/lite/micro/kernels/decode_state_prune_test.cc @@ -573,7 +573,7 @@ TEST(DecodeStatePruneTest, DecodePruneQuantizedInvalidZeroPointInt16) { tflite::testing::TestDecode( kEncodes, kAncillaries, kOutputs, kExpected, tflite::Register_DECODE(), - nullptr, kTfLiteError); + nullptr, nullptr, kTfLiteError); } TF_LITE_MICRO_TESTS_MAIN diff --git a/tensorflow/lite/micro/kernels/decode_test.cc b/tensorflow/lite/micro/kernels/decode_test.cc index 376fa9c586d..ba24452c439 100644 --- a/tensorflow/lite/micro/kernels/decode_test.cc +++ b/tensorflow/lite/micro/kernels/decode_test.cc @@ -66,6 +66,77 @@ constexpr int kEncodedShapeLUT[] = {1, sizeof(kEncodedLUT)}; constexpr int8_t kExpectLUT0[] = {1, 2, 3, 4, 4, 3, 2, 1}; constexpr int16_t kExpectLUT1[] = {5, 6, 7, 8, 8, 7, 6, 5}; +// +// Custom DECODE test data +// +constexpr int kDecodeTypeCustom = 200; + +constexpr int8_t kAncillaryDataCustom[] = {0x42}; + +constexpr uint8_t kDcmCustom[tflite::DecodeState::kDcmSizeInBytes] = { + kDecodeTypeCustom, // type: custom + 1, // DCM version: 1 +}; + +// Align the tensor data the same as a Buffer in the TfLite schema +alignas(16) const uint8_t kEncodedCustom[] = {0x42, 0x43, 0x40, 0x46, + 0x4A, 0x52, 0x62, 0x02}; + +// Tensor shapes as TfLiteIntArray +constexpr int kOutputShapeCustom[] = {1, 8}; +constexpr int kEncodedShapeCustom[] = {1, sizeof(kEncodedCustom)}; + +constexpr int8_t kExpectCustom[] = {0x00, 0x01, 0x02, 0x04, + 0x08, 0x10, 0x20, 0x40}; + +class DecodeStateCustom : public tflite::DecodeState { + public: + DecodeStateCustom() = delete; + + DecodeStateCustom(const TfLiteContext* context, + tflite::MicroProfilerInterface* profiler) + : DecodeState(context, profiler) {} + + virtual TfLiteStatus Setup(const TfLiteTensor& input, + const TfLiteTensor& ancillary, + const TfLiteTensor& output) override { + return kTfLiteOk; + } + + virtual TfLiteStatus Decode(const TfLiteEvalTensor& input, + const TfLiteEvalTensor& ancillary, + const TfLiteEvalTensor& output) override { + const uint8_t* inp = tflite::micro::GetTensorData(&input); + TF_LITE_ENSURE(const_cast(context_), inp != nullptr); + uint8_t* outp = tflite::micro::GetTensorData( + const_cast(&output)); + TF_LITE_ENSURE(const_cast(context_), outp != nullptr); + const uint8_t* vp = tflite::micro::GetTensorData(&ancillary); + TF_LITE_ENSURE(const_cast(context_), vp != nullptr); + vp += kDcmSizeInBytes; + + // simple XOR de-obfuscation + std::transform(inp, inp + input.dims->data[0], outp, + [vp](uint8_t i) { return i ^ *vp; }); + + return kTfLiteOk; + } + + static DecodeState* CreateDecodeStateCustom( + const tflite::MicroContext::CustomDecodeRegistration& registration, + const TfLiteContext& context, tflite::MicroProfilerInterface* profiler) { + alignas(DecodeStateCustom) static uint8_t buffer[sizeof(DecodeStateCustom)]; + DecodeState* instance = new (buffer) DecodeStateCustom(&context, profiler); + return instance; + } + + protected: + virtual ~DecodeStateCustom() = default; + + private: + TF_LITE_REMOVE_VIRTUAL_DELETE +}; + } // namespace using tflite::testing::AncillaryData; @@ -244,4 +315,116 @@ TEST(DecodeTest, DecodeWithAltDecompressionMemory) { encodes, ancillaries, outputs, expected, tflite::Register_DECODE(), &amr); } +TEST(DecodeTest, DecodeWithCustomRegistration) { + // Align the tensor data the same as a Buffer in the TfLite schema + alignas(16) int8_t output_data[std::size(kExpectCustom)] = {}; + alignas(16) const AncillaryData + kAncillaryData = {{kDcmCustom}, {kAncillaryDataCustom}}; + + constexpr int kAncillaryShapeCustom[] = {1, sizeof(kAncillaryData)}; + + const TfLiteIntArray* const encoded_dims = + tflite::testing::IntArrayFromInts(kEncodedShapeCustom); + static const TensorInDatum tid_encode = { + kEncodedCustom, + *encoded_dims, + }; + static constexpr std::initializer_list encodes = { + &tid_encode, + }; + + const TfLiteIntArray* const ancillary_dims = + tflite::testing::IntArrayFromInts(kAncillaryShapeCustom); + static const TensorInDatum tid_ancillary = { + &kAncillaryData, + *ancillary_dims, + }; + static constexpr std::initializer_list ancillaries = { + &tid_ancillary}; + + const TfLiteIntArray* const output_dims = + tflite::testing::IntArrayFromInts(kOutputShapeCustom); + constexpr int kOutputZeroPointsData[] = {0}; + const TfLiteIntArray* const kOutputZeroPoints = + tflite::testing::IntArrayFromInts(kOutputZeroPointsData); + const TfLiteFloatArray kOutputScales = {kOutputZeroPoints->size}; + static const TensorOutDatum tod = { + output_data, *output_dims, kTfLiteInt8, kOutputScales, *kOutputZeroPoints, + 0, {}, + }; + static constexpr std::initializer_list outputs = { + &tod}; + + const std::initializer_list expected = {kExpectCustom}; + + const std::initializer_list + cdr = { + { + DecodeStateCustom::CreateDecodeStateCustom, + kDecodeTypeCustom, + }, + }; + + tflite::testing::TestDecode( + encodes, ancillaries, outputs, expected, tflite::Register_DECODE(), + nullptr, &cdr); +} + +TEST(DecodeTest, DecodeWithCustomMismatchedRegistration) { + // Align the tensor data the same as a Buffer in the TfLite schema + alignas(16) int8_t output_data[std::size(kExpectCustom)] = {}; + alignas(16) const AncillaryData + kAncillaryData = {{kDcmCustom}, {kAncillaryDataCustom}}; + + constexpr int kAncillaryShapeCustom[] = {1, sizeof(kAncillaryData)}; + + const TfLiteIntArray* const encoded_dims = + tflite::testing::IntArrayFromInts(kEncodedShapeCustom); + static const TensorInDatum tid_encode = { + kEncodedCustom, + *encoded_dims, + }; + static constexpr std::initializer_list encodes = { + &tid_encode, + }; + + const TfLiteIntArray* const ancillary_dims = + tflite::testing::IntArrayFromInts(kAncillaryShapeCustom); + static const TensorInDatum tid_ancillary = { + &kAncillaryData, + *ancillary_dims, + }; + static constexpr std::initializer_list ancillaries = { + &tid_ancillary}; + + const TfLiteIntArray* const output_dims = + tflite::testing::IntArrayFromInts(kOutputShapeCustom); + constexpr int kOutputZeroPointsData[] = {0}; + const TfLiteIntArray* const kOutputZeroPoints = + tflite::testing::IntArrayFromInts(kOutputZeroPointsData); + const TfLiteFloatArray kOutputScales = {kOutputZeroPoints->size}; + static const TensorOutDatum tod = { + output_data, *output_dims, kTfLiteInt8, kOutputScales, *kOutputZeroPoints, + 0, {}, + }; + static constexpr std::initializer_list outputs = { + &tod}; + + const std::initializer_list expected = {kExpectCustom}; + + const std::initializer_list + cdr = { + { + DecodeStateCustom::CreateDecodeStateCustom, + kDecodeTypeCustom + 1, + }, + }; + + tflite::testing::TestDecode( + encodes, ancillaries, outputs, expected, tflite::Register_DECODE(), + nullptr, &cdr, kTfLiteError); +} + TF_LITE_MICRO_TESTS_MAIN diff --git a/tensorflow/lite/micro/kernels/decode_test_helpers.h b/tensorflow/lite/micro/kernels/decode_test_helpers.h index 47c42972415..c59b65f44b9 100644 --- a/tensorflow/lite/micro/kernels/decode_test_helpers.h +++ b/tensorflow/lite/micro/kernels/decode_test_helpers.h @@ -81,6 +81,8 @@ void ExecuteDecodeTest( const std::initializer_list& expected, TfLiteStatus expected_status, const std::initializer_list* amr = + nullptr, + const std::initializer_list* cdr = nullptr) { int kInputArrayData[kNumInputs + 1] = {kNumInputs}; for (size_t i = 0; i < kNumInputs; i++) { @@ -102,6 +104,11 @@ void ExecuteDecodeTest( amr->size()); } + if (cdr != nullptr) { + runner.GetFakeMicroContext()->SetCustomDecodeRegistrations(cdr->begin(), + cdr->size()); + } + TfLiteStatus status = runner.InitAndPrepare(); if (status == kTfLiteOk) { status = runner.Invoke(); @@ -152,6 +159,8 @@ void TestDecode( const TFLMRegistration& registration, const std::initializer_list* amr = nullptr, + const std::initializer_list* cdr = + nullptr, const TfLiteStatus expected_status = kTfLiteOk) { TfLiteTensor tensors[kNumInputs + kNumOutputs] = {}; @@ -185,7 +194,7 @@ void TestDecode( } ExecuteDecodeTest(tensors, registration, expected, - expected_status, amr); + expected_status, amr, cdr); } } // namespace testing diff --git a/tensorflow/lite/micro/micro_context.cc b/tensorflow/lite/micro/micro_context.cc index fb03ac08351..acb21688170 100644 --- a/tensorflow/lite/micro/micro_context.cc +++ b/tensorflow/lite/micro/micro_context.cc @@ -174,4 +174,14 @@ void MicroContext::ResetDecompressionMemoryAllocations() { std::fill_n(decompress_regions_allocations_, decompress_regions_size_, 0); } +TfLiteStatus MicroContext::SetCustomDecodeRegistrations( + const CustomDecodeRegistration* registrations, size_t count) { + if (custom_decode_registrations_ != nullptr) { + return kTfLiteError; + } + custom_decode_registrations_ = registrations; + custom_decode_registrations_size_ = count; + return kTfLiteOk; +} + } // namespace tflite diff --git a/tensorflow/lite/micro/micro_context.h b/tensorflow/lite/micro/micro_context.h index 0b8120abe1d..3bd337591a4 100644 --- a/tensorflow/lite/micro/micro_context.h +++ b/tensorflow/lite/micro/micro_context.h @@ -17,7 +17,7 @@ limitations under the License. #define TENSORFLOW_LITE_MICRO_MICRO_CONTEXT_H_ #include -#include +#include #include "tensorflow/lite/c/common.h" #include "tensorflow/lite/micro/micro_graph.h" @@ -33,6 +33,8 @@ namespace tflite { // TODO(b/149795762): kTfLiteAbort cannot be part of the tflite TfLiteStatus. const TfLiteStatus kTfLiteAbort = static_cast(15); +class DecodeState; // can't use decode_state.h due to circular include + // MicroContext is eventually going to become the API between TFLM and the // kernels, replacing all the functions in TfLiteContext. The end state is code // kernels to have code like: @@ -136,7 +138,7 @@ class MicroContext { }; // Set the alternate decompression memory regions. - // Can only be called during the MicroInterpreter kInit state. + // Can only be called during the kInit state. virtual TfLiteStatus SetDecompressionMemory( const AlternateMemoryRegion* regions, size_t count); @@ -169,12 +171,34 @@ class MicroContext { return nullptr; } + struct CustomDecodeRegistration { + tflite::DecodeState* (*create_state)(const CustomDecodeRegistration&, + const TfLiteContext&, + MicroProfilerInterface*); + uint8_t type; // custom decode type + }; + + // Set the DECODE operator custom registrations. + // Can only be called during the kInit state. + virtual TfLiteStatus SetCustomDecodeRegistrations( + const CustomDecodeRegistration* registrations, size_t count); + + // Get the custom decompression registrations. + virtual const std::pair + GetCustomDecodeRegistrations() const { + return std::make_pair(custom_decode_registrations_, + custom_decode_registrations_size_); + } + private: const AlternateMemoryRegion* decompress_regions_ = nullptr; size_t decompress_regions_size_ = 0; // array of size_t elements with length equal to decompress_regions_size_ size_t* decompress_regions_allocations_ = nullptr; + const CustomDecodeRegistration* custom_decode_registrations_ = nullptr; + size_t custom_decode_registrations_size_ = 0; + TF_LITE_REMOVE_VIRTUAL_DELETE }; diff --git a/tensorflow/lite/micro/micro_interpreter.cc b/tensorflow/lite/micro/micro_interpreter.cc index 1e96d50519a..111dabc1c46 100644 --- a/tensorflow/lite/micro/micro_interpreter.cc +++ b/tensorflow/lite/micro/micro_interpreter.cc @@ -349,4 +349,9 @@ TfLiteStatus MicroInterpreter::SetDecompressionMemory( return micro_context_.SetDecompressionMemory(regions, count); } +TfLiteStatus MicroInterpreter::SetCustomDecodeRegistrations( + const MicroContext::CustomDecodeRegistration* registrations, size_t count) { + return micro_context_.SetCustomDecodeRegistrations(registrations, count); +} + } // namespace tflite diff --git a/tensorflow/lite/micro/micro_interpreter.h b/tensorflow/lite/micro/micro_interpreter.h index f1de91962e2..cc788ef939a 100644 --- a/tensorflow/lite/micro/micro_interpreter.h +++ b/tensorflow/lite/micro/micro_interpreter.h @@ -174,6 +174,18 @@ class MicroInterpreter { TfLiteStatus SetDecompressionMemory( const MicroContext::AlternateMemoryRegion* regions, size_t count); + // Set the DECODE operator custom registrations. + // Can only be called during the MicroInterpreter kInit state (i.e. must + // be called before MicroInterpreter::AllocateTensors). + // The regions pointer argument is the start of a + // MicroContext::CustomDecodeRegistration array where the length of the array + // is given by the count argument. The lifetime of the + // MicroContext::CustomDecodeRegistration array must be at least that of the + // MicroInterpreter. + TfLiteStatus SetCustomDecodeRegistrations( + const MicroContext::CustomDecodeRegistration* registrations, + size_t count); + protected: const MicroAllocator& allocator() const { return allocator_; } const TfLiteContext& context() const { return context_; } diff --git a/tensorflow/lite/micro/micro_interpreter_context.cc b/tensorflow/lite/micro/micro_interpreter_context.cc index e454509362c..841b7e7e00e 100644 --- a/tensorflow/lite/micro/micro_interpreter_context.cc +++ b/tensorflow/lite/micro/micro_interpreter_context.cc @@ -247,4 +247,12 @@ MicroProfilerInterface* MicroInterpreterContext::GetAlternateProfiler() const { return alt_profiler_; } +TfLiteStatus MicroInterpreterContext::SetCustomDecodeRegistrations( + const CustomDecodeRegistration* registrations, size_t count) { + if (state_ != InterpreterState::kInit) { + return kTfLiteError; + } + return MicroContext::SetCustomDecodeRegistrations(registrations, count); +} + } // namespace tflite diff --git a/tensorflow/lite/micro/micro_interpreter_context.h b/tensorflow/lite/micro/micro_interpreter_context.h index 72a5262f30c..8f18359d7af 100644 --- a/tensorflow/lite/micro/micro_interpreter_context.h +++ b/tensorflow/lite/micro/micro_interpreter_context.h @@ -159,6 +159,11 @@ class MicroInterpreterContext : public MicroContext { // decompression subsystem. MicroProfilerInterface* GetAlternateProfiler() const override; + // Set the DECODE operator custom registrations. + // Can only be called during the kInit state. + virtual TfLiteStatus SetCustomDecodeRegistrations( + const CustomDecodeRegistration* registrations, size_t count) override; + private: MicroAllocator& allocator_; MicroInterpreterGraph& graph_; diff --git a/tensorflow/lite/micro/micro_interpreter_context_test.cc b/tensorflow/lite/micro/micro_interpreter_context_test.cc index 84e6f8077a9..c65b12a86cc 100644 --- a/tensorflow/lite/micro/micro_interpreter_context_test.cc +++ b/tensorflow/lite/micro/micro_interpreter_context_test.cc @@ -15,7 +15,9 @@ limitations under the License. #include "tensorflow/lite/micro/micro_interpreter_context.h" #include +#include +#include "tensorflow/lite/micro/kernels/decode_state.h" #include "tensorflow/lite/micro/micro_allocator.h" #include "tensorflow/lite/micro/micro_arena_constants.h" #include "tensorflow/lite/micro/micro_interpreter_graph.h" @@ -310,4 +312,54 @@ TEST(MicroInterpreterContextTest, TestResetDecompressionMemory) { EXPECT_EQ(p, &g_alt_memory[0]); } +TEST(MicroInterpreterContextTest, TestSetCustomDecode) { + tflite::MicroInterpreterContext micro_context = + tflite::CreateMicroInterpreterContext(); + + const std::initializer_list + cdr = { + { + nullptr, // the test won't instantiate tflite::DecodeState + tflite::DecodeState::kDcmTypeCustomFirst, + }, + }; + TfLiteStatus status; + + // Test that all of the MicroInterpreterContext fences are correct, by + // forcing the MicroInterpreterContext state. The SetCustomDecodeRegistrations + // method should only be allowed during the kInit state, and can only be + // set once. + + // fail during Prepare state + micro_context.SetInterpreterState( + tflite::MicroInterpreterContext::InterpreterState::kPrepare); + status = micro_context.SetCustomDecodeRegistrations(cdr.begin(), cdr.size()); + EXPECT_EQ(status, kTfLiteError); + + // fail during Invoke state + micro_context.SetInterpreterState( + tflite::MicroInterpreterContext::InterpreterState::kInvoke); + status = micro_context.SetCustomDecodeRegistrations(cdr.begin(), cdr.size()); + EXPECT_EQ(status, kTfLiteError); + + // succeed during Init state + micro_context.SetInterpreterState( + tflite::MicroInterpreterContext::InterpreterState::kInit); + status = micro_context.SetCustomDecodeRegistrations(cdr.begin(), cdr.size()); + EXPECT_EQ(status, kTfLiteOk); + + // fail on second Init state attempt + micro_context.SetInterpreterState( + tflite::MicroInterpreterContext::InterpreterState::kInit); + status = micro_context.SetCustomDecodeRegistrations(cdr.begin(), cdr.size()); + EXPECT_EQ(status, kTfLiteError); + + // check registered info. matches + const tflite::MicroContext::CustomDecodeRegistration* registration; + size_t count; + std::tie(registration, count) = micro_context.GetCustomDecodeRegistrations(); + EXPECT_EQ(registration, cdr.begin()); + EXPECT_EQ(count, 1U); +} + TF_LITE_MICRO_TESTS_MAIN diff --git a/tensorflow/lite/micro/tools/make/Makefile b/tensorflow/lite/micro/tools/make/Makefile index db053388c6d..2a0f09ef3c9 100644 --- a/tensorflow/lite/micro/tools/make/Makefile +++ b/tensorflow/lite/micro/tools/make/Makefile @@ -161,6 +161,7 @@ endif CC_WARNINGS := \ + -Werror \ -Wsign-compare \ -Wdouble-promotion \ -Wunused-variable \ From 4dc0843bd2047d6237c4f0add9618bb3b6584565 Mon Sep 17 00:00:00 2001 From: ddavis-2015 Date: Mon, 31 Aug 2026 16:52:49 -0700 Subject: [PATCH 2/4] DECODE fix channel count for non-quantized tensors @tensorflow/micro Bring DECODE operator LUT and pruning channel count discovery in-line with design documentation. This fixes the channel count always being 1 (one) for non-quantized tensors. Parse additional field in DCM. Update unit tests. bug=fixes #3685 --- .../lite/micro/kernels/decode_state_lut.cc | 41 +++-- .../lite/micro/kernels/decode_state_lut.h | 4 +- .../micro/kernels/decode_state_lut_test.cc | 82 +++++---- .../lite/micro/kernels/decode_state_prune.cc | 46 +++-- .../lite/micro/kernels/decode_state_prune.h | 5 +- .../micro/kernels/decode_state_prune_test.cc | 167 ++++++++++++++++-- tensorflow/lite/micro/kernels/decode_test.cc | 64 ++++++- 7 files changed, 327 insertions(+), 82 deletions(-) diff --git a/tensorflow/lite/micro/kernels/decode_state_lut.cc b/tensorflow/lite/micro/kernels/decode_state_lut.cc index f638ceccc17..91c2547f73b 100644 --- a/tensorflow/lite/micro/kernels/decode_state_lut.cc +++ b/tensorflow/lite/micro/kernels/decode_state_lut.cc @@ -30,25 +30,40 @@ TfLiteStatus DecodeStateLut::Setup(const TfLiteTensor& input, const TfLiteTensor& ancillary, const TfLiteTensor& output) { const uint8_t* const ancillary_data = GetTensorData(&ancillary); - if (ancillary_data[kDcmVersionOffset] != 1) { - MicroPrintf("unsupported version %u", ancillary_data[kDcmVersionOffset]); - return kTfLiteError; + TF_LITE_ENSURE_MSG(const_cast(context_), + ancillary_data[kDcmVersionOffset] == 1, + "unsupported version %u", + ancillary_data[kDcmVersionOffset]); + + // Resolve num_channels_, use_alternate_axis_. + // Axis parameter of the DCM is used to extract the channel count dimension + // from the output tensor shape. + // Constructor defaults num_channels_ to 1 (one). + // If the axis has all masked bits set, the output tensor has a single + // channel. + const uint8_t axis_mask = + ancillary_data[kDcmParamsOffset] & kDcmParamsAxisMask; + if (axis_mask != kDcmParamsAxisMask) { + const uint8_t axis = axis_mask >> kDcmParamsAxisMaskShift; + TFLITE_DCHECK(axis < NumDimensions(&output)); + num_channels_ = SizeOfDimension(&output, axis); + + if ((axis == NumDimensions(&output) - 1)) { + if (num_channels_ > 1) { + use_alternate_axis_ = true; + } + } else { + TF_LITE_ENSURE_MSG(const_cast(context_), axis == 0, + "unsupported channel axis %u", axis); + } } - // resolve num_channels_ and use_alternate_axis_ if (output.quantization.type == kTfLiteAffineQuantization && output.quantization.params != nullptr) { const TfLiteAffineQuantization* quantization = reinterpret_cast(output.quantization.params); - num_channels_ = quantization->scale->size; - if ((quantization->quantized_dimension == output.dims->size - 1) && - num_channels_ > 1) { - use_alternate_axis_ = true; - } else if (quantization->quantized_dimension != 0) { - MicroPrintf("unsupported quantization axis %u", - quantization->quantized_dimension); - return kTfLiteError; - } + TFLITE_DCHECK(num_channels_ == + static_cast(quantization->scale->size)); } compressed_indices_ = GetTensorData(&input); diff --git a/tensorflow/lite/micro/kernels/decode_state_lut.h b/tensorflow/lite/micro/kernels/decode_state_lut.h index bff4238a086..6c0bdf8da2f 100644 --- a/tensorflow/lite/micro/kernels/decode_state_lut.h +++ b/tensorflow/lite/micro/kernels/decode_state_lut.h @@ -42,11 +42,13 @@ class DecodeStateLut : public DecodeState { static constexpr size_t kMaxBitWidth = 7; static constexpr size_t kMaxValueTableChannelStride = 128; - private: + public: // LUT Decode Common Metadata constants static constexpr size_t kDcmVersionOffset = 4; static constexpr size_t kDcmParamsOffset = 5; static constexpr uint8_t kDcmParamsBitWidthMask = 0x07; + static constexpr uint8_t kDcmParamsAxisMask = 0xF0; + static constexpr uint8_t kDcmParamsAxisMaskShift = 4; static constexpr size_t kDcmValueTableStrideOffset = 6; protected: diff --git a/tensorflow/lite/micro/kernels/decode_state_lut_test.cc b/tensorflow/lite/micro/kernels/decode_state_lut_test.cc index e50c078c154..da757b7ea33 100644 --- a/tensorflow/lite/micro/kernels/decode_state_lut_test.cc +++ b/tensorflow/lite/micro/kernels/decode_state_lut_test.cc @@ -13,6 +13,8 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ +#include "tensorflow/lite/micro/kernels/decode_state_lut.h" + #include #include #include @@ -265,19 +267,60 @@ void TestDataSetup(TestingInfo* info, TestingData* data) { info->decode_common_metadata = &data->decode_common_metadata; } +uint8_t ComputeAxis(bool use_alt_axis, bool quantized, + const TfLiteIntArray& dims) { + const uint8_t axis = use_alt_axis ? dims.size - 1 : 0; + const int channel_count = dims.data[axis]; + if (quantized || channel_count > 1) { + return axis << tflite::DecodeStateLut::kDcmParamsAxisMaskShift; + } + + return tflite::DecodeStateLut::kDcmParamsAxisMask; +} + template void TestDecompression(TestingInfo* info) { GenerateData(*info); + const int first_dim = info->use_alt_axis + ? info->total_elements / info->channel_count + : info->channel_count; + const int last_dim = info->use_alt_axis + ? info->channel_count + : info->total_elements / info->channel_count; + const int output_dims_array[] = {2, first_dim, last_dim}; + const TfLiteIntArray* const output_dims = + tflite::testing::IntArrayFromInts(output_dims_array); + // The actual zero-point and scale data are never used, + // so only supply the sizes. + const TfLiteIntArray kOutputZeroPoints = { + std::is_same::value || std::is_same::value + ? 0 + : static_cast(info->channel_count)}; + const TfLiteFloatArray kOutputScales = {kOutputZeroPoints.size}; + const TensorOutDatum tod = { + info->output, + *output_dims, + typeToTfLiteType(), + kOutputScales, + kOutputZeroPoints, + info->use_alt_axis ? output_dims->size - 1 : 0, + {}, + }; + const std::initializer_list outputs = {&tod}; + const size_t stride = info->total_value_table_elements / info->channel_count; + const uint8_t axis_param = ComputeAxis( + info->use_alt_axis, kOutputZeroPoints.size != 0, *output_dims); + const uint8_t lut_params = axis_param | static_cast(info->bit_width); *info->decode_common_metadata = { - tflite::DecodeState::kDcmTypeLUT, // type: LUT - 1, // DCM version: 1 - 0, // reserved - 0, // reserved - 1, // LUT version: 1 - static_cast(info->bit_width), // Parameters: bit-width - static_cast(stride), // value table channel stride + tflite::DecodeState::kDcmTypeLUT, // type: LUT + 1, // DCM version: 1 + 0, // reserved + 0, // reserved + 1, // LUT version: 1 + lut_params, // Parameters: axis, bit-width + static_cast(stride), // value table channel stride }; const int encoded_dims_array[] = { @@ -304,31 +347,6 @@ void TestDecompression(TestingInfo* info) { const std::initializer_list ancillaries = { &tid_ancillary}; - const int first_dim = info->use_alt_axis - ? info->total_elements / info->channel_count - : info->channel_count; - const int last_dim = info->use_alt_axis - ? info->channel_count - : info->total_elements / info->channel_count; - const int output_dims_array[] = {2, first_dim, last_dim}; - const TfLiteIntArray* const output_dims = - tflite::testing::IntArrayFromInts(output_dims_array); - // The actual zero-point and scale data are never used, - // so only supply the sizes. - const TfLiteIntArray kOutputZeroPoints = { - static_cast(info->channel_count)}; - const TfLiteFloatArray kOutputScales = {kOutputZeroPoints.size}; - const TensorOutDatum tod = { - info->output, - *output_dims, - typeToTfLiteType(), - kOutputScales, - kOutputZeroPoints, - info->use_alt_axis ? kOutputScales.size - 1 : 0, - {}, - }; - const std::initializer_list outputs = {&tod}; - const std::initializer_list expected = {info->goldens}; std::fill_n(info->output, info->total_elements, static_cast(~0ULL)); diff --git a/tensorflow/lite/micro/kernels/decode_state_prune.cc b/tensorflow/lite/micro/kernels/decode_state_prune.cc index f06d7024579..13874505af5 100644 --- a/tensorflow/lite/micro/kernels/decode_state_prune.cc +++ b/tensorflow/lite/micro/kernels/decode_state_prune.cc @@ -31,26 +31,38 @@ TfLiteStatus DecodeStatePrune::Setup(const TfLiteTensor& input, const TfLiteTensor& ancillary, const TfLiteTensor& output) { const uint8_t* const ancillary_data = GetTensorData(&ancillary); - if (ancillary_data[kDcmVersionOffset] != 1) { - MicroPrintf("unsupported version %u", ancillary_data[kDcmVersionOffset]); - return kTfLiteError; + TF_LITE_ENSURE_MSG(const_cast(context_), + ancillary_data[kDcmVersionOffset] == 1, + "unsupported version %u", + ancillary_data[kDcmVersionOffset]); + + // Resolve num_channels_, use_alternate_axis_, and zero points. + // Axis parameter of the DCM is used to extract the channel count dimension + // from the output tensor shape. + // Constructor defaults num_channels_ to 1 (one). + // If the axis has all masked bits set, the output tensor has a single + // channel. + const uint8_t axis_mask = + ancillary_data[kDcmParamsOffset] & kDcmParamsAxisMask; + if (axis_mask != kDcmParamsAxisMask) { + const uint8_t axis = axis_mask >> kDcmParamsAxisMaskShift; + TFLITE_DCHECK(axis < NumDimensions(&output)); + num_channels_ = SizeOfDimension(&output, axis); + + if ((axis == NumDimensions(&output) - 1)) { + if (num_channels_ > 1) { + use_alternate_axis_ = true; + } + } else { + TF_LITE_ENSURE_MSG(const_cast(context_), axis == 0, + "unsupported channel axis %u", axis); + } } - // resolve num_channels_, use_alternate_axis_, and zero points if (output.quantization.type == kTfLiteAffineQuantization && output.quantization.params != nullptr) { const TfLiteAffineQuantization* quantization = reinterpret_cast(output.quantization.params); - num_channels_ = quantization->scale->size; - if ((quantization->quantized_dimension == output.dims->size - 1) && - num_channels_ > 1) { - use_alternate_axis_ = true; - } else if (quantization->quantized_dimension != 0) { - MicroPrintf("unsupported quantization axis %u", - quantization->quantized_dimension); - return kTfLiteError; - } - TFLITE_DCHECK(num_channels_ == static_cast(quantization->zero_point->size)); bool has_non_zero_zp = @@ -71,10 +83,8 @@ TfLiteStatus DecodeStatePrune::Setup(const TfLiteTensor& input, const size_t bufsize = num_channels_ * sizeof(*zero_points_); zero_points_ = static_cast( micro_context->AllocatePersistentBuffer(bufsize)); - if (zero_points_ == nullptr) { - MicroPrintf("unable to allocate zero_points_"); - return kTfLiteError; - } + TF_LITE_ENSURE(const_cast(context_), + zero_points_ != nullptr); std::copy_n(quantization->zero_point->data, num_channels_, zero_points_); } else { single_zero_point_ = quantization->zero_point->data[0]; diff --git a/tensorflow/lite/micro/kernels/decode_state_prune.h b/tensorflow/lite/micro/kernels/decode_state_prune.h index f95df725578..415ca7ad0ab 100644 --- a/tensorflow/lite/micro/kernels/decode_state_prune.h +++ b/tensorflow/lite/micro/kernels/decode_state_prune.h @@ -38,9 +38,12 @@ class DecodeStatePrune : public DecodeState { const TfLiteEvalTensor& ancillary, const TfLiteEvalTensor& output) override; - private: + public: // Prune Decode Common Metadata constants static constexpr size_t kDcmVersionOffset = 4; + static constexpr size_t kDcmParamsOffset = 5; + static constexpr uint8_t kDcmParamsAxisMask = 0xF0; + static constexpr uint8_t kDcmParamsAxisMaskShift = 4; protected: virtual ~DecodeStatePrune() = default; diff --git a/tensorflow/lite/micro/kernels/decode_state_prune_test.cc b/tensorflow/lite/micro/kernels/decode_state_prune_test.cc index d268bd6369e..d5fc4b422ff 100644 --- a/tensorflow/lite/micro/kernels/decode_state_prune_test.cc +++ b/tensorflow/lite/micro/kernels/decode_state_prune_test.cc @@ -13,6 +13,8 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ +#include "tensorflow/lite/micro/kernels/decode_state_prune.h" + #include #include #include @@ -86,12 +88,44 @@ constexpr int8_t kAncillaryDataPrune5[] = { 26, 23, 24, 25, 26 // chan 0 }; -constexpr uint8_t kDcmPrune[tflite::DecodeState::kDcmSizeInBytes] = { - tflite::DecodeState::kDcmTypePrune, // type: Prune - 1, // DCM version: 1 - 0, // reserved - 0, // reserved - 1, // Prune version: 1 +constexpr uint8_t kDcmPruneAxis0[tflite::DecodeState::kDcmSizeInBytes] = { + tflite::DecodeState::kDcmTypePrune, // type: Prune + 1, // DCM version: 1 + 0, // reserved + 0, // reserved + 1, // Prune version: 1 + 0 << tflite::DecodeStatePrune::kDcmParamsAxisMaskShift, // prune params: + // axis +}; + +constexpr uint8_t kDcmPruneAxis3[tflite::DecodeState::kDcmSizeInBytes] = { + tflite::DecodeState::kDcmTypePrune, // type: Prune + 1, // DCM version: 1 + 0, // reserved + 0, // reserved + 1, // Prune version: 1 + 3 << tflite::DecodeStatePrune::kDcmParamsAxisMaskShift, // prune params: + // axis +}; + +constexpr uint8_t kDcmPruneNoAxis[tflite::DecodeState::kDcmSizeInBytes] = { + tflite::DecodeState::kDcmTypePrune, // type: Prune + 1, // DCM version: 1 + 0, // reserved + 0, // reserved + 1, // Prune version: 1 + tflite::DecodeStatePrune::kDcmParamsAxisMask, // prune params: axis +}; + +constexpr uint8_t + kDcmPruneUnsupportedAxis[tflite::DecodeState::kDcmSizeInBytes] = { + tflite::DecodeState::kDcmTypePrune, // type: Prune + 1, // DCM version: 1 + 0, // reserved + 0, // reserved + 1, // Prune version: 1 + 1 << tflite::DecodeStatePrune::kDcmParamsAxisMaskShift, // prune + // params: axis }; // Align the tensor data the same as a Buffer in the TfLite schema. @@ -193,11 +227,62 @@ using tflite::testing::AncillaryData; using tflite::testing::TensorInDatum; using tflite::testing::TensorOutDatum; -TEST(DecodeStatePruneTest, DecodePruneFloat) { +TEST(DecodeStatePruneTest, DecodePruneFloatAxis0) { + // Align the tensor data the same as a Buffer in the TfLite schema + alignas(16) float output_data[std::size(kExpectPrune2)] = {}; + alignas(16) const AncillaryData + kAncillaryData = {{kDcmPruneAxis0}, {kAncillaryDataPrune2}}; + + const TfLiteIntArray* const kEncodedDims = + tflite::testing::IntArrayFromInts(kEncodedShapePrune); + static const TensorInDatum kEncodeTID = { + kEncodedPrune, + *kEncodedDims, + }; + static constexpr std::initializer_list kEncodes = { + &kEncodeTID, + }; + + constexpr int kAncillaryShape[] = {1, sizeof(kAncillaryData)}; + const TfLiteIntArray* const kAncillaryDims = + tflite::testing::IntArrayFromInts(kAncillaryShape); + static const TensorInDatum kAncillaryTID = { + &kAncillaryData, + *kAncillaryDims, + }; + static constexpr std::initializer_list kAncillaries = { + &kAncillaryTID}; + + const TfLiteIntArray* const kOutputDims = + tflite::testing::IntArrayFromInts(kOutputShapePrune); + constexpr int kOutputZeroPointsData[] = {0}; + const TfLiteIntArray* const kOutputZeroPoints = + tflite::testing::IntArrayFromInts(kOutputZeroPointsData); + const TfLiteFloatArray kOutputScales = {kOutputZeroPoints->size}; + static const TensorOutDatum kTOD = { + output_data, + *kOutputDims, + kTfLiteFloat32, + kOutputScales, + *kOutputZeroPoints, + 0, + {}, + }; + static constexpr std::initializer_list kOutputs = { + &kTOD}; + + const std::initializer_list kExpected = {kExpectPrune2}; + + tflite::testing::TestDecode( + kEncodes, kAncillaries, kOutputs, kExpected, tflite::Register_DECODE()); +} + +TEST(DecodeStatePruneTest, DecodePruneFloatNoAxis) { // Align the tensor data the same as a Buffer in the TfLite schema alignas(16) float output_data[std::size(kExpectPrune2)] = {}; alignas(16) const AncillaryData - kAncillaryData = {{kDcmPrune}, {kAncillaryDataPrune2}}; + kAncillaryData = {{kDcmPruneNoAxis}, {kAncillaryDataPrune2}}; const TfLiteIntArray* const kEncodedDims = tflite::testing::IntArrayFromInts(kEncodedShapePrune); @@ -244,11 +329,63 @@ TEST(DecodeStatePruneTest, DecodePruneFloat) { kEncodes, kAncillaries, kOutputs, kExpected, tflite::Register_DECODE()); } +TEST(DecodeStatePruneTest, DecodePruneFloatUnsupportedAxis) { + // Align the tensor data the same as a Buffer in the TfLite schema + alignas(16) float output_data[std::size(kExpectPrune2)] = {}; + alignas(16) const AncillaryData + kAncillaryData = {{kDcmPruneUnsupportedAxis}, {kAncillaryDataPrune2}}; + + const TfLiteIntArray* const kEncodedDims = + tflite::testing::IntArrayFromInts(kEncodedShapePrune); + static const TensorInDatum kEncodeTID = { + kEncodedPrune, + *kEncodedDims, + }; + static constexpr std::initializer_list kEncodes = { + &kEncodeTID, + }; + + constexpr int kAncillaryShape[] = {1, sizeof(kAncillaryData)}; + const TfLiteIntArray* const kAncillaryDims = + tflite::testing::IntArrayFromInts(kAncillaryShape); + static const TensorInDatum kAncillaryTID = { + &kAncillaryData, + *kAncillaryDims, + }; + static constexpr std::initializer_list kAncillaries = { + &kAncillaryTID}; + + const TfLiteIntArray* const kOutputDims = + tflite::testing::IntArrayFromInts(kOutputShapePrune); + constexpr int kOutputZeroPointsData[] = {0}; + const TfLiteIntArray* const kOutputZeroPoints = + tflite::testing::IntArrayFromInts(kOutputZeroPointsData); + const TfLiteFloatArray kOutputScales = {kOutputZeroPoints->size}; + static const TensorOutDatum kTOD = { + output_data, + *kOutputDims, + kTfLiteFloat32, + kOutputScales, + *kOutputZeroPoints, + 0, + {}, + }; + static constexpr std::initializer_list kOutputs = { + &kTOD}; + + const std::initializer_list kExpected = {kExpectPrune2}; + + tflite::testing::TestDecode( + kEncodes, kAncillaries, kOutputs, kExpected, tflite::Register_DECODE(), + nullptr, nullptr, kTfLiteError); +} + TEST(DecodeStatePruneTest, DecodePruneInt8) { // Align the tensor data the same as a Buffer in the TfLite schema alignas(16) int8_t output_data[std::size(kExpectPrune3)] = {}; alignas(16) const AncillaryData - kAncillaryData = {{kDcmPrune}, {kAncillaryDataPrune3}}; + kAncillaryData = {{kDcmPruneAxis0}, {kAncillaryDataPrune3}}; const TfLiteIntArray* const kEncodedDims = tflite::testing::IntArrayFromInts(kEncodedShapePrune); @@ -294,7 +431,7 @@ TEST(DecodeStatePruneTest, DecodePruneQuantizedInt8) { // Align the tensor data the same as a Buffer in the TfLite schema alignas(16) int8_t output_data[std::size(kExpectPrune3)] = {}; alignas(16) const AncillaryData - kAncillaryData = {{kDcmPrune}, {kAncillaryDataPrune3}}; + kAncillaryData = {{kDcmPruneAxis0}, {kAncillaryDataPrune3}}; const TfLiteIntArray* const kEncodedDims = tflite::testing::IntArrayFromInts(kEncodedShapePrune); @@ -339,7 +476,7 @@ TEST(DecodeStatePruneTest, DecodePruneQuantizedMixedZeroPointInt8) { // Align the tensor data the same as a Buffer in the TfLite schema alignas(16) int8_t output_data[std::size(kExpectPrune0)] = {}; alignas(16) const AncillaryData - kAncillaryData = {{kDcmPrune}, {kAncillaryDataPrune0}}; + kAncillaryData = {{kDcmPruneAxis0}, {kAncillaryDataPrune0}}; const TfLiteIntArray* const kEncodedDims = tflite::testing::IntArrayFromInts(kEncodedShapePrune); @@ -384,7 +521,7 @@ TEST(DecodeStatePruneTest, DecodePruneQuantizedSingleChannelInt8) { // Align the tensor data the same as a Buffer in the TfLite schema alignas(16) int8_t output_data[std::size(kExpectPrune5)] = {}; alignas(16) const AncillaryData - kAncillaryData = {{kDcmPrune}, {kAncillaryDataPrune5}}; + kAncillaryData = {{kDcmPruneAxis0}, {kAncillaryDataPrune5}}; const TfLiteIntArray* const kEncodedDims = tflite::testing::IntArrayFromInts(kEncodedShapePrune); @@ -429,7 +566,7 @@ TEST(DecodeStatePruneTest, DecodePruneQuantizedAltAxisInt8) { // Align the tensor data the same as a Buffer in the TfLite schema alignas(16) int8_t output_data[std::size(kExpectPrune4)] = {}; alignas(16) const AncillaryData - kAncillaryData = {{kDcmPrune}, {kAncillaryDataPrune4}}; + kAncillaryData = {{kDcmPruneAxis3}, {kAncillaryDataPrune4}}; const TfLiteIntArray* const kEncodedDims = tflite::testing::IntArrayFromInts(kEncodedShapePrune); @@ -479,7 +616,7 @@ TEST(DecodeStatePruneTest, DecodePruneQuantizedInt16) { // Align the tensor data the same as a Buffer in the TfLite schema alignas(16) int16_t output_data[std::size(kExpectPrune1)] = {}; alignas(16) const AncillaryData - kAncillaryData = {{kDcmPrune}, {kAncillaryDataPrune1}}; + kAncillaryData = {{kDcmPruneAxis0}, {kAncillaryDataPrune1}}; const TfLiteIntArray* const kEncodedDims = tflite::testing::IntArrayFromInts(kEncodedShapePrune); @@ -529,7 +666,7 @@ TEST(DecodeStatePruneTest, DecodePruneQuantizedInvalidZeroPointInt16) { // Align the tensor data the same as a Buffer in the TfLite schema alignas(16) int16_t output_data[std::size(kExpectPrune1)] = {}; alignas(16) const AncillaryData - kAncillaryData = {{kDcmPrune}, {kAncillaryDataPrune1}}; + kAncillaryData = {{kDcmPruneAxis0}, {kAncillaryDataPrune1}}; const TfLiteIntArray* const kEncodedDims = tflite::testing::IntArrayFromInts(kEncodedShapePrune); diff --git a/tensorflow/lite/micro/kernels/decode_test.cc b/tensorflow/lite/micro/kernels/decode_test.cc index ba24452c439..08d7ad81589 100644 --- a/tensorflow/lite/micro/kernels/decode_test.cc +++ b/tensorflow/lite/micro/kernels/decode_test.cc @@ -19,6 +19,7 @@ limitations under the License. #include "tensorflow/lite/core/c/common.h" #include "tensorflow/lite/micro/kernels/decode_state.h" +#include "tensorflow/lite/micro/kernels/decode_state_lut.h" #include "tensorflow/lite/micro/kernels/decode_test_helpers.h" #include "tensorflow/lite/micro/testing/micro_test_v2.h" @@ -38,7 +39,7 @@ constexpr uint8_t kDcmLUT0[tflite::DecodeState::kDcmSizeInBytes] = { 0, // reserved 0, // reserved 1, // LUT version: 1 - kBitWidthLUT, // Parameters: bit-width 2 + kBitWidthLUT, // Parameters: axis 0, bit-width 2 std::size(kAncillaryDataLUT0), // channel stride }; @@ -48,7 +49,7 @@ constexpr uint8_t kDcmLUT1[tflite::DecodeState::kDcmSizeInBytes] = { 0, // reserved 0, // reserved 1, // LUT version: 1 - kBitWidthLUT, // Parameters: bit-width 2 + kBitWidthLUT, // Parameters: axis 0, bit-width 2 std::size(kAncillaryDataLUT1), // channel stride }; @@ -427,4 +428,63 @@ TEST(DecodeTest, DecodeWithCustomMismatchedRegistration) { nullptr, &cdr, kTfLiteError); } +TEST(DecodeTest, DecodeAxisNotSupportedLUT) { + constexpr uint8_t + kDcmUnsupportedAxisLUT[tflite::DecodeState::kDcmSizeInBytes] = { + tflite::DecodeState::kDcmTypeLUT, // type: LUT + 1, // DCM version: 1 + 0, // reserved + 0, // reserved + 1, // LUT version: 1 + (1 << tflite::DecodeStateLut::kDcmParamsAxisMaskShift) | + kBitWidthLUT, // Parameters: axis 1, bit-width 2 + std::size(kAncillaryDataLUT0), // channel stride + }; + // Align the tensor data the same as a Buffer in the TfLite schema + alignas(16) int8_t output_data[std::size(kExpectLUT0)] = {}; + alignas(16) const AncillaryData + kAncillaryData = {{kDcmUnsupportedAxisLUT}, {kAncillaryDataLUT0}}; + + constexpr int kAncillaryShapeLUT[] = {1, sizeof(kAncillaryData)}; + + const TfLiteIntArray* const encoded_dims = + tflite::testing::IntArrayFromInts(kEncodedShapeLUT); + static const TensorInDatum tid_encode = { + kEncodedLUT, + *encoded_dims, + }; + static constexpr std::initializer_list encodes = { + &tid_encode, + }; + + const TfLiteIntArray* const ancillary_dims = + tflite::testing::IntArrayFromInts(kAncillaryShapeLUT); + static const TensorInDatum tid_ancillary = { + &kAncillaryData, + *ancillary_dims, + }; + static constexpr std::initializer_list ancillaries = { + &tid_ancillary}; + + const TfLiteIntArray* const output_dims = + tflite::testing::IntArrayFromInts(kOutputShapeLUT); + constexpr int kOutputZeroPointsData[] = {0}; + const TfLiteIntArray* const kOutputZeroPoints = + tflite::testing::IntArrayFromInts(kOutputZeroPointsData); + const TfLiteFloatArray kOutputScales = {kOutputZeroPoints->size}; + static const TensorOutDatum tod = { + output_data, *output_dims, kTfLiteInt8, kOutputScales, *kOutputZeroPoints, + 1, {}, + }; + static constexpr std::initializer_list outputs = { + &tod}; + + const std::initializer_list expected = {kExpectLUT0}; + + tflite::testing::TestDecode( + encodes, ancillaries, outputs, expected, tflite::Register_DECODE(), + nullptr, nullptr, kTfLiteError); +} + TF_LITE_MICRO_TESTS_MAIN From 2966f2686f31bd36ec3b9da099c817b6ee6bebdb Mon Sep 17 00:00:00 2001 From: ddavis-2015 Date: Tue, 1 Sep 2026 16:06:38 -0700 Subject: [PATCH 3/4] Re-enable -Werror in Makefile @tensorflow/micro Put the -Werror flag back into the primary makefile, to prevent creaping warning accumulation. Fix various warning generators in codebase. bug=fixes #3689 --- tensorflow/lite/micro/kernels/decompress_common.cc | 4 ++-- tensorflow/lite/micro/kernels/xtensa/depthwise_conv.cc | 2 ++ tensorflow/lite/micro/tools/make/Makefile | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/tensorflow/lite/micro/kernels/decompress_common.cc b/tensorflow/lite/micro/kernels/decompress_common.cc index 5c40af83bf3..602e87e64b8 100644 --- a/tensorflow/lite/micro/kernels/decompress_common.cc +++ b/tensorflow/lite/micro/kernels/decompress_common.cc @@ -326,7 +326,7 @@ void DecompressionState::DecompressToBufferWidthAny(T* buffer) { const T* value_table = static_cast(comp_data_.data.lut_data->value_table); for (size_t channel = 0; channel < num_channels_; channel++) { - size_t index; + size_t index = 0; switch (compressed_bit_width_) { case 1: index = GetNextTableIndexWidth1(current_offset); @@ -367,7 +367,7 @@ void DecompressionState::DecompressToBufferWidthAny(T* buffer) { size_t count = max_count; while (count-- > 0) { - size_t index; + size_t index = 0; switch (compressed_bit_width_) { case 1: index = GetNextTableIndexWidth1(current_offset); diff --git a/tensorflow/lite/micro/kernels/xtensa/depthwise_conv.cc b/tensorflow/lite/micro/kernels/xtensa/depthwise_conv.cc index e5d7f8f00fc..b59ff7d85a0 100644 --- a/tensorflow/lite/micro/kernels/xtensa/depthwise_conv.cc +++ b/tensorflow/lite/micro/kernels/xtensa/depthwise_conv.cc @@ -98,9 +98,11 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { MicroContext* micro_context = GetMicroContext(context); + [[maybe_unused]] const CompressionTensorData* filter_comp_td = micro_context->GetTensorCompressionData(node, kDepthwiseConvWeightsTensor); + [[maybe_unused]] const CompressionTensorData* bias_comp_td = micro_context->GetTensorCompressionData(node, kDepthwiseConvBiasTensor); diff --git a/tensorflow/lite/micro/tools/make/Makefile b/tensorflow/lite/micro/tools/make/Makefile index db053388c6d..2a0f09ef3c9 100644 --- a/tensorflow/lite/micro/tools/make/Makefile +++ b/tensorflow/lite/micro/tools/make/Makefile @@ -161,6 +161,7 @@ endif CC_WARNINGS := \ + -Werror \ -Wsign-compare \ -Wdouble-promotion \ -Wunused-variable \ From be35234aef696ffdb1e4191353c944167bf61241 Mon Sep 17 00:00:00 2001 From: ddavis-2015 Date: Tue, 1 Sep 2026 17:32:53 -0700 Subject: [PATCH 4/4] suppress warning. --- tensorflow/lite/micro/kernels/cast_test.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/tensorflow/lite/micro/kernels/cast_test.cc b/tensorflow/lite/micro/kernels/cast_test.cc index 3a2299095f5..d69f335ba67 100644 --- a/tensorflow/lite/micro/kernels/cast_test.cc +++ b/tensorflow/lite/micro/kernels/cast_test.cc @@ -59,6 +59,7 @@ void TestCast(const InputT (&input)[N], const OutputT (&golden)[N]) { template void TestFloatToInt(float pos_overflow, float neg_overflow) { + [[maybe_unused]] constexpr bool is_signed = std::numeric_limits::is_signed; const float input[] = {100.f, 1.0f,