From 037f02e9622da2d2551b975494cbf41ebd1c6fe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20Alfv=C3=A9n?= Date: Mon, 21 Apr 2025 09:17:10 +0200 Subject: [PATCH] MLBEDSW-10723: MLCE: Add support for the Log operator in Regor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Implemented Log operator with the generic LUT implementation - Update SUPPORTED_OPS.md and vela.py to include the LOG operator - In tflite_graph_optimiser.cpp: - Change the variable type of `prevLutResult` from float to int16_t. - Clamp the intermediate LUT value and explicitly cast it to int16_t, fixing precision and conversion issues in the LUT generation. Change-Id: I8c877cb4f3c3c2da2eb82369fdd0269e938c4fe5 Signed-off-by: Johan Alfvén --- CHANGELOG.md | 16 ++++++ SUPPORTED_OPS.md | 9 ++++ .../regor/compiler/tflite_graph_optimiser.cpp | 52 ++++++++++++++++--- .../regor/compiler/tflite_graph_optimiser.hpp | 2 + .../test/test_tflite_supported_operators.cpp | 25 +++++++++ .../tflite/tflite_supported_operators.cpp | 34 ++++++++++++ .../tflite/tflite_supported_operators.hpp | 1 + .../tflite/tflite_supported_operators_u55.cpp | 1 + .../tflite/tflite_supported_operators_u85.cpp | 1 + ethosu/vela/vela.py | 9 ++++ 10 files changed, 144 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77313b8e..717ff7cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,22 @@ main feature changes, interface changes and reported defects that have been fixed. The version numbering adheres to the [semantic versioning](https://semver.org/) scheme. +## Release 4.3.0 - 25/04/2025 + +**Main feature changes:** + +* Extended operator support: + * Ethos-U85: + * TFLite: LOG + +**Interface changes:** + + * None + +**Reported defect fixes:** + + * None + ## Release 4.2.0 - 27/02/2025 The Vela compiler has moved from diff --git a/SUPPORTED_OPS.md b/SUPPORTED_OPS.md index 9034aefd..5febbaa8 100644 --- a/SUPPORTED_OPS.md +++ b/SUPPORTED_OPS.md @@ -105,6 +105,7 @@ Please check the supported operator list for your chosen runtime for further inf | GATHER | [Generic](#tflite-generic-constraints) | [Specific](#ethos-u85-tflite-gather-constraints) | | HARD_SWISH | [Generic](#tflite-generic-constraints), [Specific](#ethos-u85-tflite-hard_swish-constraints) | | LEAKY_RELU | [Generic](#tflite-generic-constraints), [Specific](#ethos-u85-tflite-leaky_relu-constraints) | +| LOG | [Generic](#tflite-generic-constraints), [Specific](#ethos-u85-tflite-log-constraints) | | LOGISTIC | [Generic](#tflite-generic-constraints) | | MAXIMUM | [Generic](#tflite-generic-constraints), [Specific](#ethos-u85-tflite-maximum-constraints) | | MAX_POOL_2D | [Generic](#tflite-generic-constraints), [Specific](#ethos-u85-tflite-max_pool_2d-constraints) | @@ -660,6 +661,14 @@ This is a list of constraints that the LEAKY_RELU operator must satisfy in order - At least one Input's shape must match the OFM's shape - IFM and OFM data types must match +### Ethos-U85 TFLite LOG Constraints + +This is a list of constraints that the LOG operator must satisfy in order to be scheduled on the NPU. + +- At least one Input's shape must match the OFM's shape +- IFM and OFM data types must match +- IFM must be int8 or int16 + ### Ethos-U85 TFLite MAXIMUM Constraints This is a list of constraints that the MAXIMUM operator must satisfy in order to be scheduled on the NPU. diff --git a/ethosu/regor/compiler/tflite_graph_optimiser.cpp b/ethosu/regor/compiler/tflite_graph_optimiser.cpp index 95a35cd8..1b6468a7 100644 --- a/ethosu/regor/compiler/tflite_graph_optimiser.cpp +++ b/ethosu/regor/compiler/tflite_graph_optimiser.cpp @@ -414,7 +414,7 @@ static Operation *ConvertToInterpolatingLUT16(Operation *op, FUNC func, const st // Create 32-bit LUT represented by a 16-bit base and 16-bit slope. auto lut = std::make_unique(512); - float prevLutResult = 0; + int16_t prevLutResult = 0; for ( int i = 0; i < steps; i++ ) { float val = func(inputMin + i * step); @@ -427,18 +427,20 @@ static Operation *ConvertToInterpolatingLUT16(Operation *op, FUNC func, const st float midpointErr = midpointInterpVal - midpointVal; float bias = std::round(midpointErr / 2.0f); - float lutResult = std::clamp(sampleVal - bias, qMin, qMax); + float clampedLutResult = std::clamp(sampleVal - bias, qMin, qMax); + int16_t lutResult = int16_t(clampedLutResult); if ( i > 0 ) { - uint32_t base = uint32_t(prevLutResult); - uint32_t slope = uint32_t(lutResult - prevLutResult); - lut[i - 1] = base + (slope << 16); + int16_t base = prevLutResult; + int16_t slope = lutResult - prevLutResult; + lut[i - 1] = uint16_t(base) + (uint16_t(slope) << 16); } prevLutResult = lutResult; } float val = float(std::round(func(inputMax) * outputScalingInv)); - float lutResult = std::clamp(val, qMin, qMax); + float clampedLutResult = std::clamp(val, qMin, qMax); + int16_t lutResult = int16_t(clampedLutResult); uint32_t base = uint32_t(prevLutResult); uint32_t slope = uint32_t(lutResult - prevLutResult); lut[steps - 1] = base + (slope << 16); @@ -576,6 +578,44 @@ Operation *TFLiteGraphOptimiser::ConvertExpToLUT(Graph *const graph, Operation * return returnOp; } + +// Convert LOG operations to LUT +Operation *TFLiteGraphOptimiser::ConvertLogToLUT(Graph *const graph, Operation *const operation) +{ + UNUSED(graph); + Operation *returnOp = operation; + OpType type = operation->Type(); + if ( type != OpType::Log ) + { + return returnOp; + } + const auto &ifmConn = operation->Input(TensorUsage::IFM0); + DataType ifmType = ifmConn->tensor->Type(); + + const auto &ofmConn = operation->Output(TensorUsage::OFM); + float ofmScale(ofmConn->quantization.scales[0].Dequantize()); + auto zpOut = ofmConn->quantization.zeroPoints[0]; + + int qMin = ifmType == DataType::Int8 ? -128 : -32768; + float minVal = (qMin - zpOut) * ofmScale; + if ( ifmType == DataType::Int8 ) + { + returnOp = ConvertToLUT8( + operation, [&](double x) -> float { return x <= 0.0f ? minVal : std::log(float(x)); }, "Log"); + RecordOptimisation(operation, returnOp); + operation->Disconnect(); + } + else if ( ifmType == DataType::Int16 ) + { + returnOp = ConvertToInterpolatingLUT16( + operation, [&](double x) -> float { return x <= 0.0f ? minVal : std::log(float(x)); }, "Log16(interp)"); + RecordOptimisation(operation, returnOp); + operation->Disconnect(); + } + return returnOp; +} + + // Convert TFLite Pack into TOSA Concat Operation *TFLiteGraphOptimiser::RewritePack(Graph *const graph, Operation *const operation) { diff --git a/ethosu/regor/compiler/tflite_graph_optimiser.hpp b/ethosu/regor/compiler/tflite_graph_optimiser.hpp index fd77451f..72f90b0b 100644 --- a/ethosu/regor/compiler/tflite_graph_optimiser.hpp +++ b/ethosu/regor/compiler/tflite_graph_optimiser.hpp @@ -101,6 +101,7 @@ private: Operation *ClampActivations(Graph *const graph, Operation *const operation); Operation *ConvertConvolutionGroup(Graph *const graph, Operation *const operation); Operation *ConvertExpToLUT(Graph *const graph, Operation *const operation); + Operation *ConvertLogToLUT(Graph *const graph, Operation *const operation); Operation *RewritePack(Graph *const graph, Operation *const operation); Operation *RewriteUnpack(Graph *const graph, Operation *const operation); Operation *RewriteSlice(Graph *const graph, Operation *const operation); @@ -249,6 +250,7 @@ public: &TFLiteGraphOptimiser::FixupBias, &TFLiteGraphOptimiser::ConvertReduceMinMaxAnyAll, &TFLiteGraphOptimiser::ConvertExpToLUT, + &TFLiteGraphOptimiser::ConvertLogToLUT, &TFLiteGraphOptimiser::ConvertTanhSigmoidToLUT, &TFLiteGraphOptimiser::ConvertSoftmaxOps, &TFLiteGraphOptimiser::ReplacePadByExplicitPadding, diff --git a/ethosu/regor/test/test_tflite_supported_operators.cpp b/ethosu/regor/test/test_tflite_supported_operators.cpp index 056abaca..bbef8e5b 100644 --- a/ethosu/regor/test/test_tflite_supported_operators.cpp +++ b/ethosu/regor/test/test_tflite_supported_operators.cpp @@ -440,6 +440,31 @@ TEST_CASE("Supported operators Common") op->Disconnect(); } } + + SECTION("ConstraintLog") + { + // Log is only supported with int8 or int16 input + auto op = CreateOperation(OpType::Log, Shape(1, 10, 10, 1), DataType::Int8, Shape(1, 10, 10, 1), DataType::Int8); + REQUIRE(supportedOps->Check(op.get()) == true); + op->Input(TensorUsage::IFM)->tensor->ChangeType(DataType::Int16); + op->Output(TensorUsage::OFM)->tensor->ChangeType(DataType::Int16); + REQUIRE(supportedOps->Check(op.get()) == true); + for ( auto dtype : {DataType::UInt8, DataType::Int32} ) + { + op->Input(TensorUsage::IFM)->tensor->ChangeType(dtype); + op->Output(TensorUsage::OFM)->tensor->ChangeType(dtype); + REQUIRE(supportedOps->Check(op.get()) == false); + } + // IFM and OFM data types must match + op->Input(TensorUsage::IFM)->tensor->ChangeType(DataType::Int8); + op->Output(TensorUsage::OFM)->tensor->ChangeType(DataType::Int16); + REQUIRE(supportedOps->Check(op.get()) == false); + op->Disconnect(); + // IFM and OFM shape must match + auto op2 = CreateOperation(OpType::Log, Shape(1, 7, 10, 1), DataType::Int8, Shape(1, 10, 10, 1), DataType::Int8); + REQUIRE(supportedOps->Check(op2.get()) == false); + op2->Disconnect(); + } } TEST_CASE("Supported operators EthosU55") diff --git a/ethosu/regor/tflite/tflite_supported_operators.cpp b/ethosu/regor/tflite/tflite_supported_operators.cpp index e09c5020..e0218087 100644 --- a/ethosu/regor/tflite/tflite_supported_operators.cpp +++ b/ethosu/regor/tflite/tflite_supported_operators.cpp @@ -917,6 +917,39 @@ bool TfLiteSupportedOperators::ConstraintStridedSlice(const Operation *op) return true; } +bool TfLiteSupportedOperators::ConstraintLog(const Operation *op) +{ + OpType opType = op->Type(); + if ( opType != OpType::Log ) + { + return true; + } + auto ifmConn = op->Input(TensorUsage::IFM); + assert(ifmConn); + auto ofmConn = op->Output(TensorUsage::OFM); + assert(ofmConn); + auto ifmType = ifmConn->tensor->Type(); + auto ofmType = ofmConn->tensor->Type(); + const auto &ifmShape = ifmConn->shape; + const auto &ofmShape = ofmConn->shape; + if ( ifmType != DataType::Int8 && ifmType != DataType::Int16 ) + { + Failure(op, fmt::format("{} IFM", DataTypeToString(ifmType)), "IFM must be Int8 or Int16"); + return false; + } + if ( ifmType != ofmType ) + { + Failure(op, fmt::format("{} IFM {} OFM", DataTypeToString(ifmType), DataTypeToString(ofmType)), "IFM and OFM data types must match"); + return false; + } + if ( ifmShape != ofmShape ) + { + Failure(op, fmt::format("{} IFM {} OFM", ifmShape.ToString(), ofmShape.ToString()), "IFM and OFM shape must match"); + return false; + } + return true; +} + void TfLiteSupportedOperators::Failure(const Operation *op, const std::string &message, const std::string &constraint) { assert(op); @@ -977,6 +1010,7 @@ TfLiteSupportedOperators::TfLiteSupportedOperators(IArchitectureConstraints *con &TfLiteSupportedOperators::ConstraintPad, &TfLiteSupportedOperators::ConstraintTransposeDims, &TfLiteSupportedOperators::ConstraintStridedSlice, + &TfLiteSupportedOperators::ConstraintLog, }; } diff --git a/ethosu/regor/tflite/tflite_supported_operators.hpp b/ethosu/regor/tflite/tflite_supported_operators.hpp index 9ad96127..0b7126e7 100644 --- a/ethosu/regor/tflite/tflite_supported_operators.hpp +++ b/ethosu/regor/tflite/tflite_supported_operators.hpp @@ -75,6 +75,7 @@ private: bool ConstraintPad(const Operation *op); bool ConstraintTransposeDims(const Operation *op); bool ConstraintStridedSlice(const Operation *op); + bool ConstraintLog(const Operation *op); }; // Factory for supported-ops checkers diff --git a/ethosu/regor/tflite/tflite_supported_operators_u55.cpp b/ethosu/regor/tflite/tflite_supported_operators_u55.cpp index c5aa7fce..a6252e5b 100644 --- a/ethosu/regor/tflite/tflite_supported_operators_u55.cpp +++ b/ethosu/regor/tflite/tflite_supported_operators_u55.cpp @@ -78,6 +78,7 @@ TfLiteSupportedOperatorsU55::TfLiteSupportedOperatorsU55(IArchitectureConstraint OpType::Quantize, OpType::HardSwish, OpType::MemoryCopy, + OpType::Log, // clang-format on }; _supportedDataTypes = { diff --git a/ethosu/regor/tflite/tflite_supported_operators_u85.cpp b/ethosu/regor/tflite/tflite_supported_operators_u85.cpp index fddef99a..335c3fa6 100644 --- a/ethosu/regor/tflite/tflite_supported_operators_u85.cpp +++ b/ethosu/regor/tflite/tflite_supported_operators_u85.cpp @@ -100,6 +100,7 @@ TfLiteSupportedOperatorsU85::TfLiteSupportedOperatorsU85(IArchitectureConstraint OpType::BatchMatMul, OpType::ReduceAll, OpType::MemoryCopy, + OpType::Log, // clang-format on }; _supportedDataTypes = { diff --git a/ethosu/vela/vela.py b/ethosu/vela/vela.py index 686ab4e7..55591e02 100755 --- a/ethosu/vela/vela.py +++ b/ethosu/vela/vela.py @@ -382,6 +382,7 @@ Please check the supported operator list for your chosen runtime for further inf | GATHER | [Generic](#tflite-generic-constraints) | [Specific](#ethos-u85-tflite-gather-constraints) | | HARD_SWISH | [Generic](#tflite-generic-constraints), [Specific](#ethos-u85-tflite-hard_swish-constraints) | | LEAKY_RELU | [Generic](#tflite-generic-constraints), [Specific](#ethos-u85-tflite-leaky_relu-constraints) | +| LOG | [Generic](#tflite-generic-constraints), [Specific](#ethos-u85-tflite-log-constraints) | | LOGISTIC | [Generic](#tflite-generic-constraints) | | MAXIMUM | [Generic](#tflite-generic-constraints), [Specific](#ethos-u85-tflite-maximum-constraints) | | MAX_POOL_2D | [Generic](#tflite-generic-constraints), [Specific](#ethos-u85-tflite-max_pool_2d-constraints) | @@ -597,6 +598,14 @@ This is a list of constraints that the MAXIMUM operator must satisfy in order to - Both Input quantization parameters must match OFM quantization parameters - Broadcasting is only allowed for rank indices with dimension 1, from either IFM1 or IFM2 +### Ethos-U85 TFLite LOG Constraints + +This is a list of constraints that the LOG operator must satisfy in order to be scheduled on the NPU. + +- At least one Input's shape must match the OFM's shape +- IFM and OFM data types must match +- IFM must be int8 or int16 + ### Ethos-U85 TFLite MAX_POOL_2D Constraints This is a list of constraints that the MAX_POOL_2D operator must satisfy in order to be scheduled on the NPU. -- GitLab