Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions compiler/luci-interpreter/pal/linux/KernelsToBuild.lst
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ REGISTER_KERNEL(Rsqrt)
REGISTER_KERNEL(Select)
REGISTER_KERNEL(SelectV2)
REGISTER_KERNEL(Shape)
REGISTER_KERNEL(Sign)
REGISTER_KERNEL(Sin)
REGISTER_KERNEL(Slice)
REGISTER_KERNEL(Softmax)
Expand Down
90 changes: 90 additions & 0 deletions compiler/luci-interpreter/src/kernels/Sign.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* Copyright (c) 2026 Samsung Electronics Co., Ltd. All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include "kernels/Sign.h"

#include "kernels/Utils.h"

#include <cmath>

namespace luci_interpreter
{
namespace kernels
{

namespace
{

template <typename T>
inline void CalcSign(const T *input_data, const size_t num_elements, T *output_data)
{
for (size_t i = 0; i < num_elements; ++i)
{
const T x = input_data[i];
if constexpr (std::is_floating_point_v<T>)
{
if (std::isnan(x))
{
output_data[i] = x; // NaN -> NaN
continue;
}
}
output_data[i] = (T(0) < x) - (x < T(0)); // -1/0/1
}
}

} // namespace

Sign::Sign(const Tensor *input, Tensor *output) : Kernel({input}, {output}) {}

void Sign::configure()
{
auto et = input()->element_type();
LUCI_INTERPRETER_CHECK(et == DataType::FLOAT32 || et == DataType::FLOAT64 || et == DataType::S32);
LUCI_INTERPRETER_CHECK(et == output()->element_type());
output()->resize(input()->shape());
}

void Sign::execute() const
{
switch (input()->element_type())
{
case DataType::S32:
evalS32();
break;
case DataType::FLOAT32:
evalFloat32();
break;
case DataType::FLOAT64:
evalFloat64();
break;
default:
throw std::runtime_error("luci-intp Sign Unsupported type.");
}
}

template <typename T> void Sign::eval() const
{
const int size = tflite::MatchingFlatSize(getTensorShape(input()), getTensorShape(output()));
CalcSign(getTensorData<T>(input()), static_cast<size_t>(size), getTensorData<T>(output()));
}

void Sign::evalS32() const { eval<int32_t>(); }
void Sign::evalFloat32() const { eval<float>(); }
void Sign::evalFloat64() const { eval<double>(); }

} // namespace kernels
} // namespace luci_interpreter
50 changes: 50 additions & 0 deletions compiler/luci-interpreter/src/kernels/Sign.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright (c) 2026 Samsung Electronics Co., Ltd. All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#ifndef LUCI_INTERPRETER_KERNELS_SIGN_H
#define LUCI_INTERPRETER_KERNELS_SIGN_H

#include "core/Kernel.h"
#include "core/KernelParams.h"

namespace luci_interpreter
{
namespace kernels
{

class Sign : public Kernel
{
public:
Sign(const Tensor *input, Tensor *output);

const Tensor *input() const { return _inputs[0]; }
Tensor *output() const { return _outputs[0]; }

void configure() override;
void execute() const override;

private:
template <typename T> void eval() const;

void evalS32() const;
void evalFloat32() const;
void evalFloat64() const;
};

} // namespace kernels
} // namespace luci_interpreter

#endif // LUCI_INTERPRETER_KERNELS_SIGN_H
139 changes: 139 additions & 0 deletions compiler/luci-interpreter/src/kernels/Sign.test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/*
* Copyright (c) 2026 Samsung Electronics Co., Ltd. All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include "kernels/Sign.h"
#include "kernels/TestUtils.h"
#include "luci_interpreter/TestMemoryManager.h"

#include <cmath>

namespace luci_interpreter
{
namespace kernels
{
namespace
{

using namespace testing;

TEST(SignTest, S32)
{
std::unique_ptr<IMemoryManager> memory_manager = std::make_unique<TestMemoryManager>();

// 0, +, -
Shape input_shape{1, 1, 3};
std::vector<int32_t> input_data{0, 2, -3};

Tensor input_tensor =
makeInputTensor<DataType::S32>(input_shape, input_data, memory_manager.get());
Tensor output_tensor = makeOutputTensor(DataType::S32);

Sign kernel(&input_tensor, &output_tensor);
kernel.configure();
memory_manager->allocate_memory(output_tensor);
kernel.execute();

// shape check
std::vector<int32_t> ref_output_shape{1, 1, 3};
EXPECT_THAT(extractTensorShape(output_tensor), ::testing::ElementsAreArray(ref_output_shape));

// data check
auto out = extractTensorData<int32_t>(output_tensor);

EXPECT_THAT(std::vector<int32_t>(out.begin(), out.end()), ::testing::ElementsAre(0, 1, -1));
}

TEST(SignTest, Float32)
{
std::unique_ptr<IMemoryManager> memory_manager = std::make_unique<TestMemoryManager>();

// 0, +, -, NaN
Shape input_shape{1, 1, 4};
std::vector<float> input_data{0.0f, 2.0f, -3.0f, std::numeric_limits<float>::quiet_NaN()};

Tensor input_tensor =
makeInputTensor<DataType::FLOAT32>(input_shape, input_data, memory_manager.get());
Tensor output_tensor = makeOutputTensor(DataType::FLOAT32);

Sign kernel(&input_tensor, &output_tensor);
kernel.configure();
memory_manager->allocate_memory(output_tensor);
kernel.execute();

// shape check
std::vector<int32_t> ref_output_shape{1, 1, 4};
EXPECT_THAT(extractTensorShape(output_tensor), ::testing::ElementsAreArray(ref_output_shape));

// data check
auto out = extractTensorData<float>(output_tensor);

// first 3 are deterministic
std::vector<float> ref_first3{0.0f, 1.0f, -1.0f};
EXPECT_THAT(std::vector<float>(out.begin(), out.begin() + 3), FloatArrayNear(ref_first3));

// NaN should stay NaN
EXPECT_TRUE(std::isnan(out[3]));
}

TEST(SignTest, Float64)
{
std::unique_ptr<IMemoryManager> memory_manager = std::make_unique<TestMemoryManager>();

// 0, +, -, NaN
Shape input_shape{1, 1, 4};
std::vector<double> input_data{0.0, 2.0, -3.0, std::numeric_limits<double>::quiet_NaN()};

Tensor input_tensor =
makeInputTensor<DataType::FLOAT64>(input_shape, input_data, memory_manager.get());
Tensor output_tensor = makeOutputTensor(DataType::FLOAT64);

Sign kernel(&input_tensor, &output_tensor);
kernel.configure();
memory_manager->allocate_memory(output_tensor);
kernel.execute();

// shape check
std::vector<int32_t> ref_output_shape{1, 1, 4};
EXPECT_THAT(extractTensorShape(output_tensor), ::testing::ElementsAreArray(ref_output_shape));

// data check
auto out = extractTensorData<double>(output_tensor);

// first 3 are deterministic
std::vector<double> ref_first3{0.0, 1.0, -1.0};
EXPECT_THAT(std::vector<double>(out.begin(), out.begin() + 3), DoubleArrayNear(ref_first3));

// NaN should stay NaN
EXPECT_TRUE(std::isnan(out[3]));
}

TEST(SignTest, InvalidDType_NEG)
{
std::unique_ptr<IMemoryManager> memory_manager = std::make_unique<TestMemoryManager>();
Shape input_shape{1, 1, 3};
std::vector<int64_t> input_data{1l, 2l, 3l};

Tensor input_tensor =
makeInputTensor<DataType::S64>(input_shape, input_data, memory_manager.get());
Tensor output_tensor = makeOutputTensor(DataType::S64);

Sign kernel(&input_tensor, &output_tensor);
EXPECT_ANY_THROW(kernel.configure());
}

} // namespace
} // namespace kernels
} // namespace luci_interpreter
13 changes: 13 additions & 0 deletions compiler/luci-interpreter/src/kernels/TestUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ namespace testing
{

using ::testing::FloatNear;
using ::testing::DoubleNear;
using ::testing::Matcher;

Tensor makeOutputTensor(DataType element_type) { return Tensor(element_type, {}, {}, ""); }
Expand Down Expand Up @@ -112,6 +113,18 @@ Matcher<std::vector<float>> FloatArrayNear(const std::vector<float> &values, flo
return ElementsAreArray(matchers);
}

Matcher<std::vector<double>> DoubleArrayNear(const std::vector<double> &values,
Copy link
Copy Markdown
Contributor Author

@shs-park shs-park Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DoubleArrayNear function is introduced to test FLOAT64 value test in TEST(SignTest, Float64) above.

double max_abs_error)
{
std::vector<Matcher<double>> matchers;
matchers.reserve(values.size());
for (const double v : values)
{
matchers.emplace_back(DoubleNear(v, max_abs_error));
}
return ElementsAreArray(matchers);
}

std::vector<int32_t> extractTensorShape(const Tensor &tensor)
{
std::vector<int32_t> result;
Expand Down
4 changes: 4 additions & 0 deletions compiler/luci-interpreter/src/kernels/TestUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,10 @@ std::vector<float> dequantizeTensorData(const Tensor &tensor);
::testing::Matcher<std::vector<float>> FloatArrayNear(const std::vector<float> &values,
float max_abs_error = 1.0e-5f);

// Array version of `::testing::DoubleNear` matcher.
::testing::Matcher<std::vector<double>> DoubleArrayNear(const std::vector<double> &values,
double max_abs_error = 1.0e-12);

template <typename T>
std::vector<T> quantize(const float *data, size_t num_elements, float scale, int32_t zero_point)
{
Expand Down
36 changes: 36 additions & 0 deletions compiler/luci-interpreter/src/loader/nodes/Sign.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright (c) 2026 Samsung Electronics Co., Ltd. All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include "Builders.h"

#include "kernels/Sign.h"

namespace luci_interpreter
{

std::unique_ptr<Kernel> build_kernel_CircleSign(const luci::CircleNode *circle_node,
KernelBuilderHelper &helper)
{
const auto *node = loco::must_cast<const luci::CircleSign *>(circle_node);
assert(node->arity() == 1);

const Tensor *input = helper.getInputTensor(node->x());
Tensor *output = helper.getOutputTensor(node);

return std::make_unique<kernels::Sign>(input, output);
}

} // namespace luci_interpreter