Skip to content

Commit

Permalink
Merge pull request #230 from shrit/example_embedded
Browse files Browse the repository at this point in the history
Example embedded
  • Loading branch information
shrit authored Jul 5, 2024
2 parents 78ddf19 + dd45e26 commit 4d413b5
Show file tree
Hide file tree
Showing 12 changed files with 690 additions and 0 deletions.
49 changes: 49 additions & 0 deletions embedded/crosscompile_random_forest/CMake/Autodownload.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
## This function auto-downloads mlpack dependencies.
## You need to pass the LINK to download from, the name of
## the dependency, and the name of the compressed package such as
## armadillo.tar.gz
## At each download, this module sets a GENERIC_INCLUDE_DIR path,
## which means that you need to set the main path for the include
## directories for each package.
## Note that, the package should be compressed only as .tar.gz

macro(get_deps LINK DEPS_NAME PACKAGE)
if (NOT EXISTS "${CMAKE_BINARY_DIR}/deps/${PACKAGE}")
file(DOWNLOAD ${LINK}
"${CMAKE_BINARY_DIR}/deps/${PACKAGE}"
STATUS DOWNLOAD_STATUS_LIST LOG DOWNLOAD_LOG
SHOW_PROGRESS)
list(GET DOWNLOAD_STATUS_LIST 0 DOWNLOAD_STATUS)
if (DOWNLOAD_STATUS EQUAL 0)
execute_process(COMMAND ${CMAKE_COMMAND} -E
tar xf "${CMAKE_BINARY_DIR}/deps/${PACKAGE}"
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/deps/")
else ()
list(GET DOWNLOAD_STATUS_LIST 1 DOWNLOAD_ERROR)
message(FATAL_ERROR
"Could not download ${DEPS_NAME}! Error code ${DOWNLOAD_STATUS}: ${DOWNLOAD_ERROR}! Error log: ${DOWNLOAD_LOG}")
endif()
endif()
# Get the name of the directory.
file (GLOB DIRECTORIES RELATIVE "${CMAKE_BINARY_DIR}/deps/"
"${CMAKE_BINARY_DIR}/deps/${DEPS_NAME}*.*")
if(${DEPS_NAME} MATCHES "stb")
file (GLOB DIRECTORIES RELATIVE "${CMAKE_BINARY_DIR}/deps/"
"${CMAKE_BINARY_DIR}/deps/${DEPS_NAME}")
endif()
# list(FILTER) is not available on 3.5 or older, but try to keep
# configuring without filtering the list anyway
# (it works only if the file is present as .tar.gz).
if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.6.0")
list(FILTER DIRECTORIES EXCLUDE REGEX ".*\.tar\.gz")
endif ()
list(LENGTH DIRECTORIES DIRECTORIES_LEN)
if (DIRECTORIES_LEN GREATER 0)
list(GET DIRECTORIES 0 DEPENDENCY_DIR)
set(GENERIC_INCLUDE_DIR "${CMAKE_BINARY_DIR}/deps/${DEPENDENCY_DIR}/include")
install(DIRECTORY "${GENERIC_INCLUDE_DIR}/" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
else ()
message(FATAL_ERROR
"Problem unpacking ${DEPS_NAME}! Expected only one directory ${DEPS_NAME};. Try to remove the directory ${CMAKE_BINARY_DIR}/deps and reconfigure.")
endif ()
endmacro()
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# This file adds the necessary configurations to cross compile
# mlpack for embedded systems. You need to set the following variables
# from the command line: CMAKE_SYSROOT and TOOLCHAIN_PREFIX.
# This file will compile OpenBLAS if it is downloaded and it is not
# available on your system in order to find the BLAS library. If OpenBLAS will
# be compiled, the OPENBLAS_TARGET variable must be set. This can be done
# by, e.g., setting BOARD_NAME (which will set OPENBLAS_TARGET in
# `board/flags-config.cmake`).

if (CMAKE_CROSSCOMPILING)
include(board/flags-config.cmake)
if (NOT CMAKE_SYSROOT AND (NOT TOOLCHAIN_PREFIX))
message(FATAL_ERROR "Neither CMAKE_SYSROOT nor TOOLCHAIN_PREFIX are set; please set both of them and try again.")
elseif(NOT CMAKE_SYSROOT)
message(FATAL_ERROR "Cannot configure: CMAKE_SYSROOT must be set when performing cross-compiling!")
elseif(NOT TOOLCHAIN_PREFIX)
message(FATAL_ERROR "Cannot configure: TOOLCHAIN_PREFIX must be set when performing cross-compiling!")
endif()
endif()

macro(search_openblas version)
set(BLA_STATIC ON)
find_package(BLAS)
if (NOT BLAS_FOUND OR (NOT BLAS_LIBRARIES))
if(NOT OPENBLAS_TARGET)
message(FATAL_ERROR "Cannot compile OpenBLAS: OPENBLAS_TARGET is not set. Either set that variable, or set BOARD_NAME correctly!")
endif()
get_deps(https://github.com/xianyi/OpenBLAS/releases/download/v${version}/OpenBLAS-${version}.tar.gz OpenBLAS OpenBLAS-${version}.tar.gz)
if (NOT MSVC)
if (NOT EXISTS "${CMAKE_BINARY_DIR}/deps/OpenBLAS-${version}/libopenblas.a")
set(ENV{COMMON_OPT} "${CMAKE_OPENBLAS_FLAGS}") # Pass our flags to OpenBLAS
execute_process(COMMAND make TARGET=${OPENBLAS_TARGET} BINARY=${OPENBLAS_BINARY} HOSTCC=gcc CC=${CMAKE_C_COMPILER} FC=${CMAKE_FORTRAN_COMPILER} NO_SHARED=1
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/deps/OpenBLAS-${version})
endif()
file(GLOB OPENBLAS_LIBRARIES "${CMAKE_BINARY_DIR}/deps/OpenBLAS-${version}/libopenblas.a")
set(BLAS_openblas_LIBRARY ${OPENBLAS_LIBRARIES})
set(LAPACK_openblas_LIBRARY ${OPENBLAS_LIBRARIES})
set(BLA_VENDOR OpenBLAS)
set(BLAS_FOUND ON)
endif()
endif()
find_library(GFORTRAN NAMES libgfortran.a)
find_library(PTHREAD NAMES libpthread.a)
set(CROSS_COMPILE_SUPPORT_LIBRARIES ${GFORTRAN} ${PTHREAD})
endmacro()
79 changes: 79 additions & 0 deletions embedded/crosscompile_random_forest/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
cmake_minimum_required(VERSION 3.6)
project(RandomForest)

include(CMake/Autodownload.cmake)
include(CMake/ConfigureCrossCompile.cmake)

option(USE_OPENMP "If available, use OpenMP for parallelization." ON)

# Set required standard to C++17.
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# If we're using gcc, then we need to link against pthreads to use std::thread,
# which we do in the tests.
if (CMAKE_COMPILER_IS_GNUCC)
find_package(Threads)
set(MLPACK_LIBRARIES ${MLPACK_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
endif()

search_openblas(0.3.26)

get_deps(https://files.mlpack.org/armadillo-12.6.5.tar.gz armadillo armadillo-12.6.5.tar.gz)
set(ARMADILLO_INCLUDE_DIR ${GENERIC_INCLUDE_DIR})
find_package(Armadillo REQUIRED)
# Include directories for the previous dependencies.
set(MLPACK_INCLUDE_DIRS ${MLPACK_INCLUDE_DIRS} ${ARMADILLO_INCLUDE_DIRS})
set(MLPACK_LIBRARIES ${MLPACK_LIBRARIES} ${ARMADILLO_LIBRARIES})

# Find stb_image.h and stb_image_write.h.
get_deps(https://mlpack.org/files/stb.tar.gz stb stb.tar.gz)
set(STB_IMAGE_INCLUDE_DIR ${GENERIC_INCLUDE_DIR})
set(MLPACK_INCLUDE_DIRS ${MLPACK_INCLUDE_DIRS} "${STB_IMAGE_INCLUDE_DIR}")

# Find ensmallen.
get_deps(https://www.ensmallen.org/files/ensmallen-latest.tar.gz ensmallen ensmallen-latest.tar.gz)
set(ENSMALLEN_INCLUDE_DIR ${GENERIC_INCLUDE_DIR})
set(MLPACK_INCLUDE_DIRS ${MLPACK_INCLUDE_DIRS} "${ENSMALLEN_INCLUDE_DIR}")

# Find cereal.
get_deps(https://github.com/USCiLab/cereal/archive/refs/tags/v1.3.0.tar.gz cereal cereal-1.3.0.tar.gz)
set(CEREAL_INCLUDE_DIR ${GENERIC_INCLUDE_DIR})
set(MLPACK_INCLUDE_DIRS ${MLPACK_INCLUDE_DIRS} ${CEREAL_INCLUDE_DIR})

# Detect OpenMP support in a compiler. If the compiler supports OpenMP, flags
# to compile with OpenMP are returned and added. Note that MSVC does not
# support a new-enough version of OpenMP to be useful.
if (USE_OPENMP)
find_package(OpenMP)
endif ()

if (OpenMP_FOUND AND OpenMP_CXX_VERSION VERSION_GREATER_EQUAL 3.0.0)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
set(MLPACK_LIBRARIES ${MLPACK_LIBRARIES} ${OpenMP_CXX_LIBRARIES})
else ()
# Disable warnings for all the unknown OpenMP pragmas.
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unknown-pragmas")
set(OpenMP_CXX_FLAGS "")
endif ()

include_directories(BEFORE ${MLPACK_INCLUDE_DIRS})
include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}/src/)

# Finally, add any cross-compilation support libraries (they may need to come
# last). If we are not cross-compiling, no changes will happen here.
set(MLPACK_LIBRARIES ${MLPACK_LIBRARIES} ${CROSS_COMPILE_SUPPORT_LIBRARIES})

add_executable(RandomForest main.cpp ${SOURCES_FILES})
target_sources(RandomForest PRIVATE ${SOURCE_FILES})

target_include_directories(RandomForest PRIVATE
${MLPACK_INCLUDE_DIRS}
/meta/mlpack/src/
)

target_link_libraries(RandomForest PRIVATE -static
${MLPACK_LIBRARIES}
)

Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
## This file handles cross-compilation configurations for aarch64,
## known as arm64. The objective of this file is to find and assign
## cross-compiler and the entire toolchain.
##
## This configuration works best with the buildroot toolchain. When using this
## file, be sure to set the TOOLCHAIN_PREFIX and CMAKE_SYSROOT variables,
## preferably via the CMake configuration command (e.g. `-DCMAKE_SYSROOT=<...>`).
##
## Currently, we recommend using buildroot toolchain for
## cross-compilation. Here is the link to download the toolchains:
## https://toolchains.bootlin.com/

set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSROOT)
set(TOOLCHAIN_PREFIX "" CACHE STRING "Path for toolchain for cross compiler and other compilation tools.")

# Ensure that CMake tries to build static libraries when testing the compiler.
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)

set(CMAKE_AR "${TOOLCHAIN_PREFIX}gcc-ar" CACHE FILEPATH "" FORCE)
set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}gcc)
set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}g++)
set(CMAKE_LINKER ${TOOLCHAIN_PREFIX}ld)
set(CMAKE_C_ARCHIVE_CREATE "<CMAKE_AR> qcs <TARGET> <LINK_FLAGS> <OBJECTS>")
set(CMAKE_C_ARCHIVE_FINISH true)
set(CMAKE_FORTRAN_COMPILER ${TOOLCHAIN_PREFIX}gfortran)
set(CMAKE_ASM_COMPILER ${CMAKE_C_COMPILER})
set(CMAKE_OBJCOPY ${TOOLCHAIN_PREFIX}objcopy CACHE INTERNAL "objcopy tool")
set(CMAKE_SIZE_UTIL ${TOOLCHAIN_PREFIX}size CACHE INTERNAL "size tool")

## Here are the standard ROOT_PATH if you are using the standard toolchain
## if you are using a different toolchain you have to specify that too.
set(CMAKE_FIND_ROOT_PATH "${CMAKE_SYSROOT}")

set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --sysroot=${CMAKE_SYSROOT}" CACHE INTERNAL "" FORCE)

set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
95 changes: 95 additions & 0 deletions embedded/crosscompile_random_forest/board/flags-config.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# This function provides a set of specific flags for each supported board
# depending on the processor type. The objective is to optimize for size.
# Thus, all of the following flags are chosen carefully to reduce binary
# footprints.

# Set generic minimization flags for all platforms.
# These flags are the same for all cross-compilation cases and they are
# mainly to reduce the binary footprint.
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Os -s -fdata-sections -ffunction-sections")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fomit-frame-pointer -fno-unwind-tables")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-asynchronous-unwind-tables -fvisibility=hidden")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fshort-enums -finline-small-functions")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -findirect-inlining -fno-common")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fmerge-all-constants -fno-ident")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-unroll-loops -fno-math-errno")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-stack-protector")
set(CMAKE_OPENBLAS_FLAGS "${CMAKE_CXX_FLAGS}") # OpenBLAS does not supoport flto
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -flto")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--hash-style=gnu -Wl,--build-id=none")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,norelro")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections")
## Keep the following flag in comment, it will be relevant in the case of MCU's
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,-nmagic,-Bsymbolic -nostartfiles")

set(BOARD_NAME "" CACHE STRING "Specify Board name to optimize for.")
string(TOUPPER ${BOARD_NAME} BOARD)

# Set specific platforms CMAKE CXX flags.
if(BOARD MATCHES "RPI0" OR BOARD MATCHES "RPI1" OR BOARD MATCHES "ARM11")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mtune=arm1176jzf-s")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mcpu=arm1176jzf-s -mfloat-abi=hard -mfpu=vfp")
set(OPENBLAS_TARGET "ARMV6")
set(OPENBLAS_BINARY "32")
elseif(BOARD MATCHES "RPI2" OR BOARD MATCHES "CORTEXA7")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mtune=cortex-a7")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mfloat-abi=hard -mfpu=neon-vfpv4")
set(OPENBLAS_TARGET "ARMV7")
set(OPENBLAS_BINARY "32")
elseif(BOARD MATCHES "CORTEXA8")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mtune=cortex-a8")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mfloat-abi=hard -mfpu=neon")
set(OPENBLAS_TARGET "ARMV7")
set(OPENBLAS_BINARY "32")
elseif(BOARD MATCHES "CORTEXA9")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mtune=cortex-a9")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mfloat-abi=hard -mfpu=neon")
set(OPENBLAS_TARGET "CORTEXA9")
set(OPENBLAS_BINARY "32")
elseif(BOARD MATCHES "CORTEXA15")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mtune=cortex-a15")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mfloat-abi=hard -mfpu=neon")
set(OPENBLAS_TARGET "CORTEXA15")
set(OPENBLAS_BINARY "32")
elseif(BOARD MATCHES "RPI3" OR BOARD MATCHES "CORTEXA53")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mtune=cortex-a53 -mfloat-abi=hard")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mfpu=neon-fp-armv8 -mneon-for-64bit")
set(OPENBLAS_TARGET "CORTEXA53")
set(OPENBLAS_BINARY "64")
elseif(BOARD MATCHES "RPI4" OR BOARD MATCHES "CORTEXA72")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mtune=cortex-a72 -mfloat-abi=hard")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mfpu=neon-fp-armv8 -mneon-for-64bit")
set(OPENBLAS_TARGET "CORTEXA72")
set(OPENBLAS_BINARY "64")
elseif(BOARD MATCHES "JETSONAGX" OR BOARD MATCHES "CORTEXA76")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mtune=cortex-a76 -mfloat-abi=hard")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mfpu=neon-fp-armv8 -mneon-for-64bit")
set(OPENBLAS_TARGET "CORTEXA76")
set(OPENBLAS_BINARY "64")
elseif(BOARD MATCHES "BV")
set(OPENBLAS_TARGET "RISCV64_GENERIC")
set(OPENBLAS_BINARY "64")
elseif(BOARD MATCHES "C906")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mtune=thead-c906")
set(OPENBLAS_TARGET "RISCV64_GENERIC")
set(OPENBLAS_BINARY "64")
elseif(BOARD MATCHES "x280")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mtune=sifive-x280")
set(OPENBLAS_TARGET "x280")
set(OPENBLAS_BINARY "64")
elseif(BOARD MATCHES "KATAMI")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=pentium3")
set(OPENBLAS_TARGET "KATAMI")
set(OPENBLAS_BINARY "32")
elseif(BOARD MATCHES "COPPERMINE")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=pentium3")
set(OPENBLAS_TARGET "COPPERMINE")
set(OPENBLAS_BINARY "32")
elseif(BOARD MATCHES "NORTHWOOD")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=pentium4")
set(OPENBLAS_TARGET "NORTHWOOD")
set(OPENBLAS_BINARY "32")
elseif(BOARD)
## TODO: update documentation with a list of the supported boards.
message(FATAL_ERROR "Given BOARD_NAME is not known; please choose a supported board from the list")
endif()
50 changes: 50 additions & 0 deletions embedded/crosscompile_random_forest/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* This is a super simple example using random forest. The idea is show how we
* can cross compile this binary and use it on an embedded Linux device.
*
* It is up to the user to built something interesting out of this example, the
* following is just a starting point.
*
* mlpack is free software; you may redistribute it and/or modify it under the
* terms of the 3-clause BSD license. You should have received a copy of the
* 3-clause BSD license along with mlpack. If not, see
* http://www.opensource.org/licenses/BSD-3-Clause for more information.
*
* @author Omar Shrit
*/

#include <mlpack.hpp>

using namespace mlpack;

int main(int argc, char** argv)
{
arma::mat dataset;
data::Load("../../data/covertype-small.csv", dataset);

// Labels are the last row.
// The dataset stores labels from 1 through 7, but we need 0 through 6
// (in mlpack labels are zero-indexed), so we subtract 1.
arma::Row<size_t> labels = arma::conv_to<arma::Row<size_t>>::from(dataset.row(dataset.n_rows - 1)) - 1;
dataset.shed_row(dataset.n_rows - 1);

arma::mat trainSet, testSet;
arma::Row<size_t> trainLabels, testLabels;

// Split dataset randomly into training set and test set.
data::Split(dataset, labels, trainSet, testSet, trainLabels, testLabels, 0.3
/* Percentage of dataset to use for test set. */);

RandomForest<> rf(trainSet, trainLabels, 7 /* Number of classes in dataset */, 10 /* 10 trees */);
// Predict the labels of the test points.
arma::Row<size_t> output;
rf.Classify(testSet, output);
// Now print the accuracy. The 'probabilities' output could also be used to
// generate an ROC curve.
const size_t correct = arma::accu(output == testLabels);
std::cout << correct
<< " correct out of "
<< testLabels.n_elem << "\n"
<< 100.0 * correct / testLabels.n_elem
<< "%)." << std::endl;
}
Loading

0 comments on commit 4d413b5

Please sign in to comment.