From e36a9c49014ac571c216ec745554b1e345f8734b Mon Sep 17 00:00:00 2001 From: Seungbaek Hong Date: Mon, 18 Nov 2024 16:51:15 +0900 Subject: [PATCH] [layer] add pow operation layer added a pow operation layer. there was an example of a pow layer in the custom layers, so I modified the key value of the custom pow layer to "custom_pow" in order to avoid duplication of layer key value. **Self evaluation:** 1. Build test: [X]Passed [ ]Failed [ ]Skipped 2. Run test: [X]Passed [ ]Failed [ ]Skipped Signed-off-by: Seungbaek Hong --- Applications/Custom/LayerClient/jni/main.cpp | 2 +- .../LayerClient/res/custom_layer_client.ini | 2 +- .../LayerPlugin/layer_plugin_pow_test.cpp | 2 +- .../Custom/LayerPlugin/layer_plugin_test.cpp | 12 +- Applications/Custom/pow.h | 2 +- api/ccapi/include/layer.h | 9 ++ api/nntrainer-api-common.h | 1 + nntrainer/app_context.cpp | 3 + nntrainer/layers/common_properties.cpp | 2 + nntrainer/layers/common_properties.h | 16 +++ nntrainer/layers/meson.build | 1 + nntrainer/layers/pow_layer.cpp | 50 +++++++ nntrainer/layers/pow_layer.h | 124 ++++++++++++++++++ nntrainer/utils/node_exporter.h | 1 + packaging/unittest_models_v2.tar.gz | Bin 126856 -> 127015 bytes test/input_gen/genModelTests_v2.py | 23 ++++ test/unittest/layers/meson.build | 1 + test/unittest/layers/unittest_layers_pow.cpp | 30 +++++ test/unittest/models/unittest_models.cpp | 20 +++ 19 files changed, 291 insertions(+), 10 deletions(-) create mode 100644 nntrainer/layers/pow_layer.cpp create mode 100644 nntrainer/layers/pow_layer.h create mode 100644 test/unittest/layers/unittest_layers_pow.cpp diff --git a/Applications/Custom/LayerClient/jni/main.cpp b/Applications/Custom/LayerClient/jni/main.cpp index b655fce4c7..1608622048 100644 --- a/Applications/Custom/LayerClient/jni/main.cpp +++ b/Applications/Custom/LayerClient/jni/main.cpp @@ -151,7 +151,7 @@ int api_model_run() { /// creating array of layers same as in `custom_layer_client.ini` layers = std::vector>{ ml::train::layer::Input({"name=inputlayer", "input_shape=1:1:100"}), - ml::train::createLayer("pow", {"name=powlayer", "exponent=3"}), + ml::train::createLayer("custom_pow", {"name=powlayer", "exponent=3"}), ml::train::layer::FullyConnected( {"name=outputlayer", "input_layers=powlayer", "unit=10", "bias_initializer=zeros", "activation=softmax"}), diff --git a/Applications/Custom/LayerClient/res/custom_layer_client.ini b/Applications/Custom/LayerClient/res/custom_layer_client.ini index 1197e7f528..692701905e 100644 --- a/Applications/Custom/LayerClient/res/custom_layer_client.ini +++ b/Applications/Custom/LayerClient/res/custom_layer_client.ini @@ -17,7 +17,7 @@ Input_Shape = 1:1:100 [powlayer] input_layers = inputlayer -Type = pow # AppContext sees PowLayer::getType() and use this to parse type +Type = custom_pow # AppContext sees PowLayer::getType() and use this to parse type exponent = 3 # registering a custom property is done at int PowLayer::setProperty [outputlayer] diff --git a/Applications/Custom/LayerPlugin/layer_plugin_pow_test.cpp b/Applications/Custom/LayerPlugin/layer_plugin_pow_test.cpp index 37cd463bbd..def5492cc8 100644 --- a/Applications/Custom/LayerPlugin/layer_plugin_pow_test.cpp +++ b/Applications/Custom/LayerPlugin/layer_plugin_pow_test.cpp @@ -21,7 +21,7 @@ GTEST_PARAMETER_TEST(PowLayer, LayerPluginCommonTest, ::testing::Values(std::make_tuple("libpow_layer.so", - "pow"))); + "custom_pow"))); auto semantic_pow = LayerSemanticsParamType(nntrainer::createLayer, diff --git a/Applications/Custom/LayerPlugin/layer_plugin_test.cpp b/Applications/Custom/LayerPlugin/layer_plugin_test.cpp index 9e4997e172..03c1e7ec7f 100644 --- a/Applications/Custom/LayerPlugin/layer_plugin_test.cpp +++ b/Applications/Custom/LayerPlugin/layer_plugin_test.cpp @@ -29,9 +29,9 @@ TEST(AppContext, DlRegisterOpen_p) { ac.registerLayer("libpow_layer.so", NNTRAINER_PATH); - auto layer = ac.createObject("pow"); + auto layer = ac.createObject("custom_pow"); - EXPECT_EQ(layer->getType(), "pow"); + EXPECT_EQ(layer->getType(), "custom_pow"); } TEST(AppContext, DlRegisterWrongPath_n) { @@ -49,9 +49,9 @@ TEST(AppContext, DlRegisterDirectory_p) { ac.registerPluggableFromDirectory(NNTRAINER_PATH); - auto layer = ac.createObject("pow"); + auto layer = ac.createObject("custom_pow"); - EXPECT_EQ(layer->getType(), "pow"); + EXPECT_EQ(layer->getType(), "custom_pow"); } TEST(AppContext, DlRegisterDirectory_n) { @@ -64,8 +64,8 @@ TEST(AppContext, DlRegisterDirectory_n) { TEST(AppContext, DefaultEnvironmentPath_p) { /// as NNTRAINER_PATH is fed to the test, this should success without an /// error - std::shared_ptr l = ml::train::createLayer("pow"); - EXPECT_EQ(l->getType(), "pow"); + std::shared_ptr l = ml::train::createLayer("custom_pow"); + EXPECT_EQ(l->getType(), "custom_pow"); std::shared_ptr lnode = std::static_pointer_cast(l); diff --git a/Applications/Custom/pow.h b/Applications/Custom/pow.h index 4fcc2e2f29..1d3a05f01c 100644 --- a/Applications/Custom/pow.h +++ b/Applications/Custom/pow.h @@ -77,7 +77,7 @@ class PowLayer final : public nntrainer::Layer { */ void setProperty(const std::vector &values) override; - inline static const std::string type = "pow"; + inline static const std::string type = "custom_pow"; private: float exponent; diff --git a/api/ccapi/include/layer.h b/api/ccapi/include/layer.h index b1d267b79b..5b4f88b9f9 100644 --- a/api/ccapi/include/layer.h +++ b/api/ccapi/include/layer.h @@ -41,6 +41,7 @@ enum LayerType { LAYER_SUBTRACT = ML_TRAIN_LAYER_TYPE_SUBTRACT, /**< Subtract Layer type */ LAYER_MULTIPLY = ML_TRAIN_LAYER_TYPE_MULTIPLY, /**< Multiply Layer type */ LAYER_DIVIDE = ML_TRAIN_LAYER_TYPE_DIVIDE, /**< Divide Layer type */ + LAYER_POW = ML_TRAIN_LAYER_TYPE_POW, /**< Pow Layer type */ LAYER_FC = ML_TRAIN_LAYER_TYPE_FC, /**< Fully Connected Layer type */ LAYER_SWIGLU = ML_TRAIN_LAYER_TYPE_SWIGLU, /**< Swiglu Layer type */ LAYER_BN = ML_TRAIN_LAYER_TYPE_BN, /**< Batch Normalization Layer type */ @@ -330,6 +331,14 @@ DivideLayer(const std::vector &properties = {}) { return createLayer(LayerType::LAYER_DIVIDE, properties); } +/** + * @brief Helper function to create pow layer + */ +inline std::unique_ptr +PowLayer(const std::vector &properties = {}) { + return createLayer(LayerType::LAYER_POW, properties); +} + /** * @brief Helper function to create fully connected layer */ diff --git a/api/nntrainer-api-common.h b/api/nntrainer-api-common.h index 9ff621d495..ca272b2846 100644 --- a/api/nntrainer-api-common.h +++ b/api/nntrainer-api-common.h @@ -72,6 +72,7 @@ typedef enum { ML_TRAIN_LAYER_TYPE_TRANSPOSE = 36, /**< Transpose Layer type */ ML_TRAIN_LAYER_TYPE_CONV2D_TRANSPOSE = 37, /**< Convolution 2D Transpose Layer (Since 9.0) */ + ML_TRAIN_LAYER_TYPE_POW = 38, /**< Pow Layer type (Since 9.0)*/ ML_TRAIN_LAYER_TYPE_PREPROCESS_FLIP = 300, /**< Preprocess flip Layer (Since 6.5) */ ML_TRAIN_LAYER_TYPE_PREPROCESS_TRANSLATE = diff --git a/nntrainer/app_context.cpp b/nntrainer/app_context.cpp index 61890fc9d8..eabf8bc0d1 100644 --- a/nntrainer/app_context.cpp +++ b/nntrainer/app_context.cpp @@ -72,6 +72,7 @@ #include #include #include +#include #include #include #include @@ -273,6 +274,8 @@ static void add_default_object(AppContext &ac) { LayerType::LAYER_MULTIPLY); ac.registerFactory(nntrainer::createLayer, DivideLayer::type, LayerType::LAYER_DIVIDE); + ac.registerFactory(nntrainer::createLayer, PowLayer::type, + LayerType::LAYER_POW); ac.registerFactory(nntrainer::createLayer, FullyConnectedLayer::type, LayerType::LAYER_FC); ac.registerFactory(nntrainer::createLayer, diff --git a/nntrainer/layers/common_properties.cpp b/nntrainer/layers/common_properties.cpp index c38700bca6..3ce4208d78 100644 --- a/nntrainer/layers/common_properties.cpp +++ b/nntrainer/layers/common_properties.cpp @@ -92,6 +92,8 @@ InputConnection::InputConnection(const Connection &value) : Epsilon::Epsilon(float value) { set(value); } +Exponent::Exponent(float value) { set(value); } + bool Epsilon::isValid(const float &value) const { return value > 0.0f; } Momentum::Momentum(float value) { set(value); } diff --git a/nntrainer/layers/common_properties.h b/nntrainer/layers/common_properties.h index 462b614bf9..167a8729ad 100644 --- a/nntrainer/layers/common_properties.h +++ b/nntrainer/layers/common_properties.h @@ -281,6 +281,22 @@ class Epsilon : public nntrainer::Property { bool isValid(const float &value) const override; }; +/** + * @brief Exponent property, this is used for pow operation + * + */ +class Exponent : public nntrainer::Property { + +public: + /** + * @brief Construct a new Exponent object + * + */ + Exponent(float value = 1.0f); + static constexpr const char *key = "exponent"; /**< unique key to access */ + using prop_tag = float_prop_tag; /**< property type */ +}; + /** * @brief Momentum property, moving average in batch normalization layer * diff --git a/nntrainer/layers/meson.build b/nntrainer/layers/meson.build index 6d1ee32a02..983ea99b7a 100644 --- a/nntrainer/layers/meson.build +++ b/nntrainer/layers/meson.build @@ -9,6 +9,7 @@ layer_sources = [ 'subtract_layer.cpp', 'multiply_layer.cpp', 'divide_layer.cpp', + 'pow_layer.cpp', 'addition_layer.cpp', 'attention_layer.cpp', 'mol_attention_layer.cpp', diff --git a/nntrainer/layers/pow_layer.cpp b/nntrainer/layers/pow_layer.cpp new file mode 100644 index 0000000000..4d8b77f3ca --- /dev/null +++ b/nntrainer/layers/pow_layer.cpp @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: Apache-2.0 +/** + * Copyright (C) 2024 SeungBaek Hong + * + * @file pow_layer.cpp + * @date 20 Nov 2024 + * @see https://github.com/nnstreamer/nntrainer + * @author SeungBaek Hong + * @bug No known bugs except for NYI items + * @brief This is pow layer class (operation layer) + * + */ + +#include "common_properties.h" +#include +#include +#include +#include +#include + +#include + +namespace nntrainer { + +void PowLayer::finalize(InitLayerContext &context) { + context.setOutputDimensions({context.getInputDimensions()[0]}); +} + +void PowLayer::forwarding_operation(const Tensor &input, Tensor &hidden) { + float exponent = std::get(pow_props).get(); + input.pow(exponent, hidden); +} + +void PowLayer::calcDerivative(RunLayerContext &context) { + float exp = std::get(pow_props).get(); + context.getOutgoingDerivative(0).copy( + context.getIncomingDerivative(SINGLE_INOUT_IDX) + .multiply(exp) + .multiply(context.getInput(0).pow(exp - 1.0f))); +} + +void PowLayer::setProperty(const std::vector &values) { + auto remain_props = loadProperties(values, pow_props); + if (!remain_props.empty()) { + std::string msg = "[PowLayer] Unknown Layer Properties count " + + std::to_string(values.size()); + throw exception::not_supported(msg); + } +} +} /* namespace nntrainer */ diff --git a/nntrainer/layers/pow_layer.h b/nntrainer/layers/pow_layer.h new file mode 100644 index 0000000000..025b0d29b3 --- /dev/null +++ b/nntrainer/layers/pow_layer.h @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: Apache-2.0 +/** + * Copyright (C) 2024 SeungBaek Hong + * + * @file pow_layer.h + * @date 20 Nov 2024 + * @see https://github.com/nnstreamer/nntrainer + * @author SeungBaek Hong + * @bug No known bugs except for NYI items + * @brief This is pow layer class (operation layer) + * + */ + +#ifndef __POW_LAYER_H__ +#define __POW_LAYER_H__ +#ifdef __cplusplus + +#include +#include +#include + +namespace nntrainer { + +/** + * @class Pow Layer + * @brief Pow Layer + */ +class PowLayer : public UnaryOperationLayer { +public: + /** + * @brief Constructor of Pow Layer + */ + PowLayer() : + UnaryOperationLayer(), + pow_props(props::Print(), props::InPlaceProp(), props::Exponent()), + support_backwarding(true) {} + + /** + * @brief Destructor of Pow Layer + */ + ~PowLayer(){}; + + /** + * @brief Move constructor of Pow Layer. + * @param[in] PowLayer && + */ + PowLayer(PowLayer &&rhs) noexcept = default; + + /** + * @brief Move assignment operator. + * @parma[in] rhs PowLayer to be moved. + */ + PowLayer &operator=(PowLayer &&rhs) = default; + + /** + * @copydoc Layer::finalize(InitLayerContext &context) + */ + void finalize(InitLayerContext &context) final; + + /** + * @brief forwarding operation for pow + * + * @param input input tensor + * @param hidden tensor to store the result value + */ + void forwarding_operation(const Tensor &input, Tensor &hidden) final; + + /** + * @copydoc Layer::calcDerivative(RunLayerContext &context) + */ + void calcDerivative(RunLayerContext &context) final; + + /** + * @copydoc bool supportBackwarding() const + */ + bool supportBackwarding() const final { return support_backwarding; }; + + /** + * @brief Initialize the in-place settings of the layer + * @return InPlaceType + */ + InPlaceType initializeInPlace() final { + if (std::get(pow_props).empty() || + !std::get(pow_props).get()) { + is_inplace = false; + support_backwarding = true; + } else { + is_inplace = true; + support_backwarding = false; + } + + if (!supportInPlace()) + return InPlaceType::NONE; + else + return InPlaceType::NON_RESTRICTING; + } + + /** + * @copydoc Layer::exportTo(Exporter &exporter, ml::train::ExportMethods + * method) + */ + void exportTo(Exporter &exporter, + const ml::train::ExportMethods &method) const final {} + + /** + * @copydoc Layer::setProperty(const std::vector &values) + */ + void setProperty(const std::vector &values) final; + + /** + * @copydoc Layer::getType() + */ + const std::string getType() const final { return PowLayer::type; }; + + std::tuple pow_props; + bool support_backwarding; /**< support backwarding */ + + inline static const std::string type = "pow"; +}; + +} // namespace nntrainer + +#endif /* __cplusplus */ +#endif /* __POW_LAYER_H__ */ diff --git a/nntrainer/utils/node_exporter.h b/nntrainer/utils/node_exporter.h index 609f1eb1f7..74c63feb5f 100644 --- a/nntrainer/utils/node_exporter.h +++ b/nntrainer/utils/node_exporter.h @@ -237,6 +237,7 @@ class Packed; class LossScaleForMixed; class InPlaceProp; class InPlaceDirectionProp; +class Exponent; } // namespace props class LayerNode; diff --git a/packaging/unittest_models_v2.tar.gz b/packaging/unittest_models_v2.tar.gz index 1793cb10eef669916cd96f3237e5ae0f063e89d4..7b9c6a20a1708d5cf72dab918313e9c88cf6d4d6 100644 GIT binary patch delta 14743 zcmV;IIcUa+-v_7Q2L~UE2mtHPK7j|d2LTede_sNQ)zLs&#Hzlwq0ebr*7Z@go8La*))IN$n1UmvpD?#aMPfhYSv(z=SF98bfToCYIBYZ- z^0KR7#3vsnP&^)-j^#j(?swksf!;jnlNZ3Z=^)&2tHmyOg}>WlBX34GB?vBi0k^O0 z!lOtq*<)~}XCVw69S@2X zf;#fLGtEis=1v-*8T)=f-gt!pZpT{&lqPNGzB{A$;R-u&bUo9uwh68d*~mmx-A8|a zLnt=f!}PJr2Pw7ZSn$M|IiViH&scOD4(!tU!Fe`kgJ ztg~4C3kz_8*#*X)=3|rHVN~$ng_BCIU|*{{ylbBM%!akK7+X^cn;#J7ypI%0DSTjD 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 delta 14582 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 diff --git a/test/input_gen/genModelTests_v2.py b/test/input_gen/genModelTests_v2.py index 86d2c808b6..fc93504ffa 100644 --- a/test/input_gen/genModelTests_v2.py +++ b/test/input_gen/genModelTests_v2.py @@ -518,6 +518,19 @@ def forward(self, inputs, labels): return out, loss +class PowOperation(torch.nn.Module): + def __init__(self): + super().__init__() + self.fc = torch.nn.Linear(2, 2) + self.loss = torch.nn.MSELoss() + + def forward(self, inputs, labels): + out = self.fc(inputs[0]) + out = out.pow(3) + loss = self.loss(out, labels[0]) + return out, loss + + if __name__ == "__main__": record_v2( ReduceMeanLast(), @@ -836,6 +849,16 @@ def forward(self, inputs, labels): name="multiply_operation", ) + pow_operation = PowOperation() + record_v2( + pow_operation, + iteration=2, + input_dims=[(1, 2)], + input_dtype=[float], + label_dims=[(1, 2)], + name="pow_operation", + ) + # Function to check the created golden test file inspect_file("add_operation.nnmodelgolden") fc_mixed_training_nan_sgd = LinearMixedPrecisionNaNSGD() diff --git a/test/unittest/layers/meson.build b/test/unittest/layers/meson.build index 7d6bb3b49b..54d7d782fa 100644 --- a/test/unittest/layers/meson.build +++ b/test/unittest/layers/meson.build @@ -51,6 +51,7 @@ test_target = [ 'unittest_layers_subtract.cpp', 'unittest_layers_multiply.cpp', 'unittest_layers_divide.cpp', + 'unittest_layers_pow.cpp', 'unittest_layers_multiout.cpp', 'unittest_layers_rnn.cpp', 'unittest_layers_rnncell.cpp', diff --git a/test/unittest/layers/unittest_layers_pow.cpp b/test/unittest/layers/unittest_layers_pow.cpp new file mode 100644 index 0000000000..68bd11bad0 --- /dev/null +++ b/test/unittest/layers/unittest_layers_pow.cpp @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Apache-2.0 +/** + * Copyright (C) 2024 SeungBaek Hong + * + * @file unittest_layers_pow.cpp + * @date 20 Nov 2024 + * @brief Pow Layer Test + * @see https://github.com/nnstreamer/nntrainer + * @author SeungBaek Hong + * @bug No known bugs except for NYI items + */ +#include + +#include + +#include +#include + +auto semantic_pow = LayerSemanticsParamType( + nntrainer::createLayer, nntrainer::PowLayer::type, + {"exponent=3"}, LayerCreateSetPropertyOptions::AVAILABLE_FROM_APP_CONTEXT, + false, 1); + +auto semantic_pow_multi = LayerSemanticsParamType( + nntrainer::createLayer, nntrainer::PowLayer::type, + {"exponent=3"}, LayerCreateSetPropertyOptions::AVAILABLE_FROM_APP_CONTEXT, + false, 2); + +GTEST_PARAMETER_TEST(Pow, LayerSemantics, + ::testing::Values(semantic_pow, semantic_pow_multi)); diff --git a/test/unittest/models/unittest_models.cpp b/test/unittest/models/unittest_models.cpp index 8de98a0446..6c691bd9f0 100644 --- a/test/unittest/models/unittest_models.cpp +++ b/test/unittest/models/unittest_models.cpp @@ -948,6 +948,25 @@ static std::unique_ptr makeDivideOperation() { return nn; } +static std::unique_ptr makePowOperation() { + std::unique_ptr nn(new NeuralNetwork()); + + auto outer_graph = + makeGraph({{"input", {"name=in", "input_shape=1:1:2"}}, + {"fully_connected", {"name=fc", "unit=2", "input_layers=in"}}, + {"pow", {"name=pow_layer", "exponent=3", "input_layers=fc"}}, + {"mse", {"name=loss", "input_layers=pow_layer"}}}); + + for (auto &node : outer_graph) { + nn->addLayer(node); + } + + nn->setProperty({"batch_size=1"}); + nn->setOptimizer(ml::train::createOptimizer("sgd", {"learning_rate=0.1"})); + + return nn; +} + GTEST_PARAMETER_TEST( model, nntrainerModelTest, ::testing::ValuesIn({ @@ -1026,6 +1045,7 @@ GTEST_PARAMETER_TEST( ModelTestOption::ALL_V2), mkModelTc_V2(makeDivideOperation, "divide_operation", ModelTestOption::ALL_V2), + mkModelTc_V2(makePowOperation, "pow_operation", ModelTestOption::ALL_V2), }), [](const testing::TestParamInfo &info) -> const auto & { return std::get<1>(info.param); });