From 28954099facba06f09efec8a1d25a514e85392f2 Mon Sep 17 00:00:00 2001 From: Bin Li Date: Thu, 26 Mar 2020 11:55:04 +0800 Subject: [PATCH] Support HTA custom API --- mace/core/BUILD.bazel | 32 +- mace/core/CMakeLists.txt | 18 +- mace/core/allocator.cc | 48 ++ mace/core/allocator.h | 53 +- mace/core/quantize.h | 3 + mace/core/rpcmem.cc | 54 ++ mace/core/rpcmem.h | 36 + .../core/runtime/hexagon/hexagon_allocator.cc | 38 ++ mace/core/runtime/hexagon/hexagon_allocator.h | 38 ++ .../runtime/hexagon/hexagon_control_wrapper.h | 18 +- mace/core/runtime/hexagon/hexagon_device.cc | 71 ++ mace/core/runtime/hexagon/hexagon_device.h | 48 +- .../runtime/hexagon/hexagon_dsp_wrapper.cc | 16 +- .../runtime/hexagon/hexagon_dsp_wrapper.h | 3 +- .../hexagon/hexagon_hta_transformer.cc | 644 ++++++++++++++++++ .../runtime/hexagon/hexagon_hta_transformer.h | 85 +++ .../runtime/hexagon/hexagon_hta_wrapper.cc | 289 +++++--- .../runtime/hexagon/hexagon_hta_wrapper.h | 35 +- mace/core/runtime/opencl/opencl_allocator.cc | 44 +- mace/core/runtime/opencl/opencl_allocator.h | 11 +- .../runtime/opencl/opencl_helper.cc} | 8 +- .../runtime/opencl/opencl_helper.h} | 8 +- mace/libmace/BUILD.bazel | 3 + mace/libmace/mace.cc | 34 +- mace/ops/opencl/buffer/buffer_transform.h | 2 +- .../opencl/buffer/buffer_type_transform.cc | 2 +- mace/ops/opencl/buffer/conv_2d.h | 2 +- mace/ops/opencl/buffer/conv_2d_1x1.cc | 2 +- mace/ops/opencl/buffer/conv_2d_general.cc | 2 +- mace/ops/opencl/buffer/depthwise_conv2d.h | 2 +- mace/ops/opencl/buffer/pooling.h | 2 +- mace/ops/opencl/buffer/reshape.h | 2 +- mace/ops/opencl/buffer/softmax.h | 2 +- mace/ops/opencl/buffer/utils.cc | 2 +- mace/ops/opencl/cl/buffer_transform.cl | 199 ++++++ mace/ops/opencl/image/activation.h | 2 +- mace/ops/opencl/image/addn.h | 2 +- mace/ops/opencl/image/batch_norm.h | 2 +- mace/ops/opencl/image/batch_to_space.h | 2 +- mace/ops/opencl/image/bias_add.h | 2 +- mace/ops/opencl/image/buffer_to_image.h | 2 +- mace/ops/opencl/image/channel_shuffle.h | 2 +- mace/ops/opencl/image/concat.h | 2 +- mace/ops/opencl/image/conv_2d.h | 2 +- mace/ops/opencl/image/conv_2d_1x1.cc | 2 +- mace/ops/opencl/image/conv_2d_3x3.cc | 2 +- mace/ops/opencl/image/conv_2d_general.cc | 2 +- mace/ops/opencl/image/crop.h | 2 +- mace/ops/opencl/image/deconv_2d.h | 2 +- mace/ops/opencl/image/depth_to_space.h | 2 +- mace/ops/opencl/image/depthwise_conv2d.h | 2 +- mace/ops/opencl/image/depthwise_deconv2d.h | 2 +- mace/ops/opencl/image/eltwise.h | 2 +- mace/ops/opencl/image/fully_connected.h | 2 +- mace/ops/opencl/image/image_to_buffer.h | 2 +- mace/ops/opencl/image/lpnorm.h | 2 +- mace/ops/opencl/image/lstm_cell.h | 2 +- mace/ops/opencl/image/matmul.h | 2 +- mace/ops/opencl/image/mvnorm.h | 2 +- mace/ops/opencl/image/pad.h | 2 +- mace/ops/opencl/image/pooling.h | 2 +- mace/ops/opencl/image/reduce.h | 2 +- mace/ops/opencl/image/reshape.h | 2 +- mace/ops/opencl/image/resize_bicubic.h | 2 +- mace/ops/opencl/image/resize_bilinear.h | 2 +- .../opencl/image/resize_nearest_neighbor.h | 2 +- mace/ops/opencl/image/softmax.h | 2 +- mace/ops/opencl/image/space_to_batch.h | 2 +- mace/ops/opencl/image/space_to_depth.h | 2 +- mace/ops/opencl/image/split.h | 2 +- mace/ops/opencl/image/sqrdiff_mean.h | 2 +- mace/ops/opencl/image/winograd_conv2d.cc | 2 +- mace/tools/mace_run.cc | 4 +- test/ccunit/BUILD.bazel | 7 +- test/ccunit/CMakeLists.txt | 4 + .../runtime/hexagon/hta_transform_test.cc | 152 +++++ .../ops/opencl/out_of_range_check_test.cc | 2 +- test/ccunit/mace/ops/quantize_test.cc | 1 + test/ccutils/mace/ops/testing/test_utils.h | 20 +- .../nnlib/arm64-v8a/libhexagon_controller.so | Bin 57016 -> 38944 bytes .../armeabi-v7a/libhexagon_controller.so | Bin 45004 -> 31208 bytes third_party/nnlib/hexnn_dsp_controller.h | 6 +- third_party/rpcmem/arm64-v8a/rpcmem.a | Bin 24682 -> 22602 bytes third_party/rpcmem/armeabi-v7a/rpcmem.a | Bin 18718 -> 17866 bytes third_party/rpcmem/rpcmem.h | 33 +- tools/bazel-build-standalone-lib.sh | 29 + tools/bazel_adb_run.py | 6 + tools/converter.py | 3 +- tools/python/transform/hexagon_converter.py | 14 + tools/sh_commands.py | 26 +- 90 files changed, 1882 insertions(+), 355 deletions(-) create mode 100644 mace/core/rpcmem.cc create mode 100644 mace/core/rpcmem.h create mode 100644 mace/core/runtime/hexagon/hexagon_allocator.cc create mode 100644 mace/core/runtime/hexagon/hexagon_allocator.h create mode 100644 mace/core/runtime/hexagon/hexagon_device.cc create mode 100644 mace/core/runtime/hexagon/hexagon_hta_transformer.cc create mode 100644 mace/core/runtime/hexagon/hexagon_hta_transformer.h rename mace/{ops/opencl/helper.cc => core/runtime/opencl/opencl_helper.cc} (98%) rename mace/{ops/opencl/helper.h => core/runtime/opencl/opencl_helper.h} (97%) create mode 100644 test/ccunit/mace/core/runtime/hexagon/hta_transform_test.cc diff --git a/mace/core/BUILD.bazel b/mace/core/BUILD.bazel index fcb7b207..971b2a27 100644 --- a/mace/core/BUILD.bazel +++ b/mace/core/BUILD.bazel @@ -28,21 +28,37 @@ cc_library( "*.cc", "runtime/cpu/*.cc", ], + exclude = [ + "rpcmem.cc", + ], ) + if_opencl_enabled(glob( [ "runtime/opencl/*.cc", ], - )) + if_hexagon_enabled([ + )) + if_hexagon_or_hta_enabled([ + "runtime/hexagon/hexagon_allocator.h", + "runtime/hexagon/hexagon_allocator.cc", + "runtime/hexagon/hexagon_device.cc", + ]) + if_hexagon_enabled([ "runtime/hexagon/hexagon_dsp_wrapper.cc", ]) + if_hta_enabled([ + "runtime/hexagon/hexagon_hta_transformer.h", + "runtime/hexagon/hexagon_hta_transformer.cc", "runtime/hexagon/hexagon_hta_wrapper.cc", ]) + if_apu_enabled(glob([ "runtime/apu/*.cc", - ])), - hdrs = glob([ - "*.h", - "runtime/cpu/*.h", - ]) + if_opencl_enabled(glob([ + ])) + if_rpcmem_enabled([ + "rpcmem.cc", + ]), + hdrs = glob( + [ + "*.h", + "runtime/cpu/*.h", + ], + exclude = [ + "rpcmem.h", + ], + ) + if_opencl_enabled(glob([ "runtime/opencl/*.h", ])) + if_hexagon_or_hta_enabled(glob([ "runtime/hexagon/hexagon_control_wrapper.h", @@ -53,7 +69,9 @@ cc_library( "runtime/hexagon/*hta*.h", ])) + if_apu_enabled(glob([ "runtime/apu/*.h" - ])), + ])) + if_rpcmem_enabled([ + "rpcmem.h", + ]), copts = [ "-Werror", "-Wextra", diff --git a/mace/core/CMakeLists.txt b/mace/core/CMakeLists.txt index 75b74bb9..25ab20bf 100644 --- a/mace/core/CMakeLists.txt +++ b/mace/core/CMakeLists.txt @@ -17,11 +17,17 @@ set(CORE_SRCS runtime/cpu/cpu_runtime.cc ) +if(MACE_ENABLE_RPCMEM) + set(CORE_SRCS ${CORE_SRCS} rpcmem.cc) + set(EXTRA_LINK_LIBS ${EXTRA_LINK_LIBS} rpcmem) +endif(MACE_ENABLE_RPCMEM) + if(MACE_ENABLE_OPENCL) set(CORE_SRCS ${CORE_SRCS} runtime/opencl/gpu_device.cc runtime/opencl/gpu_runtime.cc runtime/opencl/opencl_allocator.cc + runtime/opencl/opencl_helper.cc runtime/opencl/opencl_runtime.cc runtime/opencl/opencl_util.cc runtime/opencl/opencl_wrapper.cc @@ -30,12 +36,20 @@ if(MACE_ENABLE_OPENCL) set(EXTRA_LINK_LIBS ${EXTRA_LINK_LIBS} generated_opencl_kernel dl) endif(MACE_ENABLE_OPENCL) +if(MACE_ENABLE_HEXAGON_DSP OR MACE_ENABLE_HEXAGON_HTA) + set(CORE_SRCS ${CORE_SRCS} + runtime/hexagon/hexagon_allocator.cc + runtime/hexagon/hexagon_device.cc + ) +endif(MACE_ENABLE_HEXAGON_DSP OR MACE_ENABLE_HEXAGON_HTA) + if(MACE_ENABLE_HEXAGON_DSP) set(CORE_SRCS ${CORE_SRCS} runtime/hexagon/hexagon_dsp_wrapper.cc) set(EXTRA_LINK_LIBS ${EXTRA_LINK_LIBS} hexagon_controller) endif(MACE_ENABLE_HEXAGON_DSP) if(MACE_ENABLE_HEXAGON_HTA) + set(CORE_SRCS ${CORE_SRCS} runtime/hexagon/hexagon_hta_transformer.cc) set(CORE_SRCS ${CORE_SRCS} runtime/hexagon/hexagon_hta_wrapper.cc) set(EXTRA_LINK_LIBS ${EXTRA_LINK_LIBS} hta_hexagon_runtime) endif(MACE_ENABLE_HEXAGON_HTA) @@ -45,10 +59,6 @@ if(MACE_ENABLE_MTK_APU) set(EXTRA_LINK_LIBS ${EXTRA_LINK_LIBS} apu-frontend) endif(MACE_ENABLE_MTK_APU) -if(MACE_ENABLE_RPCMEM) - set(EXTRA_LINK_LIBS ${EXTRA_LINK_LIBS} rpcmem) -endif(MACE_ENABLE_RPCMEM) - add_library(core STATIC ${CORE_SRCS}) target_link_libraries(core PRIVATE proto diff --git a/mace/core/allocator.cc b/mace/core/allocator.cc index 003b1c2c..28ceeedc 100644 --- a/mace/core/allocator.cc +++ b/mace/core/allocator.cc @@ -16,6 +16,54 @@ namespace mace { +MaceStatus Allocator::NewImage(const std::vector &image_shape, + const DataType dt, + void **result) { + MACE_UNUSED(image_shape); + MACE_UNUSED(dt); + MACE_UNUSED(result); + MACE_NOT_IMPLEMENTED; + return MaceStatus::MACE_SUCCESS; +} + +void Allocator::DeleteImage(void *data) { + MACE_UNUSED(data); + MACE_NOT_IMPLEMENTED; +} + +void *Allocator::Map(void *buffer, + size_t offset, + size_t nbytes, + bool finish_cmd_queue) { + MACE_UNUSED(nbytes); + MACE_UNUSED(finish_cmd_queue); + return reinterpret_cast(buffer) + offset; +} + +void *Allocator::MapImage(void *buffer, + const std::vector &image_shape, + std::vector *mapped_image_pitch, + bool finish_cmd_queue) { + MACE_UNUSED(buffer); + MACE_UNUSED(image_shape); + MACE_UNUSED(mapped_image_pitch); + MACE_UNUSED(finish_cmd_queue); + MACE_NOT_IMPLEMENTED; + return nullptr; +} + +void Allocator::Unmap(void *buffer, void *mapper_ptr) { + MACE_UNUSED(buffer); + MACE_UNUSED(mapper_ptr); +} + +#ifdef MACE_ENABLE_RPCMEM +Rpcmem *Allocator::rpcmem() { + MACE_NOT_IMPLEMENTED; + return nullptr; +} +#endif // MACE_ENABLE_RPCMEM + Allocator *GetCPUAllocator() { static CPUAllocator allocator; return &allocator; diff --git a/mace/core/allocator.h b/mace/core/allocator.h index e4e7b35f..c9f8f8f6 100644 --- a/mace/core/allocator.h +++ b/mace/core/allocator.h @@ -27,6 +27,10 @@ #include "mace/public/mace.h" #include "mace/utils/logging.h" +#ifdef MACE_ENABLE_RPCMEM +#include "mace/core/rpcmem.h" +#endif // MACE_ENABLE_RPCMEM + namespace mace { #if defined(__hexagon__) @@ -50,19 +54,22 @@ class Allocator { virtual MaceStatus New(size_t nbytes, void **result) = 0; virtual MaceStatus NewImage(const std::vector &image_shape, const DataType dt, - void **result) = 0; + void **result); virtual void Delete(void *data) = 0; - virtual void DeleteImage(void *data) = 0; + virtual void DeleteImage(void *data); virtual void *Map(void *buffer, size_t offset, size_t nbytes, - bool finish_cmd_queue) const = 0; + bool finish_cmd_queue); virtual void *MapImage(void *buffer, const std::vector &image_shape, std::vector *mapped_image_pitch, - bool finish_cmd_queue) const = 0; - virtual void Unmap(void *buffer, void *mapper_ptr) const = 0; + bool finish_cmd_queue); + virtual void Unmap(void *buffer, void *mapper_ptr); virtual bool OnHost() const = 0; +#ifdef MACE_ENABLE_RPCMEM + virtual Rpcmem *rpcmem(); +#endif // MACE_ENABLE_RPCMEM }; class CPUAllocator : public Allocator { @@ -84,46 +91,12 @@ class CPUAllocator : public Allocator { return MaceStatus::MACE_SUCCESS; } - MaceStatus NewImage(const std::vector &shape, - const DataType dt, - void **result) override { - MACE_UNUSED(shape); - MACE_UNUSED(dt); - MACE_UNUSED(result); - LOG(FATAL) << "Allocate CPU image"; - return MaceStatus::MACE_SUCCESS; - } - void Delete(void *data) override { MACE_CHECK_NOTNULL(data); VLOG(3) << "Free CPU buffer"; free(data); } - void DeleteImage(void *data) override { - LOG(FATAL) << "Free CPU image"; - free(data); - }; - void *Map(void *buffer, - size_t offset, - size_t nbytes, - bool finish_cmd_queue) const override { - MACE_UNUSED(nbytes); - MACE_UNUSED(finish_cmd_queue); - return reinterpret_cast(buffer) + offset; - } - void *MapImage(void *buffer, - const std::vector &image_shape, - std::vector *mapped_image_pitch, - bool finish_cmd_queue) const override { - MACE_UNUSED(image_shape); - MACE_UNUSED(mapped_image_pitch); - MACE_UNUSED(finish_cmd_queue); - return buffer; - } - void Unmap(void *buffer, void *mapper_ptr) const override { - MACE_UNUSED(buffer); - MACE_UNUSED(mapper_ptr); - } + bool OnHost() const override { return true; } }; diff --git a/mace/core/quantize.h b/mace/core/quantize.h index c7b6666d..439c9522 100644 --- a/mace/core/quantize.h +++ b/mace/core/quantize.h @@ -117,9 +117,12 @@ inline void GetOutputMultiplierAndShift( template class QuantizeUtil { public: + QuantizeUtil() = default; explicit QuantizeUtil(utils::ThreadPool *thread_pool) : thread_pool_(thread_pool) {} + void Init(utils::ThreadPool *thread_pool) { thread_pool_ = thread_pool; } + void QuantizeWithScaleAndZeropoint(const float *input, const index_t size, float scale, diff --git a/mace/core/rpcmem.cc b/mace/core/rpcmem.cc new file mode 100644 index 00000000..b3b3ed39 --- /dev/null +++ b/mace/core/rpcmem.cc @@ -0,0 +1,54 @@ +// Copyright 2020 The MACE Authors. 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 "mace/core/rpcmem.h" + +#include "mace/utils/logging.h" + +namespace mace { + +Rpcmem::Rpcmem() { + rpcmem_init(&rm); + MACE_CHECK(rm.flag == 0, "rpcmem_init failed!"); +} + +Rpcmem::~Rpcmem() { + rpcmem_deinit(&rm); +} + +void *Rpcmem::New(int heapid, uint32_t flags, int nbytes) { + return rpcmem_alloc(&rm, heapid, flags, nbytes); +} + +void *Rpcmem::New(int nbytes) { + return New(RPCMEM_HEAP_ID_SYSTEM, RPCMEM_FLAG_CACHED, nbytes); +} + +void Rpcmem::Delete(void *data) { + rpcmem_free(&rm, data); +} + +int Rpcmem::ToFd(void *data) { + return rpcmem_to_fd(&rm, data); +} + +int Rpcmem::SyncCacheStart(void *data) { + return rpcmem_sync_cache(&rm, data, RPCMEM_SYNC_START); +} + +int Rpcmem::SyncCacheEnd(void *data) { + return rpcmem_sync_cache(&rm, data, RPCMEM_SYNC_END); +} + +} // namespace mace diff --git a/mace/core/rpcmem.h b/mace/core/rpcmem.h new file mode 100644 index 00000000..7d5795c2 --- /dev/null +++ b/mace/core/rpcmem.h @@ -0,0 +1,36 @@ +// Copyright 2020 The MACE Authors. 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 MACE_CORE_RPCMEM_H_ +#define MACE_CORE_RPCMEM_H_ + +#include "third_party/rpcmem/rpcmem.h" + +namespace mace { +class Rpcmem { + public: + Rpcmem(); + ~Rpcmem(); + void *New(int heapid, uint32_t flags, int nbytes); + void *New(int nbytes); + void Delete(void *data); + int ToFd(void *data); + int SyncCacheStart(void *data); + int SyncCacheEnd(void *data); + + private: + rpcmem rm; +}; +} // namespace mace +#endif // MACE_CORE_RPCMEM_H_ diff --git a/mace/core/runtime/hexagon/hexagon_allocator.cc b/mace/core/runtime/hexagon/hexagon_allocator.cc new file mode 100644 index 00000000..2c1f034c --- /dev/null +++ b/mace/core/runtime/hexagon/hexagon_allocator.cc @@ -0,0 +1,38 @@ +// Copyright 2020 The MACE Authors. 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 "mace/core/runtime/hexagon/hexagon_allocator.h" + +namespace mace { + +MaceStatus HexagonAllocator::New(size_t nbytes, void **result) { + *result = rpcmem_.New(nbytes); + MACE_CHECK_NOTNULL(*result); + memset(*result, 0, nbytes); + return MaceStatus::MACE_SUCCESS; +} + +void HexagonAllocator::Delete(void *data) { + rpcmem_.Delete(data); +} + +bool HexagonAllocator::OnHost() const { + return true; +} + +Rpcmem *HexagonAllocator::rpcmem() { + return &rpcmem_; +} + +} // namespace mace diff --git a/mace/core/runtime/hexagon/hexagon_allocator.h b/mace/core/runtime/hexagon/hexagon_allocator.h new file mode 100644 index 00000000..d4143996 --- /dev/null +++ b/mace/core/runtime/hexagon/hexagon_allocator.h @@ -0,0 +1,38 @@ +// Copyright 2020 The MACE Authors. 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 MACE_CORE_RUNTIME_HEXAGON_HEXAGON_ALLOCATOR_H_ +#define MACE_CORE_RUNTIME_HEXAGON_HEXAGON_ALLOCATOR_H_ + +#include "mace/core/allocator.h" + +namespace mace { + +class HexagonAllocator : public Allocator { + public: + MaceStatus New(size_t nbytes, void **result) override; + + void Delete(void *buffer) override; + + bool OnHost() const override; + + Rpcmem *rpcmem() override; + + private: + Rpcmem rpcmem_; +}; + +} // namespace mace + +#endif // MACE_CORE_RUNTIME_HEXAGON_HEXAGON_ALLOCATOR_H_ diff --git a/mace/core/runtime/hexagon/hexagon_control_wrapper.h b/mace/core/runtime/hexagon/hexagon_control_wrapper.h index 0ab7e3f9..801c095d 100644 --- a/mace/core/runtime/hexagon/hexagon_control_wrapper.h +++ b/mace/core/runtime/hexagon/hexagon_control_wrapper.h @@ -25,27 +25,15 @@ #include "mace/public/mace.h" namespace mace { - struct InOutInfo { InOutInfo(const std::string &name, const std::vector &shape, - const DataType data_type, - const float scale, - const int32_t zero_point, - std::unique_ptr tensor_u8) - : name(name), - shape(shape), - data_type(data_type), - scale(scale), - zero_point(zero_point), - tensor_u8(std::move(tensor_u8)) {} + const DataType data_type) + : name(name), shape(shape), data_type(data_type) {} std::string name; std::vector shape; DataType data_type; - float scale; - int32_t zero_point; - std::unique_ptr tensor_u8; }; class HexagonControlWrapper { @@ -79,8 +67,6 @@ class HexagonControlWrapper { int nn_id_; - std::vector input_info_; - std::vector output_info_; int num_inputs_; int num_outputs_; diff --git a/mace/core/runtime/hexagon/hexagon_device.cc b/mace/core/runtime/hexagon/hexagon_device.cc new file mode 100644 index 00000000..e8a0aa4e --- /dev/null +++ b/mace/core/runtime/hexagon/hexagon_device.cc @@ -0,0 +1,71 @@ +// Copyright 2020 The MACE Authors. 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 "mace/core/runtime/hexagon/hexagon_device.h" + +namespace mace { + +HexagonDevice::HexagonDevice(DeviceType device_type, + utils::ThreadPool *thread_pool +#ifdef MACE_ENABLE_OPENCL + , std::unique_ptr gpu_device +#endif // MACE_ENABLE_OPENCL + ) + : CPUDevice(0, AFFINITY_NONE, thread_pool), + allocator_(make_unique()), + device_type_(device_type) +#ifdef MACE_ENABLE_OPENCL + , gpu_device_(std::move(gpu_device)) +#endif // MACE_ENABLE_OPENCL + {} + +#ifdef MACE_ENABLE_OPENCL +GPURuntime *HexagonDevice::gpu_runtime() { + return gpu_device_->gpu_runtime(); +} +#endif // MACE_ENABLE_OPENCL + +Allocator *HexagonDevice::allocator() { +#ifdef MACE_ENABLE_OPENCL + return gpu_device_->allocator(); +#else + return allocator_.get(); +#endif // MACE_ENABLE_OPENCL +} + +DeviceType HexagonDevice::device_type() const { + return device_type_; +} + +std::unique_ptr CreateHexagonControlWrapper( + Device *device) { + std::unique_ptr hexagon_controller; + auto device_type = device->device_type(); + switch (device_type) { +#ifdef MACE_ENABLE_HEXAGON + case HEXAGON: + hexagon_controller = make_unique(); + break; +#endif +#ifdef MACE_ENABLE_HTA + case HTA: + hexagon_controller = make_unique(device); + break; +#endif + default:LOG(FATAL) << "Not supported Hexagon device type: " << device_type; + } + + return hexagon_controller; +} +} // namespace mace diff --git a/mace/core/runtime/hexagon/hexagon_device.h b/mace/core/runtime/hexagon/hexagon_device.h index b17b19e5..660965eb 100644 --- a/mace/core/runtime/hexagon/hexagon_device.h +++ b/mace/core/runtime/hexagon/hexagon_device.h @@ -19,6 +19,7 @@ #include #include "mace/core/device.h" +#include "mace/core/runtime/hexagon/hexagon_allocator.h" #include "mace/core/runtime/hexagon/hexagon_control_wrapper.h" #ifdef MACE_ENABLE_HEXAGON #include "mace/core/runtime/hexagon/hexagon_dsp_wrapper.h" @@ -26,44 +27,39 @@ #ifdef MACE_ENABLE_HTA #include "mace/core/runtime/hexagon/hexagon_hta_wrapper.h" #endif +#ifdef MACE_ENABLE_OPENCL +#include "mace/core/runtime/opencl/gpu_device.h" +#include "mace/core/runtime/opencl/gpu_runtime.h" +#endif namespace mace { class HexagonDevice : public CPUDevice { public: - explicit HexagonDevice(DeviceType device_type, - utils::ThreadPool *thread_pool) - : CPUDevice(0, AFFINITY_NONE, thread_pool), - device_type_(device_type) {} + HexagonDevice(DeviceType device_type, +#ifdef MACE_ENABLE_OPENCL + utils::ThreadPool *thread_pool, + std::unique_ptr gpu_device); +#else + utils::ThreadPool *thread_pool); +#endif // MACE_ENABLE_OPENCL - DeviceType device_type() const override { - return device_type_; - }; +#ifdef MACE_ENABLE_OPENCL + GPURuntime *gpu_runtime() override; +#endif // MACE_ENABLE_OPENCL + Allocator *allocator() override; + DeviceType device_type() const override; private: + std::unique_ptr allocator_; DeviceType device_type_; +#ifdef MACE_ENABLE_OPENCL + std::unique_ptr gpu_device_; +#endif // MACE_ENABLE_OPENCL }; std::unique_ptr CreateHexagonControlWrapper( - Device *device) { - std::unique_ptr hexagon_controller; - auto device_type = device->device_type(); - switch (device_type) { -#ifdef MACE_ENABLE_HEXAGON - case HEXAGON: - hexagon_controller = make_unique(); - break; -#endif -#ifdef MACE_ENABLE_HTA - case HTA: - hexagon_controller = make_unique(device); - break; -#endif - default:LOG(FATAL) << "Not supported Hexagon device type: " << device_type; - } - - return hexagon_controller; -} + Device *device); } // namespace mace #endif // MACE_CORE_RUNTIME_HEXAGON_HEXAGON_DEVICE_H_ diff --git a/mace/core/runtime/hexagon/hexagon_dsp_wrapper.cc b/mace/core/runtime/hexagon/hexagon_dsp_wrapper.cc index 9d17ab44..dedff7f9 100644 --- a/mace/core/runtime/hexagon/hexagon_dsp_wrapper.cc +++ b/mace/core/runtime/hexagon/hexagon_dsp_wrapper.cc @@ -107,8 +107,6 @@ hexagon_nn_corner_type TransformCornerType(HexagonNNCornerType corner) { } // namespace HexagonDSPWrapper::HexagonDSPWrapper() { - hexnn_controller_init(); - std::string env_log_execute_time_str; GetEnv("MACE_DSP_LOG_EXECUTE_TIME", &env_log_execute_time_str); if (env_log_execute_time_str.empty()) { @@ -119,9 +117,7 @@ HexagonDSPWrapper::HexagonDSPWrapper() { } } -HexagonDSPWrapper::~HexagonDSPWrapper() { - hexnn_controller_deinit(); -} +HexagonDSPWrapper::~HexagonDSPWrapper() {} int HexagonDSPWrapper::GetVersion() { int version; @@ -258,10 +254,7 @@ bool HexagonDSPWrapper::SetupGraph(const NetDef &net_def, } input_info_.emplace_back(input_info.name(), input_shape, - input_info.data_type(), - input_info.scale(), - input_info.zero_point(), - make_unique()); + input_info.data_type()); } // output info @@ -275,10 +268,7 @@ bool HexagonDSPWrapper::SetupGraph(const NetDef &net_def, } output_info_.emplace_back(output_info.name(), output_shape, - output_info.data_type(), - output_info.scale(), - output_info.zero_point(), - make_unique()); + output_info.data_type()); VLOG(1) << "OutputInfo: " << "\n\t shape: " << output_shape[0] << " " << output_shape[1] << " " << output_shape[2] << " " << output_shape[3] diff --git a/mace/core/runtime/hexagon/hexagon_dsp_wrapper.h b/mace/core/runtime/hexagon/hexagon_dsp_wrapper.h index 831d163f..2ec98e7a 100644 --- a/mace/core/runtime/hexagon/hexagon_dsp_wrapper.h +++ b/mace/core/runtime/hexagon/hexagon_dsp_wrapper.h @@ -56,7 +56,8 @@ class HexagonDSPWrapper : public HexagonControlWrapper { uint64_t GetLastExecuteCycles(); bool log_execute_time_; - + std::vector input_info_; + std::vector output_info_; MACE_DISABLE_COPY_AND_ASSIGN(HexagonDSPWrapper); }; } // namespace mace diff --git a/mace/core/runtime/hexagon/hexagon_hta_transformer.cc b/mace/core/runtime/hexagon/hexagon_hta_transformer.cc new file mode 100644 index 00000000..cf0b7531 --- /dev/null +++ b/mace/core/runtime/hexagon/hexagon_hta_transformer.cc @@ -0,0 +1,644 @@ +// Copyright 2020 The MACE Authors. 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 "mace/core/runtime/hexagon/hexagon_hta_transformer.h" + +#include +#include +#include +#include + +#include "mace/core/op_context.h" +#include "mace/core/quantize.h" +#include "mace/core/tensor.h" +#include "mace/core/types.h" +#include "mace/utils/math.h" + +#ifdef MACE_ENABLE_OPENCL +#include "mace/core/runtime/opencl/opencl_helper.h" +#endif // MACE_ENABLE_OPENCL + +namespace mace { +namespace { +template +class QuantizeTransformer; + +template <> +class QuantizeTransformer : public BaseTransformer { + public: + void Init(Device *device) override { + device_ = device; + quantize_util_.Init(&device_->cpu_runtime()->thread_pool()); + } + MaceStatus Compute(const Tensor *input, Tensor *output) override { + MACE_LATENCY_LOGGER(1, "Quantize on CPU"); + MACE_RETURN_IF_ERROR(output->ResizeLike(input)); + output->SetScale(input->scale()); + output->SetZeroPoint(input->zero_point()); + Tensor::MappingGuard input_guard(input); + Tensor::MappingGuard output_guard(output); + auto input_data = input->data(); + auto output_data = output->mutable_data(); + quantize_util_.QuantizeWithScaleAndZeropoint( + input_data, input->size(), input->scale(), input->zero_point(), + output_data); + return MaceStatus::MACE_SUCCESS; + } + + private: + QuantizeUtil quantize_util_; +}; + +#ifdef MACE_ENABLE_OPENCL +template <> +class QuantizeTransformer : public BaseTransformer { + public: + MaceStatus Compute(const Tensor *input, Tensor *output) override { + MACE_LATENCY_LOGGER(1, "Quantize on GPU"); + MACE_RETURN_IF_ERROR(output->ResizeLike(input)); + output->SetScale(input->scale()); + output->SetZeroPoint(input->zero_point()); + const uint32_t gws = static_cast(RoundUpDiv4(output->size())); + OpenCLRuntime *runtime = device_->gpu_runtime()->opencl_runtime(); + if (kernel_.get() == nullptr) { + std::set built_options; + std::string kernel_name = MACE_OBFUSCATE_SYMBOL("buffer_quantize"); + built_options.emplace("-Dbuffer_quantize=" + kernel_name); + built_options.emplace("-DIN_DATA_TYPE=" + DtToCLDt(input->dtype())); + built_options.emplace("-DDATA_TYPE=" + DtToCLDt(output->dtype())); + MACE_RETURN_IF_ERROR(runtime->BuildKernel("buffer_transform", kernel_name, + built_options, &kernel_)); + } + + uint32_t idx = 0; + kernel_.setArg(idx++, gws); + kernel_.setArg(idx++, input->scale()); + kernel_.setArg(idx++, input->zero_point()); + kernel_.setArg(idx++, *(input->opencl_buffer())); + MACE_CHECK(input->buffer_offset() % GetEnumTypeSize(input->dtype()) == 0, + "buffer offset not aligned"); + kernel_.setArg(idx++, + static_cast(input->buffer_offset() / + GetEnumTypeSize(input->dtype()))); + kernel_.setArg(idx++, *(output->opencl_buffer())); + + const uint32_t lws = static_cast( + RoundUpDiv4(runtime->GetDeviceMaxWorkGroupSize())); + cl::Event event; + cl_int error; + if (runtime->IsNonUniformWorkgroupsSupported()) { + error = runtime->command_queue().enqueueNDRangeKernel( + kernel_, cl::NullRange, cl::NDRange(gws), cl::NDRange(lws), nullptr, + &event); + } else { + uint32_t roundup_gws = RoundUp(gws, lws); + error = runtime->command_queue().enqueueNDRangeKernel( + kernel_, cl::NullRange, cl::NDRange(roundup_gws), cl::NDRange(lws), + nullptr, &event); + } + MACE_CL_RET_STATUS(error); + return MaceStatus::MACE_SUCCESS; + } + + private: + cl::Kernel kernel_; +}; +#endif // MACE_ENABLE_OPENCL + +template +class DequantizeTransformer; + +template <> +class DequantizeTransformer : public BaseTransformer { + public: + void Init(Device *device) override { + device_ = device; + quantize_util_.Init(&device_->cpu_runtime()->thread_pool()); + } + MaceStatus Compute(const Tensor *input, Tensor *output) override { + MACE_LATENCY_LOGGER(1, "Dequantize on CPU"); + MACE_RETURN_IF_ERROR(output->ResizeLike(input)); + output->SetScale(input->scale()); + output->SetZeroPoint(input->zero_point()); + Tensor::MappingGuard input_guard(input); + Tensor::MappingGuard output_guard(output); + auto input_data = input->data(); + auto output_data = output->mutable_data(); + quantize_util_.Dequantize(input_data, input->size(), input->scale(), + input->zero_point(), output_data); + return MaceStatus::MACE_SUCCESS; + } + + private: + QuantizeUtil quantize_util_; +}; + +#ifdef MACE_ENABLE_OPENCL +template <> +class DequantizeTransformer : public BaseTransformer { + public: + MaceStatus Compute(const Tensor *input, Tensor *output) override { + MACE_LATENCY_LOGGER(1, "Dequantize on GPU"); + MACE_RETURN_IF_ERROR(output->ResizeLike(input)); + output->SetScale(input->scale()); + output->SetZeroPoint(input->zero_point()); + const uint32_t gws = static_cast(RoundUpDiv4(output->size())); + OpenCLRuntime *runtime = device_->gpu_runtime()->opencl_runtime(); + if (kernel_.get() == nullptr) { + std::set built_options; + std::string kernel_name = MACE_OBFUSCATE_SYMBOL("buffer_dequantize"); + built_options.emplace("-Dbuffer_dequantize=" + kernel_name); + built_options.emplace("-DIN_DATA_TYPE=" + DtToCLDt(input->dtype())); + built_options.emplace("-DDATA_TYPE=" + DtToCLDt(output->dtype())); + MACE_RETURN_IF_ERROR(runtime->BuildKernel("buffer_transform", kernel_name, + built_options, &kernel_)); + } + + uint32_t idx = 0; + kernel_.setArg(idx++, gws); + kernel_.setArg(idx++, input->scale()); + kernel_.setArg(idx++, input->zero_point()); + kernel_.setArg(idx++, *(input->opencl_buffer())); + MACE_CHECK(input->buffer_offset() % GetEnumTypeSize(input->dtype()) == 0, + "buffer offset not aligned"); + kernel_.setArg(idx++, + static_cast(input->buffer_offset() / + GetEnumTypeSize(input->dtype()))); + kernel_.setArg(idx++, *(output->opencl_buffer())); + + const uint32_t lws = static_cast( + RoundUpDiv4(runtime->GetDeviceMaxWorkGroupSize())); + cl::Event event; + cl_int error; + if (runtime->IsNonUniformWorkgroupsSupported()) { + error = runtime->command_queue().enqueueNDRangeKernel( + kernel_, cl::NullRange, cl::NDRange(gws), cl::NDRange(lws), nullptr, + &event); + } else { + uint32_t roundup_gws = RoundUp(gws, lws); + error = runtime->command_queue().enqueueNDRangeKernel( + kernel_, cl::NullRange, cl::NDRange(roundup_gws), cl::NDRange(lws), + nullptr, &event); + } + MACE_CL_RET_STATUS(error); + return MaceStatus::MACE_SUCCESS; + } + + private: + cl::Kernel kernel_; +}; +#endif // MACE_ENABLE_OPENCL + +template +class NHWCToNCHW32Transformer; +template <> +class NHWCToNCHW32Transformer : public BaseTransformer { + public: + MaceStatus Compute(const Tensor *input, Tensor *output) override { + MACE_LATENCY_LOGGER(1, "NHWCToNCHW32Transformer on CPU"); + int32_t padding_value = input->zero_point(); + index_t batch = input->dim(0); + index_t height = input->dim(1); + index_t width = input->dim(2); + index_t channels = input->dim(3); + index_t height_stride = width * channels; + index_t batch_stride = height * width * channels; + + index_t output_width = RoundUp(width, 32); + index_t output_channel_stride = height * output_width; + index_t output_batch_stride = channels * height * output_width; + + output->Resize({batch, channels, height, output_width}); + + Tensor::MappingGuard input_mapper(input); + Tensor::MappingGuard output_mapper(output); + const auto input_data = input->data(); + auto output_data = output->mutable_data(); + + device_->cpu_runtime()->thread_pool().Compute2D( + [=](index_t start0, index_t end0, index_t step0, index_t start1, + index_t end1, index_t step1) { + for (index_t b = start0; b < end0; b += step0) { + for (index_t c = start1; c < end1; c += step1) { + index_t input_offset = b * batch_stride + c; + index_t output_offset = + b * output_batch_stride + c * output_channel_stride; + for (index_t h = 0; h < height; ++h) { + for (index_t w = 0; w < width; ++w) { + output_data[output_offset + w] = + input_data[input_offset + w * channels]; + } + std::fill_n(output_data + output_offset + width, + output_width - width, padding_value); + input_offset += height_stride; + output_offset += output_width; + } + } + } + }, + 0, batch, 1, 0, channels, 1); + + return MaceStatus::MACE_SUCCESS; + } +}; + +#ifdef MACE_ENABLE_OPENCL +template <> +class NHWCToNCHW32Transformer : public BaseTransformer { + public: + MaceStatus Compute(const Tensor *input, Tensor *output) override { + MACE_LATENCY_LOGGER(1, "NHWCToNCHW32Transformer on GPU"); + const index_t batch = input->dim(0); + const index_t height = input->dim(1); + const index_t width = input->dim(2); + const index_t channels = input->dim(3); + const index_t output_width = RoundUp(width, 32); + std::vector transformed_shape = {batch, channels, height, + output_width}; + uint32_t gws[3]; + gws[0] = static_cast(RoundUpDiv4(output_width)); + gws[1] = static_cast(height); + gws[2] = static_cast(batch * channels); + MACE_RETURN_IF_ERROR(output->Resize(transformed_shape)); + + if (kernel_.get() == nullptr) { + std::set built_options; + std::string kernel_name = + MACE_OBFUSCATE_SYMBOL("transform_nhwc_to_nchw32"); + built_options.emplace("-Dtransform_nhwc_to_nchw32=" + kernel_name); + std::string data_dt = DtToCLDt(input->dtype()); + built_options.emplace("-DIN_DATA_TYPE=" + data_dt); + built_options.emplace("-DDATA_TYPE=" + data_dt); + MACE_RETURN_IF_ERROR( + device_->gpu_runtime()->opencl_runtime()->BuildKernel( + "buffer_transform", kernel_name, built_options, &kernel_)); + } + uint32_t idx = 0; + MACE_SET_3D_GWS_ARGS(kernel_, gws); + kernel_.setArg(idx++, *(input->opencl_buffer())); + MACE_CHECK(input->buffer_offset() % GetEnumTypeSize(input->dtype()) == 0, + "buffer offset not aligned"); + kernel_.setArg(idx++, + static_cast(input->buffer_offset() / + GetEnumTypeSize(input->dtype()))); + kernel_.setArg(idx++, input->zero_point()); + kernel_.setArg(idx++, *(output->opencl_buffer())); + kernel_.setArg(idx++, static_cast(batch)); + kernel_.setArg(idx++, static_cast(height)); + kernel_.setArg(idx++, static_cast(width)); + kernel_.setArg(idx++, static_cast(channels)); + + std::string tuning_key = Concat("transform_nhwc_to_nchw32", + transformed_shape[0], transformed_shape[1], + transformed_shape[2], transformed_shape[3]); + std::vector lws = {4, 4, 4, 0}; + MACE_RETURN_IF_ERROR( + TuningOrRun3DKernel(device_->gpu_runtime()->opencl_runtime(), kernel_, + tuning_key, gws, lws, nullptr)); + + return MaceStatus::MACE_SUCCESS; + } + + private: + cl::Kernel kernel_; +}; +#endif // MACE_ENABLE_OPENCL + +template +class NHWCToD32Transformer; + +template <> +class NHWCToD32Transformer : public BaseTransformer { + public: + MaceStatus Compute(const Tensor *input, Tensor *output) override { + MACE_LATENCY_LOGGER(1, "NHWCToD32Transformer on CPU"); + index_t batch = input->dim(0); + index_t height = input->dim(1); + index_t width = input->dim(2); + index_t channels = input->dim(3); + index_t height_stride = width * channels; + index_t batch_stride = height * width * channels; + + index_t channel_slices = RoundUpDiv(channels, static_cast(32)); + index_t output_channel_slices_stride = width * 32; + index_t output_height_stride = channel_slices * width * 32; + index_t output_batch_stride = height * channel_slices * width * 32; + + std::vector output_shape{batch, height, channel_slices, width, 32}; + output->Resize(output_shape); + + Tensor::MappingGuard input_guard(input); + Tensor::MappingGuard output_guard(output); + auto input_data = input->data(); + auto output_data = output->mutable_data(); + std::fill_n(output_data, output->size(), input->zero_point()); + + device_->cpu_runtime()->thread_pool().Compute2D( + [=](index_t start0, index_t end0, index_t step0, index_t start1, + index_t end1, index_t step1) { + for (index_t b = start0; b < end0; b += step0) { + for (index_t h = start1; h < end1; h += step1) { + index_t input_offset = b * batch_stride + h * height_stride; + index_t output_offset = + b * output_batch_stride + h * output_height_stride; + for (index_t w = 0; w < width; ++w) { + for (index_t c = 0; c < channels; ++c) { + output_data[output_offset + + c / 32 * output_channel_slices_stride + c % 32] = + input_data[input_offset + c]; + } + input_offset += channels; + output_offset += 32; + } + } + } + }, + 0, batch, 1, 0, height, 1); + return MaceStatus::MACE_SUCCESS; + } +}; + +#ifdef MACE_ENABLE_OPENCL +template <> +class NHWCToD32Transformer : public BaseTransformer { + public: + MaceStatus Compute(const Tensor *input, Tensor *output) override { + MACE_LATENCY_LOGGER(1, "D32ToNHWCTransformer on GPU"); + const index_t batch = input->dim(0); + const index_t height = input->dim(1); + const index_t width = input->dim(2); + const index_t channels = input->dim(3); + const index_t channel_slices = RoundUpDiv(channels, 32); + std::vector output_shape{batch, height, channel_slices, width, 32}; + output->Resize(output_shape); + + uint32_t gws[3]; + gws[0] = static_cast(RoundUpDiv4(width * 32)); + gws[1] = static_cast(channel_slices); + gws[2] = static_cast(batch * height); + + if (kernel_.get() == nullptr) { + std::set built_options; + std::string kernel_name = MACE_OBFUSCATE_SYMBOL("transform_nhwc_to_d32"); + built_options.emplace("-Dtransform_nhwc_to_d32=" + kernel_name); + std::string data_dt = DtToCLDt(input->dtype()); + built_options.emplace("-DIN_DATA_TYPE=" + data_dt); + built_options.emplace("-DDATA_TYPE=" + data_dt); + MACE_RETURN_IF_ERROR( + device_->gpu_runtime()->opencl_runtime()->BuildKernel( + "buffer_transform", kernel_name, built_options, &kernel_)); + } + + uint32_t idx = 0; + MACE_SET_3D_GWS_ARGS(kernel_, gws); + kernel_.setArg(idx++, *(input->opencl_buffer())); + MACE_CHECK(input->buffer_offset() % GetEnumTypeSize(input->dtype()) == 0, + "buffer offset not aligned"); + kernel_.setArg(idx++, + static_cast(input->buffer_offset() / + GetEnumTypeSize(input->dtype()))); + kernel_.setArg(idx++, input->zero_point()); + kernel_.setArg(idx++, *(output->opencl_buffer())); + kernel_.setArg(idx++, static_cast(batch)); + kernel_.setArg(idx++, static_cast(height)); + kernel_.setArg(idx++, static_cast(width)); + kernel_.setArg(idx++, static_cast(channels)); + kernel_.setArg(idx++, static_cast(channel_slices)); + + + std::string tuning_key = + Concat("transform_nhwc_to_d32", batch, height, width, channels); + std::vector lws = {4, 4, 4, 0}; + MACE_RETURN_IF_ERROR( + TuningOrRun3DKernel(device_->gpu_runtime()->opencl_runtime(), kernel_, + tuning_key, gws, lws, nullptr)); + return MaceStatus::MACE_SUCCESS; + } + + private: + cl::Kernel kernel_; +}; +#endif // MACE_ENABLE_OPENCL + +template +class D32ToNHWCTransformer; + +template <> +class D32ToNHWCTransformer : public BaseTransformer { + public: + MaceStatus Compute(const Tensor *input, Tensor *output) override { + MACE_LATENCY_LOGGER(1, "D32ToNHWCTransformer on CPU"); + index_t batch = output->dim(0); + index_t height = output->dim(1); + index_t width = output->dim(2); + index_t channel = output->dim(3); + index_t height_stride = width * channel; + index_t batch_stride = height * width * channel; + + index_t channel_slices = RoundUpDiv(channel, static_cast(32)); + index_t input_channel_slices_stride = width * 32; + index_t input_height_stride = channel_slices * width * 32; + index_t input_batch_stride = height * channel_slices * width * 32; + + Tensor::MappingGuard input_guard(input); + Tensor::MappingGuard output_guard(output); + auto input_data = input->data(); + auto output_data = output->mutable_data(); + + device_->cpu_runtime()->thread_pool().Compute2D( + [=](index_t start0, index_t end0, index_t step0, index_t start1, + index_t end1, index_t step1) { + for (index_t b = start0; b < end0; b += step0) { + for (index_t h = start1; h < end1; h += step1) { + index_t input_offset = + b * input_batch_stride + h * input_height_stride; + index_t output_offset = b * batch_stride + h * height_stride; + for (index_t w = 0; w < width; ++w) { + for (index_t c = 0; c < channel; ++c) { + output_data[output_offset + c] = + input_data[input_offset + + c / 32 * input_channel_slices_stride + c % 32]; + } + input_offset += 32; + output_offset += channel; + } + } + } + }, + 0, batch, 1, 0, height, 1); + return MaceStatus::MACE_SUCCESS; + } +}; + +#ifdef MACE_ENABLE_OPENCL +template <> +class D32ToNHWCTransformer : public BaseTransformer { + public: + MaceStatus Compute(const Tensor *input, Tensor *output) override { + MACE_LATENCY_LOGGER(1, "D32ToNHWCTransformer on GPU"); + const index_t batch = output->dim(0); + const index_t height = output->dim(1); + const index_t width = output->dim(2); + const index_t channels = output->dim(3); + const index_t channel_slices = RoundUpDiv(channels, 32); + + uint32_t gws[3]; + gws[0] = static_cast(RoundUpDiv4(channels)); + gws[1] = static_cast(width); + gws[2] = static_cast(batch * height); + + if (kernel_.get() == nullptr) { + std::set built_options; + std::string kernel_name = MACE_OBFUSCATE_SYMBOL("transform_d32_to_nhwc"); + built_options.emplace("-Dtransform_d32_to_nhwc=" + kernel_name); + std::string data_dt = DtToCLDt(input->dtype()); + built_options.emplace("-DIN_DATA_TYPE=" + data_dt); + built_options.emplace("-DDATA_TYPE=" + data_dt); + MACE_RETURN_IF_ERROR( + device_->gpu_runtime()->opencl_runtime()->BuildKernel( + "buffer_transform", kernel_name, built_options, &kernel_)); + } + + uint32_t idx = 0; + MACE_SET_3D_GWS_ARGS(kernel_, gws); + kernel_.setArg(idx++, *(input->opencl_buffer())); + MACE_CHECK(input->buffer_offset() % GetEnumTypeSize(input->dtype()) == 0, + "buffer offset not aligned"); + kernel_.setArg(idx++, + static_cast(input->buffer_offset() / + GetEnumTypeSize(input->dtype()))); + kernel_.setArg(idx++, *(output->opencl_buffer())); + kernel_.setArg(idx++, static_cast(batch)); + kernel_.setArg(idx++, static_cast(height)); + kernel_.setArg(idx++, static_cast(width)); + kernel_.setArg(idx++, static_cast(channels)); + kernel_.setArg(idx++, static_cast(channel_slices)); + + + std::string tuning_key = + Concat("transform_d32_to_nhwc", batch, height, width, channels); + std::vector lws = {4, 4, 4, 0}; + MACE_RETURN_IF_ERROR( + TuningOrRun3DKernel(device_->gpu_runtime()->opencl_runtime(), kernel_, + tuning_key, gws, lws, nullptr)); + return MaceStatus::MACE_SUCCESS; + } + + private: + cl::Kernel kernel_; +}; +#endif // MACE_ENABLE_OPENCL +} // namespace + +template +void HexagonHTATranformer::Init(Device *device) { + device_ = device; + quantizer_ = make_unique>(); + quantizer_->Init(device); + dequantizer_ = make_unique>(); + dequantizer_->Init(device); +} + +template +MaceStatus HexagonHTATranformer::SetInputTransformer( + hexagon_hta_hw_layout format) { + switch (format) { + case HEXAGON_HTA_HW_FORMAT_D32: + input_transformers_.push_back(make_unique>()); + break; + case HEXAGON_HTA_HW_FORMAT_PLANAR: + input_transformers_.push_back(make_unique>()); + break; + case HEXAGON_HTA_HW_FORMAT_DEPTH_FIRST: + default: + MACE_NOT_IMPLEMENTED; + break; + } + input_transformers_.back()->Init(device_); + return MaceStatus::MACE_SUCCESS; +} + +template +MaceStatus HexagonHTATranformer::SetOutputTransformer( + hexagon_hta_hw_layout format) { + switch (format) { + case HEXAGON_HTA_HW_FORMAT_D32: + output_transformers_.push_back(make_unique>()); + break; + case HEXAGON_HTA_HW_FORMAT_PLANAR: + case HEXAGON_HTA_HW_FORMAT_DEPTH_FIRST: + default: + MACE_NOT_IMPLEMENTED; + break; + } + output_transformers_.back()->Init(device_); + return MaceStatus::MACE_SUCCESS; +} + +template +MaceStatus HexagonHTATranformer::TransformInput(const Tensor *input, + Tensor *output, + int index) { + return input_transformers_[index]->Compute(input, output); +} + +template +MaceStatus HexagonHTATranformer::TransformOutput(const Tensor *input, + Tensor *output, + int index) { + return output_transformers_[index]->Compute(input, output); +} + +template +MaceStatus HexagonHTATranformer::Quantize(const Tensor *input, + Tensor *output) { + return quantizer_->Compute(input, output); +} + +template +MaceStatus HexagonHTATranformer::Dequantize(const Tensor *input, + Tensor *output) { + return dequantizer_->Compute(input, output); +} + +template void HexagonHTATranformer::Init(Device *device); +template MaceStatus HexagonHTATranformer::Quantize(const Tensor *input, + Tensor *output); +template MaceStatus HexagonHTATranformer::Dequantize(const Tensor *input, + Tensor *output); +template MaceStatus HexagonHTATranformer::SetInputTransformer( + hexagon_hta_hw_layout format); +template MaceStatus HexagonHTATranformer::SetOutputTransformer( + hexagon_hta_hw_layout format); +template MaceStatus HexagonHTATranformer::TransformInput( + const Tensor *input, Tensor *output, int index); +template MaceStatus HexagonHTATranformer::TransformOutput( + const Tensor *input, Tensor *output, int index); + +#ifdef MACE_ENABLE_OPENCL +template void HexagonHTATranformer::Init(Device *device); +template MaceStatus HexagonHTATranformer::Quantize(const Tensor *input, + Tensor *output); +template MaceStatus HexagonHTATranformer::Dequantize(const Tensor *input, + Tensor *output); +template MaceStatus HexagonHTATranformer::SetInputTransformer( + hexagon_hta_hw_layout format); +template MaceStatus HexagonHTATranformer::SetOutputTransformer( + hexagon_hta_hw_layout format); +template MaceStatus HexagonHTATranformer::TransformInput( + const Tensor *input, Tensor *output, int index); +template MaceStatus HexagonHTATranformer::TransformOutput( + const Tensor *input, Tensor *output, int index); +#endif // MACE_ENABLE_OPENCL +} // namespace mace diff --git a/mace/core/runtime/hexagon/hexagon_hta_transformer.h b/mace/core/runtime/hexagon/hexagon_hta_transformer.h new file mode 100644 index 00000000..74369a4b --- /dev/null +++ b/mace/core/runtime/hexagon/hexagon_hta_transformer.h @@ -0,0 +1,85 @@ +// Copyright 2020 The MACE Authors. 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 MACE_CORE_RUNTIME_HEXAGON_HEXAGON_HTA_TRANSFORMER_H_ +#define MACE_CORE_RUNTIME_HEXAGON_HEXAGON_HTA_TRANSFORMER_H_ + +#include +#include + +#include "mace/core/device.h" +#include "mace/core/tensor.h" +#include "mace/core/types.h" +#include "mace/utils/math.h" +#include "mace/utils/thread_pool.h" +#include "third_party/hta/hta_hexagon_api.h" + +namespace mace { +class BaseTransformer { + public: + BaseTransformer() = default; + virtual ~BaseTransformer() = default; + + virtual void Init(Device *device) { device_ = device; } + virtual MaceStatus Compute(const Tensor *input, Tensor *output) = 0; + + protected: + Device *device_; +}; + +class HexagonHTATranformerBase { + public: + HexagonHTATranformerBase() = default; + virtual ~HexagonHTATranformerBase() = default; + + virtual void Init(Device *device) = 0; + virtual MaceStatus SetInputTransformer( + const hexagon_hta_hw_layout format) = 0; + virtual MaceStatus SetOutputTransformer( + const hexagon_hta_hw_layout format) = 0; + virtual MaceStatus Quantize(const Tensor *input, Tensor *output) = 0; + virtual MaceStatus Dequantize(const Tensor *input, Tensor *output) = 0; + virtual MaceStatus TransformInput(const Tensor *input, + Tensor *output, + int index) = 0; + virtual MaceStatus TransformOutput(const Tensor *input, + Tensor *output, + int index) = 0; +}; + +template +class HexagonHTATranformer : public HexagonHTATranformerBase { + public: + void Init(Device *device) override; + MaceStatus SetInputTransformer(const hexagon_hta_hw_layout format) override; + MaceStatus SetOutputTransformer(const hexagon_hta_hw_layout format) override; + + MaceStatus Quantize(const Tensor *input, Tensor *output) override; + MaceStatus Dequantize(const Tensor *input, Tensor *output) override; + MaceStatus TransformInput(const Tensor *input, + Tensor *output, + int index) override; + MaceStatus TransformOutput(const Tensor *input, + Tensor *output, + int index) override; + + private: + Device *device_; + std::unique_ptr quantizer_; + std::unique_ptr dequantizer_; + std::vector> input_transformers_; + std::vector> output_transformers_; +}; +} // namespace mace +#endif // MACE_CORE_RUNTIME_HEXAGON_HEXAGON_HTA_TRANSFORMER_H_ diff --git a/mace/core/runtime/hexagon/hexagon_hta_wrapper.cc b/mace/core/runtime/hexagon/hexagon_hta_wrapper.cc index 069eab30..24a5eb82 100644 --- a/mace/core/runtime/hexagon/hexagon_hta_wrapper.cc +++ b/mace/core/runtime/hexagon/hexagon_hta_wrapper.cc @@ -13,7 +13,7 @@ // limitations under the License. #include "mace/core/runtime/hexagon/hexagon_hta_wrapper.h" - +#include #include #include #include @@ -24,48 +24,59 @@ #include #include "mace/core/runtime/hexagon/hexagon_hta_ops.h" +#include "mace/core/runtime/hexagon/hexagon_hta_transformer.h" #include "mace/core/types.h" #include "mace/utils/memory.h" -#include "mace/core/quantize.h" #include "third_party/hta/hta_hexagon_api.h" - namespace mace { namespace { -struct InputOutputMetadata { - void Init(float min_val, float max_val, int needs_quantization) { - this->min_val = min_val; - this->max_val = max_val; - this->needs_quantization = needs_quantization; +int GetHTAEnv(const std::string &name, int default_value) { + int value = default_value; + std::string env_str; + MaceStatus status = GetEnv(name.c_str(), &env_str); + if (status == MaceStatus::MACE_SUCCESS && !env_str.empty()) { + value = std::atoi(env_str.c_str()); } - float min_val; - float max_val; - int needs_quantization; -}; - -template -void AddInputMetadata(const T &data, hexagon_hta_nn_tensordef *tensor) { - tensor->batches = 1; - tensor->height = 1; - tensor->width = 1; - tensor->depth = 1; - tensor->data = const_cast( - reinterpret_cast(&data)); - tensor->dataLen = sizeof(data); - tensor->data_valid_len = sizeof(data); - tensor->unused = 0; + return value; } -template -void AddOutputMetadata(const T &data, hexagon_hta_nn_tensordef *tensor) { - tensor->data = const_cast( - reinterpret_cast(&data)); - tensor->dataLen = sizeof(data); +// Print the API logs to standard output. +void HtaApiLog(hexagon_hta_nn_nn_id id, + const char *api_op, + const char *const format, + ...) { + va_list arg; + va_start(arg, format); + if (api_op != NULL) { + printf("Graph ID: %d\t", id); + } + vfprintf(stdout, format, arg); + va_end(arg); +} +// Print the performance stats to standard output. +void HtaPerformanceLog(int log_level, + uint32_t network_handle, + uint32_t thread_id, + const char *const format, + ...) { + va_list arg; + va_start(arg, format); + printf("Log Level: %d, Network Handle: %d, Thread ID: %d - ", log_level, + network_handle, thread_id); + vfprintf(stdout, format, arg); + va_end(arg); } } // namespace HexagonHTAWrapper::HexagonHTAWrapper(Device *device) - : quantize_util_(&device->cpu_runtime()->thread_pool()) { + : allocator_(device->allocator()), +#ifdef MACE_ENABLE_OPENCL + transformer_(make_unique>()) { +#else + transformer_(make_unique>()) { +#endif + transformer_->Init(device); } int HexagonHTAWrapper::GetVersion() { @@ -84,6 +95,40 @@ bool HexagonHTAWrapper::Init() { LOG(INFO) << "Hexagon init"; MACE_CHECK(hexagon_hta_nn_init(&nn_id_) == 0, "hexagon_nn_init failed"); ResetPerfInfo(); + + int ret; + int power_level = GetHTAEnv("MACE_HTA_POWER_LEVEL", -1); + if (power_level != -1) { + ret = hexagon_hta_nn_set_config_params(nn_id_, HTA_NN_CONFIG_POWER_LEVEL, + &power_level, sizeof(power_level)); + LOG(INFO) << "HTA_NN_CONFIG_POWER_LEVEL: " << power_level + << " returns: " << ret; + } + + int is_compress = GetHTAEnv("MACE_HTA_BANDWIDTH_COMPRESSION", 1); + if (is_compress) { + ret = hexagon_hta_nn_set_config_params(nn_id_, + HTA_NN_CONFIG_BANDWIDTH_COMPRESSION, + &is_compress, sizeof(is_compress)); + LOG(INFO) << "HTA_NN_CONFIG_BANDWIDTH_COMPRESSION: " << is_compress + << " returns: " << ret; + } + + if (VLOG_IS_ON(2)) { + ret = hexagon_hta_nn_set_config_params( + nn_id_, HTA_NN_CONFIG_PERFORMANCE_LOG, + reinterpret_cast(&HtaPerformanceLog), + sizeof(&HtaPerformanceLog)); + MACE_CHECK(ret == 0, "HTA_NN_CONFIG_PERFORMANCE_LOG returns: " , ret); + } + + if (VLOG_IS_ON(3)) { + ret = hexagon_hta_nn_set_config_params(nn_id_, HTA_NN_CONFIG_API_LOG, + reinterpret_cast(&HtaApiLog), + sizeof(&HtaApiLog)); + MACE_CHECK(ret == 0, "HTA_NN_CONFIG_API_LOG returns: ", ret); + } + return true; } @@ -172,52 +217,107 @@ bool HexagonHTAWrapper::SetupGraph(const NetDef &net_def, outputs.size()); } + int64_t t1 = NowMicros(); + + MACE_CHECK(hexagon_hta_nn_prepare(nn_id_) == 0, "hexagon_nn_prepare failed"); + + int64_t t2 = NowMicros(); + + VLOG(1) << "Setup time: " << t1 - t0 << " " << t2 - t1; + // input info num_inputs_ = net_def.input_info_size(); input_info_.reserve(num_inputs_); - for (const InputOutputInfo &input_info : net_def.input_info()) { + input_tensordef_.resize(num_inputs_); + for (int index = 0; index < num_inputs_; ++index) { + auto input_info = net_def.input_info(index); std::vector input_shape(input_info.dims().begin(), input_info.dims().end()); while (input_shape.size() < 4) { input_shape.insert(input_shape.begin(), 1); } - input_info_.emplace_back(input_info.name(), - input_shape, - input_info.data_type(), - input_info.scale(), - input_info.zero_point(), - make_unique()); + + auto quantized_tensor = make_unique(allocator_, DT_UINT8); + auto hta_tensor = make_unique(allocator_, DT_UINT8); + hexagon_hta_nn_hw_tensordef &input_tensordef = input_tensordef_[index]; + memset(&input_tensordef, 0, sizeof(input_tensordef)); + MACE_CHECK(hexagon_hta_nn_get_memory_layout(nn_id_, 0, index, + &input_tensordef) == 0); + input_tensordef.dataLen = input_tensordef.batchStride; + VLOG(1) << input_tensordef.format << " " << input_tensordef.elementSize + << " " << input_tensordef.numDims << " " + << input_tensordef.batchStride; + for (uint32_t i = 0; i < input_tensordef.numDims; ++i) { + VLOG(1) << input_tensordef.dim[i].length << " " + << input_tensordef.dim[i].lpadding << " " + << input_tensordef.dim[i].valid; + } + hta_tensor->Resize({input_tensordef.dataLen}); + MACE_CHECK(hta_tensor->raw_size() == input_tensordef.dataLen); + Tensor::MappingGuard input_guard(hta_tensor.get()); + input_tensordef.fd = + allocator_->rpcmem()->ToFd(hta_tensor->mutable_data()); + MACE_CHECK(hexagon_hta_nn_register_tensor(nn_id_, &input_tensordef) == 0); + + transformer_->SetInputTransformer(input_tensordef.format); + + input_info_.emplace_back( + input_info.name(), input_shape, input_info.data_type(), + input_info.scale(), input_info.zero_point(), + std::move(quantized_tensor), + std::move(hta_tensor)); } // output info num_outputs_ = net_def.output_info_size(); output_info_.reserve(num_outputs_); - for (const InputOutputInfo &output_info : net_def.output_info()) { + output_tensordef_.resize(num_outputs_); + for (int index = 0; index < num_outputs_; ++index) { + auto output_info = net_def.output_info(index); std::vector output_shape(output_info.dims().begin(), output_info.dims().end()); while (output_shape.size() < 4) { output_shape.insert(output_shape.begin(), 1); } - output_info_.emplace_back(output_info.name(), - output_shape, - output_info.data_type(), - output_info.scale(), - output_info.zero_point(), - make_unique()); + + auto quantized_tensor = make_unique(allocator_, DT_UINT8); + auto hta_tensor = make_unique(allocator_, DT_UINT8); + quantized_tensor->SetScale(output_info.scale()); + quantized_tensor->SetZeroPoint(output_info.zero_point()); + + hexagon_hta_nn_hw_tensordef &output_tensordef = output_tensordef_[index]; + memset(&output_tensordef, 0, sizeof(output_tensordef)); + MACE_CHECK(hexagon_hta_nn_get_memory_layout(nn_id_, 1, index, + &output_tensordef) == 0); + output_tensordef.dataLen = output_tensordef.batchStride; + VLOG(1) << output_tensordef.format << " " << output_tensordef.elementSize + << " " << output_tensordef.numDims << " " + << output_tensordef.batchStride; + for (uint32_t i = 0; i < output_tensordef.numDims; ++i) { + VLOG(1) << output_tensordef.dim[i].length << " " + << output_tensordef.dim[i].lpadding << " " + << output_tensordef.dim[i].valid; + } + hta_tensor->Resize({output_tensordef.batchStride}); + MACE_CHECK(hta_tensor->raw_size() == output_tensordef.dataLen); + Tensor::MappingGuard output_guard(hta_tensor.get()); + output_tensordef.fd = + allocator_->rpcmem()->ToFd(hta_tensor->mutable_data()); + MACE_CHECK(hexagon_hta_nn_register_tensor(nn_id_, &output_tensordef) == 0); + + transformer_->SetOutputTransformer(output_tensordef.format); + + output_info_.emplace_back( + output_info.name(), output_shape, output_info.data_type(), + output_info.scale(), output_info.zero_point(), + std::move(quantized_tensor), std::move(hta_tensor)); + VLOG(1) << "OutputInfo: " << "\n\t shape: " << output_shape[0] << " " << output_shape[1] << " " << output_shape[2] << " " << output_shape[3] << "\n\t type: " << output_info.data_type(); } - int64_t t1 = NowMicros(); - - MACE_CHECK(hexagon_hta_nn_prepare(nn_id_) == 0, "hexagon_nn_prepare failed"); - - int64_t t2 = NowMicros(); - - VLOG(1) << "Setup time: " << t1 - t0 << " " << t2 - t1; - return true; } @@ -266,78 +366,41 @@ bool HexagonHTAWrapper::ExecuteGraphNew( MACE_CHECK(num_outputs_ == static_cast(num_outputs), "Wrong outputs num"); - std::vector inputs(num_inputs * kNumMetaData); - std::vector outputs(num_outputs * kNumMetaData); - std::vector input_metadata(num_inputs); - std::vector output_metadata(num_outputs); - for (size_t i = 0; i < num_inputs; ++i) { const auto input_tensor = input_tensors.at(input_info_[i].name); - const auto &input_shape = input_tensor->shape(); - size_t index = i * kNumMetaData; - inputs[index].batches = static_cast(input_shape[0]); - inputs[index].height = static_cast(input_shape[1]); - inputs[index].width = static_cast(input_shape[2]); - inputs[index].depth = static_cast(input_shape[3]); - inputs[index].data = const_cast( - reinterpret_cast(input_tensor->raw_data())); - inputs[index].dataLen = static_cast(input_tensor->raw_size()); - inputs[index].data_valid_len = - static_cast(input_tensor->raw_size()); - inputs[index].unused = 0; - input_metadata[i].Init(.0f, .0f, 1); - AddInputMetadata(input_metadata[i].min_val, &inputs[index + 1]); - AddInputMetadata(input_metadata[i].max_val, &inputs[index + 2]); - AddInputMetadata(input_metadata[i].needs_quantization, &inputs[index + 3]); - } + input_tensor->SetScale(input_info_[i].scale); + input_tensor->SetZeroPoint(input_info_[i].zero_point); + MACE_CHECK_SUCCESS( + transformer_->Quantize(input_tensors.at(input_info_[i].name), + input_info_[i].quantized_tensor.get())); - // transform mace output to hexagon output - for (size_t i = 0; i < num_outputs; ++i) { - auto output_tensor = output_tensors->at(output_info_[i].name); - size_t index = i * kNumMetaData; - output_tensor->SetDtype(output_info_[i].data_type); - output_tensor->Resize(output_info_[i].shape); - - outputs[index].data = reinterpret_cast( - output_tensor->raw_mutable_data()); - outputs[index].dataLen = static_cast(output_tensor->raw_size()); - output_metadata[i].Init(.0f, .0f, 1); - - AddOutputMetadata(output_metadata[i].min_val, &outputs[index + 1]); - AddOutputMetadata(output_metadata[i].max_val, &outputs[index + 2]); - AddOutputMetadata(output_metadata[i].needs_quantization, - &outputs[index + 3]); + MACE_CHECK_SUCCESS(transformer_->TransformInput( + input_info_[i].quantized_tensor.get(), + input_info_[i].hta_tensor.get(), i)); + + Tensor::MappingGuard input_guard(input_info_[i].hta_tensor.get()); } - int res = hexagon_hta_nn_execute_new(nn_id_, - inputs.data(), - num_inputs * kNumMetaData, - outputs.data(), - num_outputs * kNumMetaData); - MACE_CHECK(res == 0, "execute error"); + MACE_CHECK(hexagon_hta_nn_execute_hw(nn_id_, + input_tensordef_.data(), num_inputs, + output_tensordef_.data(), num_outputs, + nullptr, nullptr) == 0); for (size_t i = 0; i < num_outputs; ++i) { - size_t index = i * kNumMetaData; - std::vector output_shape{ - outputs[index].batches, outputs[index].height, outputs[index].width, - outputs[index].depth}; - MACE_CHECK(output_shape.size() == output_info_[i].shape.size(), - output_shape.size(), " vs ", output_info_[i].shape.size(), - " wrong output shape inferred"); - for (size_t j = 0; j < output_shape.size(); ++j) { - MACE_CHECK(static_cast(output_shape[j]) - == output_info_[i].shape[j], - output_shape[j], " vs ", output_info_[i].shape[j], - " wrong output shape[", j, "] inferred"); + { // To sync cache + Tensor::MappingGuard output_guard(output_info_[i].hta_tensor.get()); } + output_info_[i].quantized_tensor->Resize(output_info_[i].shape); + transformer_->TransformOutput(output_info_[i].hta_tensor.get(), + output_info_[i].quantized_tensor.get(), i); + + auto output_tensor = output_tensors->at(output_info_[i].name); - MACE_CHECK(static_cast(outputs[index].data_valid_len) - == output_tensor->raw_size(), - outputs[index].data_valid_len, " vs ", output_tensor->raw_size(), - " wrong output bytes inferred."); + MaceStatus st = transformer_->Dequantize( + output_info_[i].quantized_tensor.get(), output_tensor); } - return res == 0; + return true; } } // namespace mace diff --git a/mace/core/runtime/hexagon/hexagon_hta_wrapper.h b/mace/core/runtime/hexagon/hexagon_hta_wrapper.h index 6b33514c..aabcdc61 100644 --- a/mace/core/runtime/hexagon/hexagon_hta_wrapper.h +++ b/mace/core/runtime/hexagon/hexagon_hta_wrapper.h @@ -16,18 +16,40 @@ #define MACE_CORE_RUNTIME_HEXAGON_HEXAGON_HTA_WRAPPER_H_ #include +#include #include #include +#include -#include "mace/utils/thread_pool.h" -#include "mace/core/quantize.h" +#include "mace/core/device.h" #include "mace/core/runtime/hexagon/hexagon_control_wrapper.h" +#include "mace/core/runtime/hexagon/hexagon_hta_transformer.h" #include "mace/core/tensor.h" -#include "mace/core/device.h" #include "mace/public/mace.h" +#include "third_party/hta/hta_hexagon_api.h" namespace mace { +struct HTAInOutInfo : public InOutInfo { + HTAInOutInfo(const std::string &name, + const std::vector &shape, + const DataType data_type, + const float scale, + const int32_t zero_point, + std::unique_ptr quantized_tensor, + std::unique_ptr hta_tensor) + : InOutInfo(name, shape, data_type), + scale(scale), + zero_point(zero_point), + quantized_tensor(std::move(quantized_tensor)), + hta_tensor(std::move(hta_tensor)) {} + + float scale; + int32_t zero_point; + std::unique_ptr quantized_tensor; + std::unique_ptr hta_tensor; +}; + class HexagonHTAWrapper : public HexagonControlWrapper { public: explicit HexagonHTAWrapper(Device *device); @@ -50,7 +72,12 @@ class HexagonHTAWrapper : public HexagonControlWrapper { void SetDebugLevel(int level) override; private: - QuantizeUtil quantize_util_; + Allocator *allocator_; + std::vector input_info_; + std::vector output_info_; + std::vector input_tensordef_; + std::vector output_tensordef_; + std::unique_ptr transformer_; MACE_DISABLE_COPY_AND_ASSIGN(HexagonHTAWrapper); }; } // namespace mace diff --git a/mace/core/runtime/opencl/opencl_allocator.cc b/mace/core/runtime/opencl/opencl_allocator.cc index b0bf041b..a0865958 100644 --- a/mace/core/runtime/opencl/opencl_allocator.cc +++ b/mace/core/runtime/opencl/opencl_allocator.cc @@ -16,9 +16,6 @@ #include "mace/core/runtime/opencl/opencl_allocator.h" #include "mace/core/runtime/opencl/opencl_runtime.h" -#ifdef MACE_ENABLE_RPCMEM -#include "third_party/rpcmem/rpcmem.h" -#endif // MACE_ENABLE_RPCMEM namespace mace { namespace { @@ -39,24 +36,10 @@ static cl_channel_type DataTypeToCLChannelType(const DataType t) { } } -#ifdef MACE_ENABLE_RPCMEM -std::once_flag ion_prepared; -void PrepareQualcommION() { - rpcmem_init(); - std::atexit(rpcmem_deinit); -} -#endif // MACE_ENABLE_RPCMEM - } // namespace OpenCLAllocator::OpenCLAllocator( - OpenCLRuntime *opencl_runtime): opencl_runtime_(opencl_runtime) { -#ifdef MACE_ENABLE_RPCMEM - if (opencl_runtime_->ion_type() == IONType::QUALCOMM_ION) { - std::call_once(ion_prepared, PrepareQualcommION); - } -#endif // MACE_ENABLE_RPCMEM -} + OpenCLRuntime *opencl_runtime): opencl_runtime_(opencl_runtime) {} OpenCLAllocator::~OpenCLAllocator() {} @@ -168,7 +151,7 @@ void OpenCLAllocator::Delete(void *buffer) { if (opencl_runtime_->ion_type() == IONType::QUALCOMM_ION) { auto it = cl_to_host_map_.find(buffer); MACE_CHECK(it != cl_to_host_map_.end(), "OpenCL buffer not found!"); - rpcmem_free(it->second); + rpcmem_.Delete(it->second); cl_to_host_map_.erase(buffer); } #endif // MACE_ENABLE_RPCMEM @@ -184,7 +167,7 @@ void OpenCLAllocator::DeleteImage(void *buffer) { if (opencl_runtime_->ion_type() == IONType::QUALCOMM_ION) { auto it = cl_to_host_map_.find(buffer); MACE_CHECK(it != cl_to_host_map_.end(), "OpenCL image not found!"); - rpcmem_free(it->second); + rpcmem_.Delete(it->second); cl_to_host_map_.erase(buffer); } #endif // MACE_ENABLE_RPCMEM @@ -194,7 +177,7 @@ void OpenCLAllocator::DeleteImage(void *buffer) { void *OpenCLAllocator::Map(void *buffer, size_t offset, size_t nbytes, - bool finish_cmd_queue) const { + bool finish_cmd_queue) { MACE_LATENCY_LOGGER(1, "Map OpenCL buffer"); void *mapped_ptr = nullptr; #ifdef MACE_ENABLE_RPCMEM @@ -209,7 +192,7 @@ void *OpenCLAllocator::Map(void *buffer, if (opencl_runtime_->qcom_host_cache_policy() == CL_MEM_HOST_WRITEBACK_QCOM) { - MACE_CHECK(rpcmem_sync_cache(mapped_ptr, RPCMEM_SYNC_START) == 0); + MACE_CHECK(rpcmem_.SyncCacheStart(mapped_ptr) == 0); } } else { #endif // MACE_ENABLE_RPCMEM @@ -234,7 +217,7 @@ void *OpenCLAllocator::Map(void *buffer, void *OpenCLAllocator::MapImage(void *buffer, const std::vector &image_shape, std::vector *mapped_image_pitch, - bool finish_cmd_queue) const { + bool finish_cmd_queue) { MACE_LATENCY_LOGGER(1, "Map OpenCL Image"); MACE_CHECK(image_shape.size() == 2) << "Just support map 2d image"; void *mapped_ptr = nullptr; @@ -251,7 +234,7 @@ void *OpenCLAllocator::MapImage(void *buffer, if (opencl_runtime_->qcom_host_cache_policy() == CL_MEM_HOST_WRITEBACK_QCOM) { - MACE_CHECK(rpcmem_sync_cache(mapped_ptr, RPCMEM_SYNC_START) == 0); + MACE_CHECK(rpcmem_.SyncCacheStart(mapped_ptr) == 0); } } else { #endif // MACE_ENABLE_RPCMEM @@ -275,13 +258,13 @@ void *OpenCLAllocator::MapImage(void *buffer, return mapped_ptr; } -void OpenCLAllocator::Unmap(void *buffer, void *mapped_ptr) const { +void OpenCLAllocator::Unmap(void *buffer, void *mapped_ptr) { MACE_LATENCY_LOGGER(1, "Unmap OpenCL buffer/Image"); #ifdef MACE_ENABLE_RPCMEM if (opencl_runtime_->ion_type() == IONType::QUALCOMM_ION) { if (opencl_runtime_->qcom_host_cache_policy() == CL_MEM_HOST_WRITEBACK_QCOM) { - MACE_CHECK(rpcmem_sync_cache(mapped_ptr, RPCMEM_SYNC_END) == 0); + MACE_CHECK(rpcmem_.SyncCacheEnd(mapped_ptr) == 0); } } else { #endif // MACE_ENABLE_RPCMEM @@ -301,17 +284,20 @@ void OpenCLAllocator::Unmap(void *buffer, void *mapped_ptr) const { bool OpenCLAllocator::OnHost() const { return false; } #ifdef MACE_ENABLE_RPCMEM +Rpcmem *OpenCLAllocator::rpcmem() { + return &rpcmem_; +} + void OpenCLAllocator::CreateQualcommBufferIONHostPtr( const size_t nbytes, cl_mem_ion_host_ptr *ion_host) { - void *host = rpcmem_alloc(RPCMEM_HEAP_ID_SYSTEM, RPCMEM_FLAG_CACHED, - nbytes + opencl_runtime_->qcom_ext_mem_padding()); + void *host = rpcmem_.New(nbytes + opencl_runtime_->qcom_ext_mem_padding()); MACE_CHECK_NOTNULL(host); auto host_addr = reinterpret_cast(host); auto page_size = opencl_runtime_->qcom_page_size(); MACE_CHECK(host_addr % page_size == 0, "ION memory address: ", host_addr, " must be aligned to page size: ", page_size); - int fd = rpcmem_to_fd(host); + int fd = rpcmem_.ToFd(host); MACE_CHECK(fd >= 0, "Invalid rpcmem file descriptor: ", fd); ion_host->ext_host_ptr.allocation_type = CL_MEM_ION_HOST_PTR_QCOM; diff --git a/mace/core/runtime/opencl/opencl_allocator.h b/mace/core/runtime/opencl/opencl_allocator.h index 0c2783a1..5411a93e 100644 --- a/mace/core/runtime/opencl/opencl_allocator.h +++ b/mace/core/runtime/opencl/opencl_allocator.h @@ -48,17 +48,21 @@ class OpenCLAllocator : public Allocator { void *Map(void *buffer, size_t offset, size_t nbytes, - bool finish_cmd_queue) const override; + bool finish_cmd_queue) override; void *MapImage(void *buffer, const std::vector &image_shape, std::vector *mapped_image_pitch, - bool finish_cmd_queue) const override; + bool finish_cmd_queue) override; - void Unmap(void *buffer, void *mapped_ptr) const override; + void Unmap(void *buffer, void *mapped_ptr) override; bool OnHost() const override; +#ifdef MACE_ENABLE_RPCMEM + Rpcmem *rpcmem() override; +#endif // MACE_ENABLE_RPCMEM + private: #ifdef MACE_ENABLE_RPCMEM void CreateQualcommBufferIONHostPtr(const size_t nbytes, @@ -69,6 +73,7 @@ class OpenCLAllocator : public Allocator { cl_mem_ion_host_ptr *ion_host); std::unordered_map cl_to_host_map_; + Rpcmem rpcmem_; #endif // MACE_ENABLE_RPCMEM OpenCLRuntime *opencl_runtime_; }; diff --git a/mace/ops/opencl/helper.cc b/mace/core/runtime/opencl/opencl_helper.cc similarity index 98% rename from mace/ops/opencl/helper.cc rename to mace/core/runtime/opencl/opencl_helper.cc index 16acafb5..a216b621 100644 --- a/mace/ops/opencl/helper.cc +++ b/mace/core/runtime/opencl/opencl_helper.cc @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" #include #include @@ -22,7 +22,6 @@ #include "mace/utils/math.h" namespace mace { -namespace ops { std::vector FormatBufferShape( const std::vector &buffer_shape, @@ -59,8 +58,10 @@ std::string DtToCLDt(const DataType dt) { return "float"; case DT_HALF: return "half"; + case DT_UINT8: + return "uchar"; default: - LOG(FATAL) << "Unsupported data type"; + LOG(FATAL) << "Unsupported data type: " << dt; return ""; } } @@ -365,5 +366,4 @@ MaceStatus TuningOrRun2DKernel(OpenCLRuntime *runtime, return MaceStatus::MACE_SUCCESS; } -} // namespace ops } // namespace mace diff --git a/mace/ops/opencl/helper.h b/mace/core/runtime/opencl/opencl_helper.h similarity index 97% rename from mace/ops/opencl/helper.h rename to mace/core/runtime/opencl/opencl_helper.h index a9e9866c..4d10862a 100644 --- a/mace/ops/opencl/helper.h +++ b/mace/core/runtime/opencl/opencl_helper.h @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef MACE_OPS_OPENCL_HELPER_H_ -#define MACE_OPS_OPENCL_HELPER_H_ +#ifndef MACE_CORE_RUNTIME_OPENCL_OPENCL_HELPER_H_ +#define MACE_CORE_RUNTIME_OPENCL_OPENCL_HELPER_H_ #include #include @@ -30,7 +30,6 @@ #include "mace/utils/math.h" namespace mace { -namespace ops { // oorc for 'Out Of Range Check' #define MACE_OUT_OF_RANGE_DEFINITION \ std::shared_ptr oorc_flag; @@ -161,6 +160,5 @@ std::vector Default3DLocalWS(OpenCLRuntime *runtime, const uint32_t *gws, const uint32_t kwg_size); -} // namespace ops } // namespace mace -#endif // MACE_OPS_OPENCL_HELPER_H_ +#endif // MACE_CORE_RUNTIME_OPENCL_OPENCL_HELPER_H_ diff --git a/mace/libmace/BUILD.bazel b/mace/libmace/BUILD.bazel index 2d16fd94..8b540b53 100644 --- a/mace/libmace/BUILD.bazel +++ b/mace/libmace/BUILD.bazel @@ -21,6 +21,7 @@ load( "if_opencl_enabled", "if_openmp_enabled", "if_quantize_enabled", + "if_rpcmem_enabled", ) cc_library( @@ -47,6 +48,8 @@ cc_library( "-DMACE_ENABLE_HTA", ]) + if_apu_enabled([ "-DMACE_ENABLE_APU", + ]) + if_rpcmem_enabled([ + "-DMACE_ENABLE_RPCMEM", ]), deps = [ "//mace/ops", diff --git a/mace/libmace/mace.cc b/mace/libmace/mace.cc index 3ab4b13e..b9d3b13c 100644 --- a/mace/libmace/mace.cc +++ b/mace/libmace/mace.cc @@ -520,9 +520,23 @@ MaceEngine::Impl::Impl(const MaceEngineConfig &config) #if defined(MACE_ENABLE_HEXAGON) || defined(MACE_ENABLE_HTA) if (device_type_ == DeviceType::HEXAGON || device_type_ == DeviceType::HTA) { +#ifdef MACE_ENABLE_OPENCL + device_.reset(new HexagonDevice( + device_type_, thread_pool_.get(), + make_unique( + config.impl_->gpu_context()->opencl_tuner(), + config.impl_->gpu_context()->opencl_cache_storage(), + config.impl_->gpu_priority_hint(), + config.impl_->gpu_perf_hint(), + config.impl_->gpu_context()->opencl_binary_storage(), + config.impl_->num_threads(), + config.impl_->cpu_affinity_policy(), + thread_pool_.get()))); +#else device_.reset(new HexagonDevice(device_type_, thread_pool_.get())); +#endif // MACE_ENABLE_OPENCL } -#endif +#endif // defined(MACE_ENABLE_HEXAGON) || defined(MACE_ENABLE_HTA) #ifdef MACE_ENABLE_APU if (device_type_ == DeviceType::APU) { device_.reset(new ApuDevice(thread_pool_.get())); @@ -579,15 +593,19 @@ MaceStatus MaceEngine::Impl::Init( << MakeString(MapKeys(output_info_map_)); } #if defined(MACE_ENABLE_HEXAGON) || defined(MACE_ENABLE_HTA) - DataType output_dt = output_info_map_[output_name].data_type(); - Tensor *output_tensor = - ws_->CreateTensor(output_name, device_->allocator(), output_dt); - output_tensor->set_data_format(DataFormat::NHWC); + if (device_type_ == HEXAGON || device_type_ == HTA) { + DataType output_dt = output_info_map_[output_name].data_type(); + Tensor *output_tensor = + ws_->CreateTensor(output_name, device_->allocator(), output_dt); + output_tensor->set_data_format(DataFormat::NHWC); + } #endif #if defined(MACE_ENABLE_APU) - Tensor *output_tensor = - ws_->CreateTensor(output_name, device_->allocator(), DT_FLOAT); - output_tensor->set_data_format(DataFormat::NHWC); + if (device_type_ == DeviceType::APU) { + Tensor *output_tensor = + ws_->CreateTensor(output_name, device_->allocator(), DT_FLOAT); + output_tensor->set_data_format(DataFormat::NHWC); + } #endif } #ifdef MACE_ENABLE_HEXAGON diff --git a/mace/ops/opencl/buffer/buffer_transform.h b/mace/ops/opencl/buffer/buffer_transform.h index c32ccbb1..25415877 100644 --- a/mace/ops/opencl/buffer/buffer_transform.h +++ b/mace/ops/opencl/buffer/buffer_transform.h @@ -21,7 +21,7 @@ #include "mace/core/op_context.h" #include "mace/core/tensor.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" namespace mace { namespace ops { diff --git a/mace/ops/opencl/buffer/buffer_type_transform.cc b/mace/ops/opencl/buffer/buffer_type_transform.cc index 2cb3ae00..688ded66 100644 --- a/mace/ops/opencl/buffer/buffer_type_transform.cc +++ b/mace/ops/opencl/buffer/buffer_type_transform.cc @@ -15,7 +15,7 @@ #include "mace/core/op_context.h" #include "mace/core/runtime/opencl/opencl_runtime.h" #include "mace/core/tensor.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" namespace mace { namespace ops { diff --git a/mace/ops/opencl/buffer/conv_2d.h b/mace/ops/opencl/buffer/conv_2d.h index c50752c3..563b8358 100644 --- a/mace/ops/opencl/buffer/conv_2d.h +++ b/mace/ops/opencl/buffer/conv_2d.h @@ -21,7 +21,7 @@ #include #include "mace/ops/opencl/buffer/utils.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" #include "mace/utils/memory.h" namespace mace { diff --git a/mace/ops/opencl/buffer/conv_2d_1x1.cc b/mace/ops/opencl/buffer/conv_2d_1x1.cc index 6eeb0f1d..95c85b17 100644 --- a/mace/ops/opencl/buffer/conv_2d_1x1.cc +++ b/mace/ops/opencl/buffer/conv_2d_1x1.cc @@ -15,7 +15,7 @@ #include "mace/core/op_context.h" #include "mace/core/runtime/opencl/opencl_runtime.h" #include "mace/ops/common/activation_type.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" namespace mace { namespace ops { diff --git a/mace/ops/opencl/buffer/conv_2d_general.cc b/mace/ops/opencl/buffer/conv_2d_general.cc index b19b7020..4c03ee2a 100644 --- a/mace/ops/opencl/buffer/conv_2d_general.cc +++ b/mace/ops/opencl/buffer/conv_2d_general.cc @@ -15,7 +15,7 @@ #include "mace/core/op_context.h" #include "mace/core/runtime/opencl/opencl_runtime.h" #include "mace/ops/common/activation_type.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" namespace mace { namespace ops { diff --git a/mace/ops/opencl/buffer/depthwise_conv2d.h b/mace/ops/opencl/buffer/depthwise_conv2d.h index 98dffa12..60d68077 100644 --- a/mace/ops/opencl/buffer/depthwise_conv2d.h +++ b/mace/ops/opencl/buffer/depthwise_conv2d.h @@ -21,7 +21,7 @@ #include #include "mace/ops/opencl/buffer/utils.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" #include "mace/utils/memory.h" namespace mace { diff --git a/mace/ops/opencl/buffer/pooling.h b/mace/ops/opencl/buffer/pooling.h index 9e675e29..952a8f5e 100644 --- a/mace/ops/opencl/buffer/pooling.h +++ b/mace/ops/opencl/buffer/pooling.h @@ -23,7 +23,7 @@ #include #include "mace/ops/opencl/buffer/utils.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" #include "mace/utils/memory.h" namespace mace { diff --git a/mace/ops/opencl/buffer/reshape.h b/mace/ops/opencl/buffer/reshape.h index f030f1e7..bc572acf 100644 --- a/mace/ops/opencl/buffer/reshape.h +++ b/mace/ops/opencl/buffer/reshape.h @@ -19,7 +19,7 @@ #include -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" namespace mace { namespace ops { diff --git a/mace/ops/opencl/buffer/softmax.h b/mace/ops/opencl/buffer/softmax.h index 05d27cac..0acae465 100644 --- a/mace/ops/opencl/buffer/softmax.h +++ b/mace/ops/opencl/buffer/softmax.h @@ -23,7 +23,7 @@ #include "mace/core/op_context.h" #include "mace/core/tensor.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" namespace mace { namespace ops { diff --git a/mace/ops/opencl/buffer/utils.cc b/mace/ops/opencl/buffer/utils.cc index 536a7e0d..3e15115c 100644 --- a/mace/ops/opencl/buffer/utils.cc +++ b/mace/ops/opencl/buffer/utils.cc @@ -19,7 +19,7 @@ #include #include "mace/core/runtime/opencl/opencl_runtime.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" namespace mace { namespace ops { diff --git a/mace/ops/opencl/cl/buffer_transform.cl b/mace/ops/opencl/cl/buffer_transform.cl index 0a7674d6..9e554dcf 100644 --- a/mace/ops/opencl/cl/buffer_transform.cl +++ b/mace/ops/opencl/cl/buffer_transform.cl @@ -209,3 +209,202 @@ __kernel void transform_data_type(BUFFER_OUT_OF_RANGE_PARAMS DATA_TYPE4 input_value = CONVERT4(vload4(out_idx, input + input_offset)); vstore4(input_value, out_idx, output); } + +__kernel void buffer_quantize(BUFFER_OUT_OF_RANGE_PARAMS + __private const int global_size_dim0, + __private const float scale, + __private const int zero_point, + __global float *input, + __private const int input_offset, + __global uchar *output) { + const int out_idx = get_global_id(0); + +#ifndef NON_UNIFORM_WORK_GROUP + if (out_idx >= global_size_dim0) { + return; + } +#endif + + uchar4 output_value = + convert_uchar4_sat_rte(vload4(out_idx, input) / scale + zero_point); + vstore4(output_value, out_idx, output); +} + +__kernel void buffer_dequantize(BUFFER_OUT_OF_RANGE_PARAMS + __private const int global_size_dim0, + __private const float scale, + __private const int zero_point, + __global uchar *input, + __private const int input_offset, + __global float *output) { + const int out_idx = get_global_id(0); + +#ifndef NON_UNIFORM_WORK_GROUP + if (out_idx >= global_size_dim0) { + return; + } +#endif + float4 output_value = + convert_float4(convert_int4(vload4(out_idx, input)) - zero_point) * scale; + vstore4(output_value, out_idx, output); +} + + +// NHWC -> NCHW (W roundup to 32) +__kernel void transform_nhwc_to_nchw32(BUFFER_OUT_OF_RANGE_PARAMS + GLOBAL_WORK_GROUP_SIZE_DIM3 + __global uchar *input, // NHWC + __private const int input_offset, + __private const int zero_point, + __global uchar *output, + __private const int batch, + __private const int height, + __private const int width, + __private const int channels) { + const int width_blk_idx = get_global_id(0); + const int h_idx = get_global_id(1); + const int bc_idx = get_global_id(2); + +#ifndef NON_UNIFORM_WORK_GROUP + if (width_blk_idx >= global_size_dim0 || + h_idx >= global_size_dim1 || + bc_idx >= global_size_dim2) { + return; + } +#endif + const int b_idx = bc_idx / channels; + const int chan_idx = bc_idx - mul24(b_idx, channels); + const int w_idx = width_blk_idx << 2; + const int padded_width = global_size_dim0 << 2; + + const int in_offset = mad24(mad24(mad24(b_idx, height, h_idx), + width, w_idx), channels, chan_idx) + input_offset; + const int out_offset = (mad24(mad24(mad24(b_idx, channels, chan_idx), + height, h_idx), padded_width, w_idx)); + + uchar4 value = zero_point; + if (w_idx + 3 < width) { + value.x = input[in_offset]; + value.y = input[in_offset + channels]; + value.z = input[in_offset + 2 * channels]; + value.w = input[in_offset + 3 * channels]; + } else if (w_idx < width) { + const int diff = width - w_idx; + switch(diff) { + case 3: + value.z = input[in_offset + 2 * channels]; + case 2: + value.y = input[in_offset + channels]; + case 1: + value.x = input[in_offset]; + } + } + VSTORE4(value, output, out_offset); +} + + +// N H ceil(C/32) W 32 -> NHWC +__kernel void transform_d32_to_nhwc(BUFFER_OUT_OF_RANGE_PARAMS + GLOBAL_WORK_GROUP_SIZE_DIM3 + __global uchar *input, // D32 + __private const int input_offset, + __global uchar *output, // NHWC + __private const int batch, + __private const int height, + __private const int width, + __private const int channels, + __private const int channel_slices) { + const int chan_blk_idx = get_global_id(0); + const int w_idx = get_global_id(1); + const int bh_idx = get_global_id(2); + +#ifndef NON_UNIFORM_WORK_GROUP + if (chan_blk_idx >= global_size_dim0 || + w_idx >= global_size_dim1 || + bh_idx >= global_size_dim2) { + return; + } +#endif + const int b_idx = bh_idx / height; + const int h_idx = bh_idx - mul24(b_idx, height); + const int c_idx = chan_blk_idx << 2; + const int c_slice = c_idx >> 5; + const int c_slice_idx = c_idx & 31; + + const int in_offset = mad24(mad24(mad24(mad24(b_idx, height, h_idx), + channel_slices, c_slice), width, w_idx), 32, c_slice_idx) + input_offset; + const int out_offset = (mad24(mad24(mad24(b_idx, height, h_idx), + width, w_idx), channels, c_idx)); + + uchar4 value = vload4(0, input + in_offset); + if (c_idx + 3 < channels) { + VSTORE4(value, output, out_offset); + } else { + const int diff = channels - c_idx; + switch(diff) { + case 3: + vstore3(value.xyz, 0, output + out_offset); + break; + case 2: + vstore2(value.xy, 0, output + out_offset); + break; + case 1: + output[out_offset] = value.x; + break; + } + } +} + +// NHWC -> N H ceil(C/32) W 32 +__kernel void transform_nhwc_to_d32(BUFFER_OUT_OF_RANGE_PARAMS + GLOBAL_WORK_GROUP_SIZE_DIM3 + __global uchar *input, // NHWC + __private const int input_offset, + __private const int zero_point, + __global uchar *output, // D32 + __private const int batch, + __private const int height, + __private const int width, + __private const int channels, + __private const int channel_slices) { + const int w_32_idx = get_global_id(0); + const int c_slice = get_global_id(1); + const int bh_idx = get_global_id(2); + +#ifndef NON_UNIFORM_WORK_GROUP + if (w_32_idx >= global_size_dim0 || + c_slice >= global_size_dim1 || + bh_idx >= global_size_dim2) { + return; + } +#endif + const int b_idx = bh_idx / height; + const int h_idx = bh_idx - mul24(b_idx, height); + const int w_idx = w_32_idx >> 3; + const int c_slice_blk_idx = w_32_idx & 7; + const int c_slice_idx = c_slice_blk_idx << 2; + const int c_idx = (c_slice << 5) + c_slice_idx; + + const int in_offset = (mad24(mad24(mad24(b_idx, height, h_idx), + width, w_idx), channels, c_idx)) + input_offset; + const int out_offset = mad24(mad24(mad24(mad24(b_idx, height, h_idx), + channel_slices, c_slice), width, w_idx), 32, c_slice_idx); + + uchar4 value = zero_point; + if (c_idx + 3 < channels) { + value = vload4(0, input + in_offset); + } else if (c_idx < channels) { + value = vload4(0, input + in_offset); + const int diff = channels - c_idx; + switch(diff) { + case 3: + value.w = zero_point; break; + case 2: + value.zw = zero_point; break; + case 1: + value.yzw = zero_point; break; + } + } // else value = zero_point + + VSTORE4(value, output, out_offset); +} diff --git a/mace/ops/opencl/image/activation.h b/mace/ops/opencl/image/activation.h index e98b5e9d..929d267d 100644 --- a/mace/ops/opencl/image/activation.h +++ b/mace/ops/opencl/image/activation.h @@ -24,7 +24,7 @@ #include "mace/core/op_context.h" #include "mace/core/tensor.h" #include "mace/ops/common/activation_type.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" namespace mace { namespace ops { diff --git a/mace/ops/opencl/image/addn.h b/mace/ops/opencl/image/addn.h index b163152b..575dee22 100644 --- a/mace/ops/opencl/image/addn.h +++ b/mace/ops/opencl/image/addn.h @@ -23,7 +23,7 @@ #include "mace/core/op_context.h" #include "mace/core/tensor.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" namespace mace { namespace ops { diff --git a/mace/ops/opencl/image/batch_norm.h b/mace/ops/opencl/image/batch_norm.h index b2201a96..6b777368 100644 --- a/mace/ops/opencl/image/batch_norm.h +++ b/mace/ops/opencl/image/batch_norm.h @@ -24,7 +24,7 @@ #include "mace/core/op_context.h" #include "mace/core/tensor.h" #include "mace/ops/common/activation_type.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" namespace mace { namespace ops { diff --git a/mace/ops/opencl/image/batch_to_space.h b/mace/ops/opencl/image/batch_to_space.h index a0aced7c..a9d047aa 100644 --- a/mace/ops/opencl/image/batch_to_space.h +++ b/mace/ops/opencl/image/batch_to_space.h @@ -23,7 +23,7 @@ #include "mace/core/op_context.h" #include "mace/core/tensor.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" namespace mace { namespace ops { diff --git a/mace/ops/opencl/image/bias_add.h b/mace/ops/opencl/image/bias_add.h index 7c25662d..67644d6a 100644 --- a/mace/ops/opencl/image/bias_add.h +++ b/mace/ops/opencl/image/bias_add.h @@ -23,7 +23,7 @@ #include "mace/core/op_context.h" #include "mace/core/tensor.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" namespace mace { namespace ops { diff --git a/mace/ops/opencl/image/buffer_to_image.h b/mace/ops/opencl/image/buffer_to_image.h index 493f6579..33891182 100644 --- a/mace/ops/opencl/image/buffer_to_image.h +++ b/mace/ops/opencl/image/buffer_to_image.h @@ -23,7 +23,7 @@ #include "mace/core/op_context.h" #include "mace/core/tensor.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" namespace mace { namespace ops { diff --git a/mace/ops/opencl/image/channel_shuffle.h b/mace/ops/opencl/image/channel_shuffle.h index 371ecf22..94448d80 100644 --- a/mace/ops/opencl/image/channel_shuffle.h +++ b/mace/ops/opencl/image/channel_shuffle.h @@ -23,7 +23,7 @@ #include "mace/core/op_context.h" #include "mace/core/tensor.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" namespace mace { namespace ops { diff --git a/mace/ops/opencl/image/concat.h b/mace/ops/opencl/image/concat.h index f1e51fd9..e5cd2977 100644 --- a/mace/ops/opencl/image/concat.h +++ b/mace/ops/opencl/image/concat.h @@ -21,7 +21,7 @@ #include "mace/core/op_context.h" #include "mace/core/tensor.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" namespace mace { namespace ops { diff --git a/mace/ops/opencl/image/conv_2d.h b/mace/ops/opencl/image/conv_2d.h index a1ee3301..6044c1a7 100644 --- a/mace/ops/opencl/image/conv_2d.h +++ b/mace/ops/opencl/image/conv_2d.h @@ -21,7 +21,7 @@ #include "mace/core/op_context.h" #include "mace/core/tensor.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" namespace mace { namespace ops { diff --git a/mace/ops/opencl/image/conv_2d_1x1.cc b/mace/ops/opencl/image/conv_2d_1x1.cc index 71824015..494672a4 100644 --- a/mace/ops/opencl/image/conv_2d_1x1.cc +++ b/mace/ops/opencl/image/conv_2d_1x1.cc @@ -15,7 +15,7 @@ #include "mace/core/op_context.h" #include "mace/core/runtime/opencl/opencl_runtime.h" #include "mace/ops/common/activation_type.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" namespace mace { namespace ops { diff --git a/mace/ops/opencl/image/conv_2d_3x3.cc b/mace/ops/opencl/image/conv_2d_3x3.cc index d8a8b9cf..8bfc988c 100644 --- a/mace/ops/opencl/image/conv_2d_3x3.cc +++ b/mace/ops/opencl/image/conv_2d_3x3.cc @@ -15,7 +15,7 @@ #include "mace/core/op_context.h" #include "mace/core/runtime/opencl/opencl_runtime.h" #include "mace/ops/common/activation_type.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" #include "mace/utils/math.h" namespace mace { diff --git a/mace/ops/opencl/image/conv_2d_general.cc b/mace/ops/opencl/image/conv_2d_general.cc index bf4baea7..9964c5f2 100644 --- a/mace/ops/opencl/image/conv_2d_general.cc +++ b/mace/ops/opencl/image/conv_2d_general.cc @@ -14,7 +14,7 @@ #include "mace/core/op_context.h" #include "mace/core/runtime/opencl/opencl_runtime.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" #include "mace/ops/common/activation_type.h" #include "mace/utils/math.h" diff --git a/mace/ops/opencl/image/crop.h b/mace/ops/opencl/image/crop.h index c2f1c53a..33a5d260 100644 --- a/mace/ops/opencl/image/crop.h +++ b/mace/ops/opencl/image/crop.h @@ -23,7 +23,7 @@ #include "mace/core/op_context.h" #include "mace/core/tensor.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" namespace mace { namespace ops { diff --git a/mace/ops/opencl/image/deconv_2d.h b/mace/ops/opencl/image/deconv_2d.h index aa3b9d24..4f1db7e6 100644 --- a/mace/ops/opencl/image/deconv_2d.h +++ b/mace/ops/opencl/image/deconv_2d.h @@ -23,7 +23,7 @@ #include "mace/core/op_context.h" #include "mace/core/tensor.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" namespace mace { namespace ops { diff --git a/mace/ops/opencl/image/depth_to_space.h b/mace/ops/opencl/image/depth_to_space.h index ac68cbdb..383a4c6f 100644 --- a/mace/ops/opencl/image/depth_to_space.h +++ b/mace/ops/opencl/image/depth_to_space.h @@ -23,7 +23,7 @@ #include "mace/core/op_context.h" #include "mace/core/tensor.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" namespace mace { namespace ops { diff --git a/mace/ops/opencl/image/depthwise_conv2d.h b/mace/ops/opencl/image/depthwise_conv2d.h index f4bc4f2a..c72170ac 100644 --- a/mace/ops/opencl/image/depthwise_conv2d.h +++ b/mace/ops/opencl/image/depthwise_conv2d.h @@ -21,7 +21,7 @@ #include "mace/core/op_context.h" #include "mace/core/tensor.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" namespace mace { namespace ops { diff --git a/mace/ops/opencl/image/depthwise_deconv2d.h b/mace/ops/opencl/image/depthwise_deconv2d.h index 20555116..fe039cb6 100644 --- a/mace/ops/opencl/image/depthwise_deconv2d.h +++ b/mace/ops/opencl/image/depthwise_deconv2d.h @@ -23,7 +23,7 @@ #include "mace/core/op_context.h" #include "mace/core/tensor.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" namespace mace { namespace ops { diff --git a/mace/ops/opencl/image/eltwise.h b/mace/ops/opencl/image/eltwise.h index 5678f9c7..a9298cc6 100644 --- a/mace/ops/opencl/image/eltwise.h +++ b/mace/ops/opencl/image/eltwise.h @@ -25,7 +25,7 @@ #include "mace/core/op_context.h" #include "mace/core/tensor.h" #include "mace/ops/common/eltwise_type.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" namespace mace { namespace ops { diff --git a/mace/ops/opencl/image/fully_connected.h b/mace/ops/opencl/image/fully_connected.h index 9f1bae64..010edcac 100644 --- a/mace/ops/opencl/image/fully_connected.h +++ b/mace/ops/opencl/image/fully_connected.h @@ -24,7 +24,7 @@ #include "mace/core/op_context.h" #include "mace/core/tensor.h" #include "mace/ops/common/activation_type.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" namespace mace { namespace ops { diff --git a/mace/ops/opencl/image/image_to_buffer.h b/mace/ops/opencl/image/image_to_buffer.h index 85893f6b..5d5c5248 100644 --- a/mace/ops/opencl/image/image_to_buffer.h +++ b/mace/ops/opencl/image/image_to_buffer.h @@ -21,7 +21,7 @@ #include "mace/core/op_context.h" #include "mace/ops/opencl/buffer_transform_kernel.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" namespace mace { namespace ops { diff --git a/mace/ops/opencl/image/lpnorm.h b/mace/ops/opencl/image/lpnorm.h index ae517c28..cac64112 100644 --- a/mace/ops/opencl/image/lpnorm.h +++ b/mace/ops/opencl/image/lpnorm.h @@ -17,7 +17,7 @@ #include "mace/core/op_context.h" #include "mace/core/runtime/opencl/opencl_runtime.h" #include "mace/core/tensor.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" #include "mace/ops/opencl/lpnorm.h" namespace mace { diff --git a/mace/ops/opencl/image/lstm_cell.h b/mace/ops/opencl/image/lstm_cell.h index 006374f9..998d8147 100644 --- a/mace/ops/opencl/image/lstm_cell.h +++ b/mace/ops/opencl/image/lstm_cell.h @@ -23,7 +23,7 @@ #include "mace/core/op_context.h" #include "mace/core/tensor.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" namespace mace { namespace ops { diff --git a/mace/ops/opencl/image/matmul.h b/mace/ops/opencl/image/matmul.h index afd4792c..8ee05239 100644 --- a/mace/ops/opencl/image/matmul.h +++ b/mace/ops/opencl/image/matmul.h @@ -24,7 +24,7 @@ #include "mace/core/op_context.h" #include "mace/core/tensor.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" namespace mace { namespace ops { diff --git a/mace/ops/opencl/image/mvnorm.h b/mace/ops/opencl/image/mvnorm.h index 9ff6d47f..f6e609d2 100644 --- a/mace/ops/opencl/image/mvnorm.h +++ b/mace/ops/opencl/image/mvnorm.h @@ -20,7 +20,7 @@ #include "mace/core/op_context.h" #include "mace/core/runtime/opencl/opencl_runtime.h" #include "mace/core/tensor.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" #include "mace/ops/opencl/mvnorm.h" namespace mace { diff --git a/mace/ops/opencl/image/pad.h b/mace/ops/opencl/image/pad.h index f4b8278b..3df88f34 100644 --- a/mace/ops/opencl/image/pad.h +++ b/mace/ops/opencl/image/pad.h @@ -24,7 +24,7 @@ #include "mace/core/op_context.h" #include "mace/core/tensor.h" #include "mace/ops/common/pad_type.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" namespace mace { namespace ops { diff --git a/mace/ops/opencl/image/pooling.h b/mace/ops/opencl/image/pooling.h index 8d709368..5c0e14a5 100644 --- a/mace/ops/opencl/image/pooling.h +++ b/mace/ops/opencl/image/pooling.h @@ -24,7 +24,7 @@ #include "mace/core/op_context.h" #include "mace/core/tensor.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" namespace mace { namespace ops { diff --git a/mace/ops/opencl/image/reduce.h b/mace/ops/opencl/image/reduce.h index 992ac1b1..0dfb48b4 100644 --- a/mace/ops/opencl/image/reduce.h +++ b/mace/ops/opencl/image/reduce.h @@ -23,7 +23,7 @@ #include "mace/core/op_context.h" #include "mace/core/tensor.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" #include "mace/ops/common/reduce_type.h" namespace mace { diff --git a/mace/ops/opencl/image/reshape.h b/mace/ops/opencl/image/reshape.h index 4004fb5e..60be5fe0 100644 --- a/mace/ops/opencl/image/reshape.h +++ b/mace/ops/opencl/image/reshape.h @@ -21,7 +21,7 @@ #include #include "mace/core/operator.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" #include "mace/ops/opencl/buffer_transform_kernel.h" namespace mace { diff --git a/mace/ops/opencl/image/resize_bicubic.h b/mace/ops/opencl/image/resize_bicubic.h index cb215f19..5abc5539 100644 --- a/mace/ops/opencl/image/resize_bicubic.h +++ b/mace/ops/opencl/image/resize_bicubic.h @@ -24,7 +24,7 @@ #include "mace/core/op_context.h" #include "mace/core/tensor.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" namespace mace { namespace ops { diff --git a/mace/ops/opencl/image/resize_bilinear.h b/mace/ops/opencl/image/resize_bilinear.h index 68b1478d..ca3602d3 100644 --- a/mace/ops/opencl/image/resize_bilinear.h +++ b/mace/ops/opencl/image/resize_bilinear.h @@ -24,7 +24,7 @@ #include "mace/core/op_context.h" #include "mace/core/tensor.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" namespace mace { namespace ops { diff --git a/mace/ops/opencl/image/resize_nearest_neighbor.h b/mace/ops/opencl/image/resize_nearest_neighbor.h index 9e2cec61..8bb10d4b 100644 --- a/mace/ops/opencl/image/resize_nearest_neighbor.h +++ b/mace/ops/opencl/image/resize_nearest_neighbor.h @@ -24,7 +24,7 @@ #include "mace/core/op_context.h" #include "mace/core/tensor.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" namespace mace { namespace ops { diff --git a/mace/ops/opencl/image/softmax.h b/mace/ops/opencl/image/softmax.h index 505dff57..525f1edc 100644 --- a/mace/ops/opencl/image/softmax.h +++ b/mace/ops/opencl/image/softmax.h @@ -24,7 +24,7 @@ #include "mace/core/op_context.h" #include "mace/core/tensor.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" namespace mace { namespace ops { diff --git a/mace/ops/opencl/image/space_to_batch.h b/mace/ops/opencl/image/space_to_batch.h index 6ad5d228..20777dc8 100644 --- a/mace/ops/opencl/image/space_to_batch.h +++ b/mace/ops/opencl/image/space_to_batch.h @@ -23,7 +23,7 @@ #include "mace/core/op_context.h" #include "mace/core/tensor.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" namespace mace { namespace ops { diff --git a/mace/ops/opencl/image/space_to_depth.h b/mace/ops/opencl/image/space_to_depth.h index 324977ea..661e09af 100644 --- a/mace/ops/opencl/image/space_to_depth.h +++ b/mace/ops/opencl/image/space_to_depth.h @@ -23,7 +23,7 @@ #include "mace/core/op_context.h" #include "mace/core/tensor.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" namespace mace { namespace ops { diff --git a/mace/ops/opencl/image/split.h b/mace/ops/opencl/image/split.h index 956ff657..20e19362 100644 --- a/mace/ops/opencl/image/split.h +++ b/mace/ops/opencl/image/split.h @@ -24,7 +24,7 @@ #include "mace/core/op_context.h" #include "mace/core/tensor.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" namespace mace { namespace ops { diff --git a/mace/ops/opencl/image/sqrdiff_mean.h b/mace/ops/opencl/image/sqrdiff_mean.h index bd2d1e7f..5acddb25 100644 --- a/mace/ops/opencl/image/sqrdiff_mean.h +++ b/mace/ops/opencl/image/sqrdiff_mean.h @@ -23,7 +23,7 @@ #include "mace/core/op_context.h" #include "mace/core/tensor.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" namespace mace { namespace ops { diff --git a/mace/ops/opencl/image/winograd_conv2d.cc b/mace/ops/opencl/image/winograd_conv2d.cc index 1ea2634a..fd7cdfe6 100644 --- a/mace/ops/opencl/image/winograd_conv2d.cc +++ b/mace/ops/opencl/image/winograd_conv2d.cc @@ -16,7 +16,7 @@ #include "mace/core/op_context.h" #include "mace/ops/common/activation_type.h" #include "mace/ops/common/conv_pool_2d_util.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" #include "mace/utils/memory.h" #include "mace/utils/math.h" diff --git a/mace/tools/mace_run.cc b/mace/tools/mace_run.cc index 55f29317..6d025026 100644 --- a/mace/tools/mace_run.cc +++ b/mace/tools/mace_run.cc @@ -175,9 +175,9 @@ bool RunModel(const std::string &model_name, if (status != MaceStatus::MACE_SUCCESS) { LOG(WARNING) << "Set openmp or cpu affinity failed."; } -#ifdef MACE_ENABLE_OPENCL +#if defined(MACE_ENABLE_OPENCL) || defined(MACE_ENABLE_HTA) std::shared_ptr gpu_context; - if (device_type == DeviceType::GPU) { + if (device_type == DeviceType::GPU || device_type == DeviceType::HTA) { const char *storage_path_ptr = getenv("MACE_INTERNAL_STORAGE_PATH"); const std::string storage_path = std::string(storage_path_ptr == nullptr ? diff --git a/test/ccunit/BUILD.bazel b/test/ccunit/BUILD.bazel index d3039c00..ce4d268c 100644 --- a/test/ccunit/BUILD.bazel +++ b/test/ccunit/BUILD.bazel @@ -9,6 +9,7 @@ load( "if_android", "if_android_armv7", "if_hexagon_enabled", + "if_hta_enabled", "if_neon_enabled", "if_opencl_enabled", "if_openmp_enabled", @@ -41,7 +42,9 @@ cc_test( [ "mace/ops/opencl/*.cc", ] - )), + )) + if_hta_enabled([ + "mace/core/runtime/hexagon/hta_transform_test.cc", + ]), copts = [ "-Werror", "-Wextra", @@ -59,6 +62,8 @@ cc_test( "-DMACE_ENABLE_QUANTIZE", ]) + if_hexagon_enabled([ "-DMACE_ENABLE_HEXAGON", + ]) + if_hta_enabled([ + "-DMACE_ENABLE_HTA", ]), linkopts = if_openmp_enabled([ "-fopenmp", diff --git a/test/ccunit/CMakeLists.txt b/test/ccunit/CMakeLists.txt index 21107815..ae83e1a3 100644 --- a/test/ccunit/CMakeLists.txt +++ b/test/ccunit/CMakeLists.txt @@ -7,6 +7,10 @@ file(GLOB MACE_CC_TEST_SRCS mace/libmace/*.cc ) +if(MACE_ENABLE_HTA) + set(MACE_CC_TEST_SRCS ${MACE_CC_TEST_SRCS} mace/core/runtime/hexagon/hta_transform_test.cc) +endif(MACE_ENABLE_HTA) + add_executable(mace_cc_test ${MACE_CC_TEST_SRCS}) target_link_libraries(mace_cc_test PUBLIC mace_cc_test_utils diff --git a/test/ccunit/mace/core/runtime/hexagon/hta_transform_test.cc b/test/ccunit/mace/core/runtime/hexagon/hta_transform_test.cc new file mode 100644 index 00000000..3d123c8b --- /dev/null +++ b/test/ccunit/mace/core/runtime/hexagon/hta_transform_test.cc @@ -0,0 +1,152 @@ +// Copyright 2020 The MACE Authors. 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 "mace/core/quantize.h" +#include "mace/core/runtime/hexagon/hexagon_hta_transformer.h" +#include "mace/ops/ops_test_util.h" + +namespace mace { +namespace ops { +namespace test { + +class HTATransformTest : public OpsTestBase {}; + +namespace { +template +void TestHTAQuantizeDequantize(const std::vector &input) { + float min_val, max_val; + FindMinMax(input.data(), input.size(), &min_val, &max_val); + float scale; + int32_t zero; + AdjustRange(min_val, max_val, false, &scale, &zero); + + OpsTestNet net; + Device *device = OpTestContext::Get()->GetDevice(D); + + net.AddInputFromArray("Input", + {static_cast(input.size())}, + input); + Tensor *input_tensor = net.GetOutput("Input"); + input_tensor->SetScale(scale); + input_tensor->SetZeroPoint(zero); + Tensor *quantized_output = net.ws()->CreateTensor( + "QuantizedOutput", device->allocator(), DT_UINT8); + Tensor *dequantized_output = net.ws()->CreateTensor( + "DequantizedOutput", device->allocator(), DT_FLOAT); + + mace::HexagonHTATranformer transformer; + transformer.Init(device); + transformer.Quantize(input_tensor, quantized_output); + transformer.Dequantize(quantized_output, dequantized_output); + + ExpectTensorNear(*input_tensor, + *dequantized_output, + 0.1); +} + +} // namespace + +TEST_F(HTATransformTest, TestHTAQuantize) { + TestHTAQuantizeDequantize({-2, -1, 0, 1, 2, 3, 4}); + TestHTAQuantizeDequantize({-2, -1, 0, 1, 2, 3, 4}); +} + +namespace { +void TestHTAInputTransform(const std::vector &input_shape, + const hexagon_hta_hw_layout format) { + OpsTestNet net; + Device *device = OpTestContext::Get()->GetDevice(DeviceType::GPU); + net.AddRandomInput("Input", input_shape); + Tensor *input_tensor = net.GetOutput("Input"); + input_tensor->SetScale(0.1); + input_tensor->SetZeroPoint(1); + Tensor *cpu_transformed_tensor = net.ws()->CreateTensor( + "CpuTransformedOutput", device->allocator(), DT_UINT8); + Tensor *gpu_transformed_tensor = net.ws()->CreateTensor( + "GpuTransformedOutput", device->allocator(), DT_UINT8); + + mace::HexagonHTATranformer cpu_transformer; + mace::HexagonHTATranformer gpu_transformer; + cpu_transformer.Init(device); + gpu_transformer.Init(device); + cpu_transformer.SetInputTransformer(format); + gpu_transformer.SetInputTransformer(format); + cpu_transformer.TransformInput(input_tensor, cpu_transformed_tensor, 0); + gpu_transformer.TransformInput(input_tensor, gpu_transformed_tensor, 0); + + net.Sync(); + ExpectTensorNear(*cpu_transformed_tensor, *gpu_transformed_tensor); +} + +} // namespace + +TEST_F(HTATransformTest, TestHTAInputTransform) { + TestHTAInputTransform({1, 15, 33, 2}, HEXAGON_HTA_HW_FORMAT_PLANAR); + TestHTAInputTransform({1, 19, 31, 3}, HEXAGON_HTA_HW_FORMAT_PLANAR); + TestHTAInputTransform({1, 224, 224, 3}, HEXAGON_HTA_HW_FORMAT_PLANAR); + TestHTAInputTransform({1, 19, 31, 3}, HEXAGON_HTA_HW_FORMAT_D32); + TestHTAInputTransform({1, 15, 33, 27}, HEXAGON_HTA_HW_FORMAT_D32); + TestHTAInputTransform({1, 15, 33, 35}, HEXAGON_HTA_HW_FORMAT_D32); + TestHTAInputTransform({1, 224, 224, 3}, HEXAGON_HTA_HW_FORMAT_D32); +} + + +namespace { +void TestHTAOutputTransform(const std::vector &output_shape, + const hexagon_hta_hw_layout format) { + index_t batch = output_shape[0]; + index_t height = output_shape[1]; + index_t width = output_shape[2]; + index_t channels = output_shape[3]; + MACE_CHECK(format == HEXAGON_HTA_HW_FORMAT_D32); + std::vector input_shape { + batch, height, RoundUpDiv(channels, 32), width, 32}; + + OpsTestNet net; + Device *device = OpTestContext::Get()->GetDevice(DeviceType::GPU); + net.AddRandomInput("Input", input_shape); + Tensor *input_tensor = net.GetOutput("Input"); + Tensor *cpu_transformed_tensor = net.ws()->CreateTensor( + "CpuTransformedOutput", device->allocator(), DT_UINT8); + Tensor *gpu_transformed_tensor = net.ws()->CreateTensor( + "GpuTransformedOutput", device->allocator(), DT_UINT8); + cpu_transformed_tensor->Resize(output_shape); + gpu_transformed_tensor->Resize(output_shape); + + mace::HexagonHTATranformer cpu_transformer; + mace::HexagonHTATranformer gpu_transformer; + cpu_transformer.Init(device); + gpu_transformer.Init(device); + cpu_transformer.SetOutputTransformer(format); + gpu_transformer.SetOutputTransformer(format); + cpu_transformer.TransformOutput(input_tensor, cpu_transformed_tensor, 0); + gpu_transformer.TransformOutput(input_tensor, gpu_transformed_tensor, 0); + + net.Sync(); + ExpectTensorNear(*cpu_transformed_tensor, *gpu_transformed_tensor); +} + +} // namespace + +TEST_F(HTATransformTest, TestHTAOutputTransform) { + TestHTAOutputTransform({1, 15, 33, 2}, HEXAGON_HTA_HW_FORMAT_D32); + TestHTAOutputTransform({1, 19, 31, 27}, HEXAGON_HTA_HW_FORMAT_D32); + TestHTAOutputTransform({1, 19, 31, 35}, HEXAGON_HTA_HW_FORMAT_D32); + TestHTAOutputTransform({1, 224, 224, 2}, HEXAGON_HTA_HW_FORMAT_D32); + TestHTAOutputTransform({1, 384, 384, 3}, HEXAGON_HTA_HW_FORMAT_D32); +} + +} // namespace test +} // namespace ops +} // namespace mace diff --git a/test/ccunit/mace/ops/opencl/out_of_range_check_test.cc b/test/ccunit/mace/ops/opencl/out_of_range_check_test.cc index 5ee423d3..3dfe468a 100644 --- a/test/ccunit/mace/ops/opencl/out_of_range_check_test.cc +++ b/test/ccunit/mace/ops/opencl/out_of_range_check_test.cc @@ -21,7 +21,7 @@ #include "mace/core/runtime/opencl/opencl_runtime.h" #include "mace/core/tensor.h" #include "mace/core/workspace.h" -#include "mace/ops/opencl/helper.h" +#include "mace/core/runtime/opencl/opencl_helper.h" #include "mace/utils/memory.h" namespace mace { diff --git a/test/ccunit/mace/ops/quantize_test.cc b/test/ccunit/mace/ops/quantize_test.cc index ecfe5f5d..b6a86841 100644 --- a/test/ccunit/mace/ops/quantize_test.cc +++ b/test/ccunit/mace/ops/quantize_test.cc @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include "mace/core/quantize.h" #include "mace/ops/ops_test_util.h" namespace mace { diff --git a/test/ccutils/mace/ops/testing/test_utils.h b/test/ccutils/mace/ops/testing/test_utils.h index 6a0a045b..ef830781 100644 --- a/test/ccutils/mace/ops/testing/test_utils.h +++ b/test/ccutils/mace/ops/testing/test_utils.h @@ -250,8 +250,24 @@ struct Expector { Tensor::MappingGuard y_mapper(&y); auto a = x.data(); auto b = y.data(); - for (int i = 0; i < x.size(); ++i) { - ExpectEqual(a[i], b[i]); + if (x.dim_size() == 4) { + for (int n = 0; n < x.dim(0); ++n) { + for (int h = 0; h < x.dim(1); ++h) { + for (int w = 0; w < x.dim(2); ++w) { + for (int c = 0; c < x.dim(3); ++c) { + EXPECT_EQ(*a, *b) << "with index = [" << n << ", " << h << ", " + << w << ", " << c << "]"; + a++; + b++; + } + } + } + } + } else { + for (int i = 0; i < x.size(); ++i) { + EXPECT_EQ(a[i], b[i]) + << "a = " << a << " b = " << b << " index = " << i; + } } } diff --git a/third_party/nnlib/arm64-v8a/libhexagon_controller.so b/third_party/nnlib/arm64-v8a/libhexagon_controller.so index 5b17a007b440e9e068925ca3f347dc8d94ccb55a..5e2e85a1341ba040885086e45d8b03e85ae5c6d6 100755 GIT binary patch literal 38944 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 Kws{?v|NjLEY=PPU literal 57016 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
    Qm450f)kSuAQrn>rx~+yC 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 diff --git a/third_party/nnlib/armeabi-v7a/libhexagon_controller.so b/third_party/nnlib/armeabi-v7a/libhexagon_controller.so index fc3e89b99f531afbfddba918c5ee4c8ffddb05ac..1ac9ae9d00ed1e3f8ee0acb04bb006a1d625bbcc 100755 GIT binary patch literal 31208 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 diff --git a/third_party/nnlib/hexnn_dsp_controller.h b/third_party/nnlib/hexnn_dsp_controller.h index 671a6c68..8818217d 100644 --- a/third_party/nnlib/hexnn_dsp_controller.h +++ b/third_party/nnlib/hexnn_dsp_controller.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2019, The Linux Foundation. All rights reserved. + * Copyright (c) 2019, The Linux Foundation. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted (subject to the limitations in the @@ -42,10 +42,6 @@ extern "C" { int hexnn_controller_request_unsigned_pd(); -int hexnn_controller_init(); - -int hexnn_controller_deinit(); - #ifdef __cplusplus } #endif diff --git a/third_party/rpcmem/arm64-v8a/rpcmem.a b/third_party/rpcmem/arm64-v8a/rpcmem.a index 982714b75645f2a408f7fde000376cf32d18055b..9bc7dea567e21127a9eede04904b678c881d2b7f 100644 GIT binary patch literal 22602 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ª4cs=3F ze}x9kM#0bc4k2Gbo5)Tf&-gCFr9M3xuE%*o@DCCHe!()2e%Zf&MRq78oa2Y_ z3L*ax>FE>ljCTq77bv)TggoPi2$%8jl;HUd@r(!_#*f(WoX~JR|4#`1KcNR^rvyLa zr!^iuuBHUfCE|Hq@Gw4Y!;=&|Mf7m%9l^u+j17;Q{E7C|`&&pj`~9cHUqm?jopD3s z(fiBen0bomVUkDiFkWxNvypH)-?b1f=est-x!o>mcL(9DKjV)G`6lwqE+NnOppcJ| zeG)>RariBPl=-)WHWN<^dB#TxmvMel!}UD;n&5AyMg5fEXZ(!DqxzEQ{@ApU@mXM8i^vR|DVuFqdxg8vobj|hIo`!pWikB0=$J+!GE7CelP*zg~LDhGd?Bcj}!mvLZ0zA373Akpy4znnsNV* z;Qs{$-6g@#cs?al$v>#!q@u}xO2hT%3_l@U?(a?$F86n@5zg^{oc3>1gme5eewJ{l z=d|GA4~s4c9>(9X;h7~I_1B+QWYM}6_1E!2!ciX`Uq?9i>n!!FlyL4B|5^>#?eEcW zno>+V@6d1xJrf_*aDCjpsp0y#D6gqlB~mjK8Dt==#hOF7>%exYQ?$);ri=U7viyS)czV zeO3_8`Y>*2JbE5egNdGaG!zN;?@L7Y2P4s;L_BstRmP)zp-Mm!Dj1}qxd?u>fbj2U*u8vZ60VG zuz{GuA2m?sTl?H3esy&5mq1Am@%x~Jn%{p|lugxg-vIoc>7&97CR7RE4~{MAlrog>6yTTXx zcib7{H@L#85)7IM?S|ulemt&=RC@8BYH)7M{Js+iRKw-E{goaww|$s@{uAPpK2l4e zIn{1m^5@We6U0->TDU@trsIWs0Xi$C_~je)aSdOZbG*dx<|VHsG6g^6fdz*Ne)Py= zfY;13&^WeF;&5h_9%}c~sGwF@e?Hd1Jv^6|{6?i!3Ex-J3t#dP$2Xtra^I@buNKdx Z?0Ep_Bh^JAI6Ff1yU00M5Bo0r|Nr;fWt0E_ literal 24682 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 L4C`UOk^cKX2cUh6 diff --git a/third_party/rpcmem/armeabi-v7a/rpcmem.a b/third_party/rpcmem/armeabi-v7a/rpcmem.a index faa1baa5f8e7a689e66ac126df0446c27d08e061..85f991e897b53977cfd02ce9fa12edb3953bfece 100644 GIT binary patch literal 17866 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 BpI86@ literal 18718 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 + /** * RPCMEM_DEFAULT_HEAP * Dynamicaly select the heap to use. This should be ok for most usecases. @@ -77,16 +79,25 @@ extern "C" { #endif +typedef struct rpcmem rpcmem; +struct rpcmem { + void *lst; // QList* + void *mt; // pthread_mutex_t* + int ionfd; + int flag; + int ionversion; +}; + /** * call once to initialize the library - * NOTE: rpcmem_init is not thread safe + * NOTE: rpcmem_init is now thread safe */ -void rpcmem_init(void); +void rpcmem_init(rpcmem *rm); /** * call once for cleanup - * NOTE: rpcmem_deinit is not thread safe + * NOTE: rpcmem_deinit is now thread safe */ -void rpcmem_deinit(void); +void rpcmem_deinit(rpcmem *rm); /** * Allocate via ION a buffer of size @@ -96,10 +107,10 @@ void rpcmem_deinit(void); * @retval, 0 on failure, pointer to buffer on success * * For example: - * buf = rpcmem_alloc(RPCMEM_DEFAULT_HEAP, RPCMEM_DEFAULT_FLAGS, size); + * buf = rpcmem_alloc(rm, RPCMEM_DEFAULT_HEAP, RPCMEM_DEFAULT_FLAGS, size); */ -void* rpcmem_alloc(int heapid, unsigned int flags, int size); +void* rpcmem_alloc(rpcmem *rm, int heapid, uint32_t flags, int size); /** * allocate with default settings @@ -107,24 +118,24 @@ void* rpcmem_alloc(int heapid, unsigned int flags, int size); #if !defined(WINNT) && !defined (_WIN32_WINNT) __attribute__((unused)) #endif -static __inline void* rpcmem_alloc_def(int size) { - return rpcmem_alloc(RPCMEM_DEFAULT_HEAP, RPCMEM_DEFAULT_FLAGS, size); +static __inline void* rpcmem_alloc_def(rpcmem *rm, int size) { + return rpcmem_alloc(rm, RPCMEM_DEFAULT_HEAP, RPCMEM_DEFAULT_FLAGS, size); } /** * free buffer, ignores invalid buffers */ -void rpcmem_free(void* po); +void rpcmem_free(rpcmem *rm, void* po); /** * returns associated fd */ -int rpcmem_to_fd(void* po); +int rpcmem_to_fd(rpcmem *rm, void* po); /** * cache coherency management */ -int rpcmem_sync_cache(void* po, unsigned int flags); +int rpcmem_sync_cache(rpcmem *rm, void* po, uint32_t flags); #ifdef __cplusplus } diff --git a/tools/bazel-build-standalone-lib.sh b/tools/bazel-build-standalone-lib.sh index 8a078113..f01dd065 100755 --- a/tools/bazel-build-standalone-lib.sh +++ b/tools/bazel-build-standalone-lib.sh @@ -21,6 +21,11 @@ mkdir -p $LIB_DIR/arm64-v8a/cpu_gpu_dsp mkdir -p $LIB_DIR/arm64-v8a/cpu_gpu mkdir -p $LIB_DIR/arm64-v8a/cpu_gpu_apu +if [[ "$BUILD_HTA" == "1" ]]; then + mkdir -p $LIB_DIR/armeabi-v7a/cpu_gpu_hta + mkdir -p $LIB_DIR/arm64-v8a/cpu_gpu_hta +fi + rm -rf $LIB_DIR/linux-x86-64 mkdir -p $LIB_DIR/linux-x86-64 @@ -33,6 +38,18 @@ mkdir -p $LIB_DIR/aarch64_linux_gnu/cpu_gpu # build shared libraries +if [[ "$BUILD_HTA" == "1" ]]; then + echo "build shared lib for armeabi-v7a + cpu_gpu_hta" + bazel build --config android --config optimization mace/libmace:libmace_dynamic --define neon=true --define opencl=true --define hta=true --define quantize=true --cpu=armeabi-v7a --define rpcmem=true + cp bazel-bin/mace/libmace/libmace.so $LIB_DIR/armeabi-v7a/cpu_gpu_hta/ + cp third_party/hta/armeabi-v7a/*so $LIB_DIR/armeabi-v7a/cpu_gpu_hta/ + + echo "build shared lib for arm64-v8a + cpu_gpu_hta" + bazel build --config android --config optimization mace/libmace:libmace_dynamic --define neon=true --define opencl=true --define hta=true --define quantize=true --cpu=arm64-v8a --define rpcmem=true + cp bazel-bin/mace/libmace/libmace.so $LIB_DIR/arm64-v8a/cpu_gpu_hta/ + cp third_party/hta/arm64-v8a/*so $LIB_DIR/arm64-v8a/cpu_gpu_hta/ +fi + echo "build shared lib for armeabi-v7a + cpu_gpu_dsp" bazel build --config android --config optimization mace/libmace:libmace_dynamic --define neon=true --define opencl=true --define hexagon=true --define quantize=true --cpu=armeabi-v7a --define rpcmem=true cp bazel-bin/mace/libmace/libmace.so $LIB_DIR/armeabi-v7a/cpu_gpu_dsp/ @@ -71,6 +88,18 @@ if [[ "$OSTYPE" != "darwin"* ]];then fi # build static libraries +if [[ "$BUILD_HTA" == "1" ]]; then + echo "build static lib for armeabi-v7a + cpu_gpu_hta" + bazel build --config android --config optimization mace/libmace:libmace_static --config symbol_hidden --define neon=true --define opencl=true --define hta=true --define quantize=true --cpu=armeabi-v7a --define rpcmem=true + cp bazel-genfiles/mace/libmace/libmace.a $LIB_DIR/armeabi-v7a/cpu_gpu_hta/ + cp third_party/hta/armeabi-v7a/*so $LIB_DIR/armeabi-v7a/cpu_gpu_hta/ + + echo "build static lib for arm64-v8a + cpu_gpu_hta" + bazel build --config android --config optimization mace/libmace:libmace_static --config symbol_hidden --define neon=true --define opencl=true --define hta=true --define quantize=true --cpu=arm64-v8a --define rpcmem=true + cp bazel-genfiles/mace/libmace/libmace.a $LIB_DIR/arm64-v8a/cpu_gpu_hta/ + cp third_party/hta/arm64-v8a/*so $LIB_DIR/arm64-v8a/cpu_gpu_hta/ +fi + echo "build static lib for armeabi-v7a + cpu_gpu_dsp" bazel build --config android --config optimization mace/libmace:libmace_static --config symbol_hidden --define neon=true --define opencl=true --define hexagon=true --define quantize=true --cpu=armeabi-v7a --define rpcmem=true cp bazel-genfiles/mace/libmace/libmace.a $LIB_DIR/armeabi-v7a/cpu_gpu_dsp/ diff --git a/tools/bazel_adb_run.py b/tools/bazel_adb_run.py index 1679f604..a0bed249 100644 --- a/tools/bazel_adb_run.py +++ b/tools/bazel_adb_run.py @@ -100,6 +100,11 @@ def parse_args(): type=str2bool, default=True, help="Whether to use rpcmem") + parser.add_argument( + "--enable_hta", + type=str2bool, + default=False, + help="Whether to use hta") parser.add_argument( '--address_sanitizer', action="store_true", @@ -170,6 +175,7 @@ def main(unused_args): enable_neon=FLAGS.enable_neon, enable_quantize=FLAGS.enable_quantize, enable_rpcmem=FLAGS.enable_rpcmem, + enable_hta=FLAGS.enable_hta, address_sanitizer=FLAGS.address_sanitizer, debug_mode=FLAGS.debug_mode) if FLAGS.run_target: diff --git a/tools/converter.py b/tools/converter.py index 32490794..dd9a6cbc 100644 --- a/tools/converter.py +++ b/tools/converter.py @@ -218,7 +218,8 @@ def get_opencl_mode(configs): YAMLKeyword.runtime, "") runtime_list.append(model_runtime.lower()) - if RuntimeType.gpu in runtime_list or RuntimeType.cpu_gpu in runtime_list: + if RuntimeType.gpu in runtime_list or RuntimeType.cpu_gpu in runtime_list \ + or RuntimeType.hta in runtime_list: return True return False diff --git a/tools/python/transform/hexagon_converter.py b/tools/python/transform/hexagon_converter.py index 3a99c5cf..6d6d8eaf 100644 --- a/tools/python/transform/hexagon_converter.py +++ b/tools/python/transform/hexagon_converter.py @@ -295,6 +295,20 @@ class HexagonConverter(base_converter.ConverterInterface): else: index += 1 + if self._option.device == DeviceType.HTA.value: + # replace QuantizeINPUT_f_to_8 with INPUT + quantize_input_op.type = HexagonOp.INPUT.name + del quantize_input_op.output_shape[1:] + del quantize_input_op.output_type[1:] + del quantize_input_op.out_max_byte_size[1:] + + # replace first op's input min max with constant + self.add_constant_min_max_for_first_op(self._model.op[1]) + + # replace DequantizeOUTPUT_8tof with OUTPUT + dequantize_output_op.type = HexagonOp.OUTPUT.name + del dequantize_output_op.input[1:] + return quantize_input_op.output def add_node_id(self, model_inputs): diff --git a/tools/sh_commands.py b/tools/sh_commands.py index 831d015d..219b8502 100644 --- a/tools/sh_commands.py +++ b/tools/sh_commands.py @@ -605,18 +605,20 @@ def create_internal_storage_dir(serialno, phone_data_dir): def push_depended_so_libs(libmace_dynamic_library_path, abi, phone_data_dir, serialno): - dep_so_libs = sh.bash(os.environ["ANDROID_NDK_HOME"] + "/ndk-depends", - libmace_dynamic_library_path) - src_file = "" - for dep in split_stdout(dep_so_libs): - if dep == "libgnustl_shared.so": - src_file = "%s/sources/cxx-stl/gnu-libstdc++/4.9/libs/" \ - "%s/libgnustl_shared.so" \ - % (os.environ["ANDROID_NDK_HOME"], abi) - elif dep == "libc++_shared.so": - src_file = "%s/sources/cxx-stl/llvm-libc++/libs/" \ - "%s/libc++_shared.so"\ - % (os.environ["ANDROID_NDK_HOME"], abi) + src_file = "%s/sources/cxx-stl/llvm-libc++/libs/" \ + "%s/libc++_shared.so" \ + % (os.environ["ANDROID_NDK_HOME"], abi) + try: + dep_so_libs = sh.bash(os.environ["ANDROID_NDK_HOME"] + "/ndk-depends", + libmace_dynamic_library_path) + except sh.ErrorReturnCode_127: + print("Find no ndk-depends, use default libc++_shared.so") + else: + for dep in split_stdout(dep_so_libs): + if dep == "libgnustl_shared.so": + src_file = "%s/sources/cxx-stl/gnu-libstdc++/4.9/libs/" \ + "%s/libgnustl_shared.so" \ + % (os.environ["ANDROID_NDK_HOME"], abi) print("push %s to %s" % (src_file, phone_data_dir)) adb_push(src_file, phone_data_dir, serialno) -- GitLab