From 7e849aafa62bbc4e12f38033939b9c1659a17ef0 Mon Sep 17 00:00:00 2001 From: blee-bot <93bslee@gmail.com> Date: Thu, 10 Oct 2024 15:08:34 +0900 Subject: [PATCH] add record-hessian --- compiler/record-hessian/CMakeLists.txt | 32 ++ compiler/record-hessian/README.md | 3 + .../include/record-hessian/HessianComputer.h | 58 +++ .../include/record-hessian/HessianObserver.h | 44 ++ .../include/record-hessian/HessianVector.h | 63 +++ .../include/record-hessian/RecordHessian.h | 62 +++ compiler/record-hessian/requires.cmake | 3 + .../record-hessian/src/HessianComputer.cpp | 156 +++++++ .../record-hessian/src/HessianObserver.cpp | 44 ++ compiler/record-hessian/src/RecordHessian.cpp | 386 ++++++++++++++++++ .../tests/HessianComputer.test.cpp | 94 +++++ 11 files changed, 945 insertions(+) create mode 100644 compiler/record-hessian/CMakeLists.txt create mode 100644 compiler/record-hessian/README.md create mode 100644 compiler/record-hessian/include/record-hessian/HessianComputer.h create mode 100644 compiler/record-hessian/include/record-hessian/HessianObserver.h create mode 100644 compiler/record-hessian/include/record-hessian/HessianVector.h create mode 100644 compiler/record-hessian/include/record-hessian/RecordHessian.h create mode 100644 compiler/record-hessian/requires.cmake create mode 100644 compiler/record-hessian/src/HessianComputer.cpp create mode 100644 compiler/record-hessian/src/HessianObserver.cpp create mode 100644 compiler/record-hessian/src/RecordHessian.cpp create mode 100644 compiler/record-hessian/tests/HessianComputer.test.cpp diff --git a/compiler/record-hessian/CMakeLists.txt b/compiler/record-hessian/CMakeLists.txt new file mode 100644 index 00000000000..8ac1d82f044 --- /dev/null +++ b/compiler/record-hessian/CMakeLists.txt @@ -0,0 +1,32 @@ +file(GLOB_RECURSE SOURCES "src/*.cpp") + +add_library(record-hessian STATIC ${SOURCES}) + +target_include_directories(record-hessian PUBLIC include) +target_include_directories(record-hessian PRIVATE src) +target_link_libraries(record-hessian luci_import) +target_link_libraries(record-hessian luci_env) +target_link_libraries(record-hessian luci_export) +target_link_libraries(record-hessian luci_interpreter) +target_link_libraries(record-hessian luci_log) +target_link_libraries(record-hessian dio_hdf5) +install(TARGETS record-hessian DESTINATION lib) +install(DIRECTORY include/ DESTINATION include + FILES_MATCHING PATTERN "*.h") + +if(NOT ENABLE_TEST) + return() +endif(NOT ENABLE_TEST) + +nnas_find_package(GTest REQUIRED) +set(TEST_SOURCES, "src/MinMaxComputer.cpp") + +file(GLOB_RECURSE TESTS "tests/*.test.cpp") + +GTest_AddTest(record_hessian_tests ${TESTS} ${TEST_SOURCES}) +target_include_directories(record_hessian_tests PUBLIC include) +target_include_directories(record_hessian_tests PRIVATE src) +target_link_libraries(record_hessian_tests luci_lang) +target_link_libraries(record_hessian_tests nncc_coverage) +target_link_libraries(record_hessian_tests luci_interpreter) +target_link_libraries(record_hessian_tests record-hessian) diff --git a/compiler/record-hessian/README.md b/compiler/record-hessian/README.md new file mode 100644 index 00000000000..ede6bbdec25 --- /dev/null +++ b/compiler/record-hessian/README.md @@ -0,0 +1,3 @@ +# record-hessian + +_record-hessian_ calculates hessian metrix of activations for quantization. \ No newline at end of file diff --git a/compiler/record-hessian/include/record-hessian/HessianComputer.h b/compiler/record-hessian/include/record-hessian/HessianComputer.h new file mode 100644 index 00000000000..dc7481de3db --- /dev/null +++ b/compiler/record-hessian/include/record-hessian/HessianComputer.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2024 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 __RECORD_HESSIAN_HESSIANCOMPUTER_H__ +#define __RECORD_HESSIAN_HESSIANCOMPUTER_H__ + +#include "HessianVector.h" + +#include +#include + +namespace record_hessian +{ + +class HessianComputer +{ +public: + // Record min/max of node + void recordHessian(const luci::CircleNode *node, const luci_interpreter::Tensor *input_tensor); + + void unfold(std::vector &buf, uint32_t input_n, uint32_t input_h, uint32_t input_w, + uint32_t input_c, uint32_t stride_h, uint32_t stride_w, uint32_t dilation_h, + uint32_t dilation_w, uint32_t kernel_oc, uint32_t kernel_h, uint32_t kernel_w, + uint32_t kernel_ic); + + std::unique_ptr getMap() const + { + + auto hessian_map = std::make_unique(); + + for (auto item : _hessian_map) + { + auto &vec = (*hessian_map)[item.first]; + vec = item.second.hessian; + } + + return hessian_map; + } + +private: + std::unordered_map _hessian_map; +}; +} // namespace record_hessian + +#endif // __RECORD_HESSIAN_HESSIANCOMPUTER_H__ diff --git a/compiler/record-hessian/include/record-hessian/HessianObserver.h b/compiler/record-hessian/include/record-hessian/HessianObserver.h new file mode 100644 index 00000000000..235dc0fbad2 --- /dev/null +++ b/compiler/record-hessian/include/record-hessian/HessianObserver.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2024 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 __RECORD_HESSIAN_HESSIANOBSERVER_H__ +#define __RECORD_HESSIAN_HESSIANOBSERVER_H__ + +#include +#include +#include + +#include "HessianComputer.h" +namespace record_hessian +{ + +class HessianObserver : public luci_interpreter::ExecutionObserver +{ +public: + HessianObserver() = default; + + void postTensorWrite(const luci::CircleNode *node, + const luci_interpreter::Tensor *tensor) override; + + std::unique_ptr hessianData() { return _hessian_computer.getMap(); } + +private: + HessianComputer _hessian_computer; +}; + +} // namespace record_hessian + +#endif // __RECORD_HESSIAN_HESSIANOBSERVER_H__ diff --git a/compiler/record-hessian/include/record-hessian/HessianVector.h b/compiler/record-hessian/include/record-hessian/HessianVector.h new file mode 100644 index 00000000000..fb31a610998 --- /dev/null +++ b/compiler/record-hessian/include/record-hessian/HessianVector.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2024 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 __RECORD_HESSIAN_HESSIANVECTOR_H__ +#define __RECORD_HESSIAN_HESSIANVECTOR_H__ + +#include + +#include +#include +#include + +namespace record_hessian +{ + +using HessianMap = std::unordered_map>; + +struct HessianVector +{ + std::vector hessian; + size_t count = 0; + + HessianVector() : count(0) {} + + void update(const std::vector &new_hessian) + { + if (count == 0) + { + hessian.resize(new_hessian.size()); + } + else if (hessian.size() != new_hessian.size()) + { + hessian.resize(new_hessian.size()); + } + + size_t numel = new_hessian.size(); + float alpha = 1.f / static_cast(count + 1); + + for (size_t i = 0; i < numel; ++i) + { + hessian[i] = (hessian[i] * count + new_hessian[i]) * alpha; + } + + count++; + }; +}; + +} // namespace record_hessian + +#endif // __RECORD_HESSIAN_HESSIANVECTOR_H__ diff --git a/compiler/record-hessian/include/record-hessian/RecordHessian.h b/compiler/record-hessian/include/record-hessian/RecordHessian.h new file mode 100644 index 00000000000..1674bc44944 --- /dev/null +++ b/compiler/record-hessian/include/record-hessian/RecordHessian.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2024 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 __RECORD_HESSIAN_H__ +#define __RECORD_HESSIAN_H__ + +#include +#include + +#include "record-hessian/HessianObserver.h" + +namespace record_hessian +{ + +class RecordHessian +{ +public: + explicit RecordHessian() {} + + ~RecordHessian() = default; + + void initialize(luci::Module *module); + + // TODO Refactor profile functions + std::unique_ptr profileData(const std::string &input_data_path); + + std::unique_ptr profileDataInParallel(const std::string &input_data_path); + + std::unique_ptr profileRawData(const std::string &input_data_path); + + std::unique_ptr profileRawDataDirectory(const std::string &input_data_path); + + std::unique_ptr profileDataWithRandomInputs(void); + +private: + luci_interpreter::Interpreter *getInterpreter() const { return _interpreter.get(); } + + // Never return nullptr + HessianObserver *getObserver() const { return _observer.get(); } + + luci::Module *_module = nullptr; + + std::unique_ptr _interpreter; + std::unique_ptr _observer; +}; + +} // namespace record_hessian + +#endif // __RECORD_HESSIAN_H__ diff --git a/compiler/record-hessian/requires.cmake b/compiler/record-hessian/requires.cmake new file mode 100644 index 00000000000..bfba787368f --- /dev/null +++ b/compiler/record-hessian/requires.cmake @@ -0,0 +1,3 @@ +require("luci") +require("luci-interpreter") +require("dio-hdf5") diff --git a/compiler/record-hessian/src/HessianComputer.cpp b/compiler/record-hessian/src/HessianComputer.cpp new file mode 100644 index 00000000000..cc86dd4dde8 --- /dev/null +++ b/compiler/record-hessian/src/HessianComputer.cpp @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2024 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 "record-hessian/HessianComputer.h" + +#include + +namespace record_hessian +{ + +/** + * @brief unfold the vector with NHWC shape, inherently acting in an in-place manner. + * @note (N, H, W, C) -> (N, L, H*W*C) + */ +void HessianComputer::unfold(std::vector &buf, uint32_t input_n, uint32_t input_h, + uint32_t input_w, uint32_t input_c, uint32_t stride_h, + uint32_t stride_w, uint32_t dilation_h, uint32_t dilation_w, + uint32_t kernel_oc, uint32_t kernel_h, uint32_t kernel_w, + uint32_t kernel_ic) +{ + if (input_c != kernel_ic) + { + throw std::runtime_error("Input channels do not match kernel channels."); + } + int out_height = (input_h - dilation_h * (kernel_h - 1) - 1) / stride_h + 1; + int out_width = (input_w - dilation_w * (kernel_w - 1) - 1) / stride_w + 1; + int patch_size = kernel_h * kernel_w * kernel_ic; + std::vector unfolded_buf(input_n * out_height * out_width * patch_size, 0.0f); + + int index = 0; + int in_y, in_x; + for (int n = 0; n < input_n; ++n) + { + for (int y = 0; y < out_height; ++y) + { + for (int x = 0; x < out_width; ++x) + { + for (int in_c = 0; in_c < input_c; ++in_c) + { + for (int ky = 0; ky < kernel_h; ++ky) + { + for (int kx = 0; kx < kernel_w; ++kx) + { + in_y = y * stride_h + ky * dilation_h; + in_x = x * stride_w + kx * dilation_w; + if (in_y < input_h && in_x < input_w) + { + unfolded_buf[index] = buf[((n * input_h + in_y) * input_w + in_x) * input_c + in_c]; + } + index++; + } + } + } + } + } + } + + buf.swap(unfolded_buf); +} + +void HessianComputer::recordHessian(const luci::CircleNode *node, + const luci_interpreter::Tensor *input_tensor) +{ + if (node == nullptr || input_tensor == nullptr) + { + throw std::invalid_argument("node or input_tensor is null."); + } + uint32_t size_in_ch; + uint32_t length; + + const auto data = input_tensor->data(); + const auto num_elements = input_tensor->shape().num_elements(); + std::vector buf(data, data + num_elements); + + // get the size of input channel from weights + if (node->opcode() == luci::CircleOpcode::FULLY_CONNECTED) + { + if (input_tensor->shape().num_dims() == 3) + { // input_tensor [batch, length, channel] + size_in_ch = input_tensor->shape().dim(2); + } + else if (input_tensor->shape().num_dims() == 2) + { + size_in_ch = input_tensor->shape().dim(1); // input_tensor [length, channel] + } + else + { + throw std::runtime_error("Unsupported node rank"); + } + length = num_elements / size_in_ch; + } + else if (node->opcode() == luci::CircleOpcode::CONV_2D) + { + const auto node_filter = loco::must_cast( + loco::must_cast(node)->filter()); + const auto node_bias = loco::must_cast( + loco::must_cast(node)->bias()); + size_in_ch = + node_filter->size() / node_bias->size(); + + uint32_t input_n = input_tensor->shape().dim(0); + uint32_t input_h = input_tensor->shape().dim(1); + uint32_t input_w = input_tensor->shape().dim(2); + uint32_t input_c = input_tensor->shape().dim(3); + + uint32_t stride_h = ((luci::CircleConv2D *)node)->stride()->h(); + uint32_t stride_w = ((luci::CircleConv2D *)node)->stride()->w(); + uint32_t dilation_h = ((luci::CircleConv2D *)node)->dilation()->h(); + uint32_t dilation_w = ((luci::CircleConv2D *)node)->dilation()->w(); + + uint32_t kernel_oc = node_filter->dim(0).value(); + uint32_t kernel_h = node_filter->dim(1).value(); + uint32_t kernel_w = node_filter->dim(2).value(); + uint32_t kernel_ic = node_filter->dim(3).value(); + + unfold(buf, input_n, input_h, input_w, input_c, stride_h, stride_w, dilation_h, dilation_w, + kernel_oc, kernel_h, kernel_w, kernel_ic); + length = buf.size() / size_in_ch; + } + else + { + throw std::runtime_error(node->name() + " is unsupported op for record hessian."); + } + std::vector hessian(size_in_ch * size_in_ch, 0); + + for (int i = 0; i < size_in_ch; ++i) + { + for (int j = 0; j < size_in_ch; ++j) + { + float sum = 0; + for (int k = 0; k < length; ++k) + { + sum += buf[i + k * size_in_ch] * buf[j + k * size_in_ch]; + } + hessian[i * size_in_ch + j] = 2 * sum; + } + } + + HessianVector &vector = _hessian_map[node]; + vector.update(hessian); +} + +} // namespace record_hessian diff --git a/compiler/record-hessian/src/HessianObserver.cpp b/compiler/record-hessian/src/HessianObserver.cpp new file mode 100644 index 00000000000..4740024dea5 --- /dev/null +++ b/compiler/record-hessian/src/HessianObserver.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2024 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 "record-hessian/HessianObserver.h" + +#include +#include + +using DataType = luci_interpreter::DataType; + +namespace record_hessian +{ + +void HessianObserver::postTensorWrite(const luci::CircleNode *node, + const luci_interpreter::Tensor *tensor) +{ + + auto node_outputs = loco::succs(node); + for (auto node : node_outputs) + { + auto _node = dynamic_cast(node); + // TODO : ADD TCONV/DepthCONV cases + if (_node->opcode() == luci::CircleOpcode::FULLY_CONNECTED || + _node->opcode() == luci::CircleOpcode::CONV_2D) + { + _hessian_computer.recordHessian(_node, tensor); + } + } +} + +} // namespace record_hessian diff --git a/compiler/record-hessian/src/RecordHessian.cpp b/compiler/record-hessian/src/RecordHessian.cpp new file mode 100644 index 00000000000..30912a4086c --- /dev/null +++ b/compiler/record-hessian/src/RecordHessian.cpp @@ -0,0 +1,386 @@ +/* + * Copyright (c) 2024 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 "record-hessian/RecordHessian.h" +#include "record-hessian/HessianObserver.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using Shape = std::vector; +using DataType = loco::DataType; + +namespace +{ + +// Return a string with no whitespace from both ends +std::string trim(std::string s) +{ + // Trim left side + s.erase(s.begin(), + std::find_if(s.begin(), s.end(), [](unsigned char ch) { return !std::isspace(ch); })); + + // Trim right side + s.erase( + std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) { return !std::isspace(ch); }).base(), + s.end()); + + return s; +} + +std::vector parse_line(const std::string &line) +{ + auto trimmed = trim(line); + std::stringstream ss(trimmed); + + std::vector res; + + std::string filename; + while (getline(ss, filename, ' ')) + { + res.emplace_back(filename); + } + return res; +} + +uint32_t numElements(const luci::CircleNode *node) +{ + uint32_t num_elements = 1; + for (uint32_t i = 0; i < node->rank(); i++) + num_elements *= node->dim(i).value(); + + return num_elements; +} + +// Throw exception if input has one of the following conditions. +// 1. Have unknown dimension +// 2. Number of elements is 0 +void checkInputDimension(const luci::CircleInput *input) +{ + for (uint32_t i = 0; i < input->rank(); i++) + if (!input->dim(i).known()) + throw std::runtime_error(input->name() + " has unknown dimension"); + + if (numElements(input) == 0) + throw std::runtime_error(input->name() + " is a zero-sized input"); +} + +void readDataFromFile(const std::string &filename, std::vector &data, size_t data_size) +{ + assert(data.size() == data_size); // FIX_CALLER_UNLESS + + std::ifstream fs(filename, std::ifstream::binary); + if (fs.fail()) + throw std::runtime_error("Cannot open file \"" + filename + "\".\n"); + if (fs.read(data.data(), data_size).fail()) + throw std::runtime_error("Failed to read data from file \"" + filename + "\".\n"); + if (fs.peek() != EOF) + throw std::runtime_error("Input tensor size mismatches with \"" + filename + "\".\n"); +} + +/** + * @brief getTensorSize will return size in bytes + */ +template size_t getTensorSize(const NodeT *node) +{ + uint32_t tensor_size = luci::size(node->dtype()); + for (uint32_t i = 0; i < node->rank(); ++i) + tensor_size *= node->dim(i).value(); + return tensor_size; +} + +/** + * @brief verifyTypeShape checks the type and the shape of CircleInput + * This throws an exception if type or shape does not match + */ +void verifyTypeShape(const luci::CircleInput *input_node, const DataType &dtype, const Shape &shape) +{ + // Type check + if (dtype != input_node->dtype()) + throw std::runtime_error("Wrong input type."); + + if (shape.size() != input_node->rank()) + throw std::runtime_error("Input rank mismatch."); + + for (uint32_t i = 0; i < shape.size(); i++) + { + if (not(shape.at(i) == input_node->dim(i))) + throw std::runtime_error("Input shape mismatch."); + } +} + +} // namespace + +namespace record_hessian +{ + +void RecordHessian::initialize(luci::Module *module) +{ + + // Create and initialize interpreters and observers + + _module = module; + + auto interpreter = std::make_unique(module); + auto observer = std::make_unique(); + + interpreter->attachObserver(observer.get()); + + _observer = std::move(observer); + _interpreter = std::move(interpreter); +} + +// input_data_path is a path to the directory +// The directory should contain binary files each of which is a raw data, +// ready to be consumed by the input circle model without any modification +// TODO reduce duplicate codes with profileRawData +std::unique_ptr +RecordHessian::profileRawDataDirectory(const std::string &input_data_path) +{ + struct dirent *entry = nullptr; + DIR *dp = nullptr; + + dp = opendir(input_data_path.c_str()); + if (not dp) + throw std::runtime_error("Cannot open directory. Please check \"" + input_data_path + + "\" is a directory.\n"); + + uint32_t num_records = 0; + const auto input_nodes = loco::input_nodes(_module->graph()); + + // Get total input size + uint32_t total_input_size = 0; + for (auto input : input_nodes) + { + const auto *input_node = loco::must_cast(input); + checkInputDimension(input_node); + total_input_size += getTensorSize(input_node); + } + + while ((entry = readdir(dp))) + { + // Skip if the entry is not a regular file + if (entry->d_type != DT_REG) + continue; + + const std::string filename = entry->d_name; + std::cout << "Recording " << num_records << "'th data for hessian" << std::endl; + + // Read data from file to buffer + // Assumption: For a multi-input model, the binary file should have inputs concatenated in the + // same order with the input index. + std::vector input_data(total_input_size); + readDataFromFile(input_data_path + "/" + filename, input_data, total_input_size); + + // Write data from buffer to interpreter + uint32_t offset = 0; + for (auto input : input_nodes) + { + const auto *input_node = loco::must_cast(input); + const auto input_size = getTensorSize(input_node); + getInterpreter()->writeInputTensor(input_node, input_data.data() + offset, input_size); + + offset += input_size; + } + + getInterpreter()->interpret(); + + num_records++; + } + + closedir(dp); + + if (num_records == 0) + throw std::runtime_error("The input data file does not contain any record."); + + std::cout << "Recording finished. Number of recorded data: " << num_records << std::endl; + + return getObserver()->hessianData(); +} + +// input_data_path is a text file which specifies the representative data +// The text file should contain absolute file path per line. +// The pointed file should be a binary file containing one representative data, +// ready to be consumed by the input circle model without any modification +// NOTE If a model has multiple inputs, the binary file should have inputs concatenated in the +// same order with the input index of the circle model. +std::unique_ptr RecordHessian::profileRawData(const std::string &input_data_path) +{ + std::ifstream input_file(input_data_path); + if (input_file.fail()) + throw std::runtime_error("Cannot open file \"" + input_data_path + "\".\n"); + + std::string record; + uint32_t num_records = 0; + const auto input_nodes = loco::input_nodes(_module->graph()); + + // Get total input size + uint32_t total_input_size = 0; + for (auto input : input_nodes) + { + const auto *input_node = loco::must_cast(input); + checkInputDimension(input_node); + total_input_size += getTensorSize(input_node); + } + + while (getline(input_file, record)) + { + std::cout << "Recording " << num_records << "'th data for hessian" << std::endl; + + auto file_names = parse_line(record); + + // Have multiple files in one line + if (file_names.size() == input_nodes.size()) + { + std::vector> input_data; + for (uint32_t i = 0; i < file_names.size(); i++) + { + const auto file_name = file_names[i]; + const auto input_node = loco::must_cast(input_nodes[i]); + const auto input_size = getTensorSize(input_node); + + input_data.emplace_back(input_size); + + // Read data from file + readDataFromFile(file_name, input_data[i], input_size); + + // Write data from buffer to interpreter + getInterpreter()->writeInputTensor(input_node, input_data[i].data(), input_size); + } + + getInterpreter()->interpret(); + + num_records++; + } + else + { + // Must have a single file in one line (inputs are concatenated) + if (file_names.size() != 1) + throw std::runtime_error( + "Wrong number of inputs are given. Model has " + std::to_string(input_nodes.size()) + + " inputs, but list file gives " + std::to_string(file_names.size()) + " inputs."); + + // clang-format off + // Read data from file to buffer + // Assumption: For a multi-input model, the binary file should have inputs concatenated in the + // same order with the input index. + std::vector input_data(total_input_size); + readDataFromFile(record, input_data, total_input_size); + + // Write data from buffer to interpreter + uint32_t offset = 0; + for (auto input : input_nodes) + { + const auto *input_node = loco::must_cast(input); + const auto input_size = getTensorSize(input_node); + getInterpreter()->writeInputTensor(input_node, input_data.data() + offset, input_size); + + offset += input_size; + } + + getInterpreter()->interpret(); + + num_records++; + // clang-format on + } + } + + if (num_records == 0) + throw std::runtime_error("The input data file does not contain any record."); + + std::cout << "Recording finished. Number of recorded data: " << num_records << std::endl; + + return getObserver()->hessianData(); +} + +std::unique_ptr RecordHessian::profileData(const std::string &input_data_path) +{ + try + { + dio::hdf5::HDF5Importer importer(input_data_path); + importer.importGroup("value"); + + bool is_raw_data = importer.isRawData(); + + const auto num_records = importer.numData(); + if (num_records == 0) + throw std::runtime_error("The input data file does not contain any record."); + + const auto input_nodes = loco::input_nodes(_module->graph()); + const auto num_inputs = input_nodes.size(); + + for (int32_t record_idx = 0; record_idx < num_records; record_idx++) + { + if (num_inputs != static_cast(importer.numInputs(record_idx))) + throw std::runtime_error("Wrong number of inputs."); + + std::cout << "Recording " << record_idx << "'th data for hessian" << std::endl; + + for (uint32_t input_idx = 0; input_idx < num_inputs; input_idx++) + { + const auto *input_node = loco::must_cast(input_nodes[input_idx]); + assert(input_node->index() == input_idx); + checkInputDimension(input_node); + std::vector input_data(getTensorSize(input_node)); + + if (!is_raw_data) + { + DataType dtype; + Shape shape; + importer.readTensor(record_idx, input_idx, &dtype, &shape, input_data.data(), + input_data.size()); + + // Check the type and the shape of the input data is valid + verifyTypeShape(input_node, dtype, shape); + } + else + { + // Skip type/shape check for raw data + importer.readTensor(record_idx, input_idx, input_data.data(), input_data.size()); + } + + // TODO: Input data is copied twice (file -> buffer (input_data) -> interpreter inputs) + // We can redcue the copy by directly writing data from file to interpreter inputs + getInterpreter()->writeInputTensor(input_node, input_data.data(), input_data.size()); + } + + getInterpreter()->interpret(); + } + + std::cout << "Recording finished. Number of recorded data: " << num_records << std::endl; + } + catch (const H5::Exception &e) + { + H5::Exception::printErrorStack(); + throw std::runtime_error("HDF5 error occurred."); + } + + return getObserver()->hessianData(); +} + +} // namespace record_hessian diff --git a/compiler/record-hessian/tests/HessianComputer.test.cpp b/compiler/record-hessian/tests/HessianComputer.test.cpp new file mode 100644 index 00000000000..d7a08eb7ecc --- /dev/null +++ b/compiler/record-hessian/tests/HessianComputer.test.cpp @@ -0,0 +1,94 @@ +#include "record-hessian/HessianComputer.h" +#include +#include +#include +#include + +using namespace record_hessian; + +TEST(HessianComputerTest, recordHessianValidInput) +{ + + luci::CircleFullyConnected node; + + std::vector input_data = {1.0, 2.0, 3.0, 4.0}; + + luci_interpreter::DataType data_type = luci_interpreter::DataType::FLOAT32; + luci_interpreter::Shape shape({1, 4}); + luci_interpreter::AffineQuantization quantization; + quantization.scale = {1.0}; + quantization.zero_point = {0}; + + std::string tensor_name = "input_tensor"; + + luci_interpreter::Tensor input_tensor(data_type, shape, quantization, tensor_name); + + size_t data_size = input_data.size() * sizeof(float); + std::vector buffer(data_size); + + input_tensor.set_data_buffer(buffer.data()); + input_tensor.writeData(input_data.data(), data_size); + + HessianComputer computer; + + EXPECT_NO_THROW(computer.recordHessian(&node, &input_tensor)); +} + +TEST(HessianComputerTest, recordHessianValidInput_NEG) +{ + luci::CircleAdd node; + + std::vector input_data = {1.0, 2.0, 3.0, 4.0}; + + luci_interpreter::DataType data_type = luci_interpreter::DataType::FLOAT32; + luci_interpreter::Shape shape({1, 2, 2, 1}); + luci_interpreter::AffineQuantization quantization; + quantization.scale = {1.0}; + quantization.zero_point = {0}; + + std::string tensor_name = "input_tensor"; + + luci_interpreter::Tensor input_tensor(data_type, shape, quantization, tensor_name); + + size_t data_size = input_data.size() * sizeof(float); + std::vector buffer(data_size); + + input_tensor.set_data_buffer(buffer.data()); + input_tensor.writeData(input_data.data(), data_size); + + HessianComputer computer; + + EXPECT_ANY_THROW(computer.recordHessian(&node, &input_tensor)); +} + +TEST(HessianComputerTest, recordHessianNullTensor_NEG) +{ + luci::CircleAdd node; + HessianComputer computer; + EXPECT_ANY_THROW(computer.recordHessian(&node, nullptr)); +} + +TEST(HessianComputerTest, unfoldValidInput) +{ + std::vector buf = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0}; + uint32_t input_n = 1, input_h = 2, input_w = 2, input_c = 2; + uint32_t stride_h = 1, stride_w = 1, dilation_h = 1, dilation_w = 1; + uint32_t kernel_oc = 1, kernel_h = 2, kernel_w = 2, kernel_ic = 2; + + HessianComputer computer; + computer.unfold(buf, input_n, input_h, input_w, input_c, stride_h, stride_w, dilation_h, dilation_w, kernel_oc, kernel_h, kernel_w, kernel_ic); + std::vector expected_output = {1.0, 3.0, 5.0, 7.0, 2.0, 4.0, 6.0, 8.0}; + + EXPECT_EQ(buf, expected_output); +} + +TEST(HessianComputerTest, unfoldInvalidInput_NEG) +{ + std::vector buf = {1.0, 2.0, 3.0, 4.0}; + uint32_t input_n = 1, input_h = 2, input_w = 2, input_c = 1; + uint32_t stride_h = 1, stride_w = 1, dilation_h = 1, dilation_w = 1; + uint32_t kernel_oc = 1, kernel_h = 2, kernel_w = 2, kernel_ic = 2; + + HessianComputer computer; + EXPECT_ANY_THROW(computer.unfold(buf, input_n, input_h, input_w, input_c, stride_h, stride_w, dilation_h, dilation_w, kernel_oc, kernel_h, kernel_w, kernel_ic)); +}