Tutorial for compiling NCNN with WASM

The second step to deploy a deep learning model in the browser.

Posted by Zekun on November 15, 2020

The content of this tutorial is an extension of the Tutorial for compiling NCNN library.

When we successfully compile the NCNN library normally, we can use the tools in it to convert our models into NCNN format models (*.param and *.bin). This model can be used for various mobile terminal deployments.

But when we need to deploy the model in a webpage, we need to compile the model and the C++ program that calls it into WebAssembly format. Before compiling the program, we must first compile the NCNN library into the form of WebAssembly.

The content of this article is the specific steps for compiling the NCNN library into WebAssembly. The following steps have been tested on Ubuntu 18.04.

Install Node.js

Before starting, we need to install the latest version of node.js as a dependency in the environment. You can find the installation tutorial on its official Github. I chose its recommended installation location.

VERSION=v14.15.1
DISTRO=linux-x64
sudo mkdir -p /usr/local/lib/nodejs
sudo tar -xJvf node-$VERSION-$DISTRO.tar.xz -C /usr/local/lib/nodejs

This step is to extract the nodejs package to the recommended location. Next, run ~/.profile in terminal, and paste the content below to the end of the pop-up file.

# Nodejs
VERSION=v14.15.1
DISTRO=linux-x64
export PATH=/usr/local/lib/nodejs/node-$VERSION-$DISTRO/bin:$PATH

Run . ~/.profile in terminal to refresh the profile.

After the installation is over, you can run node -v in terminal to check whether the installation is successful.

Build NCNN with WASM

First, let’s clone the NCNN repository. This folder should be different from the one we used before(in the previous article), because they have different usage.

git clone https://github.com/Tencent/ncnn.git
cd ncnn

Then clone the key library emsdk for compiling WASM.

git clone https://github.com/emscripten-core/emsdk.git
cd emsdk

Install and activate it.

./emsdk install latest
./emsdk activate latest

Then, return to the parent folder(master of ncnn), set the emsdk as a source.

cd ..
source emsdk/emsdk_env.sh

Observe the output of the terminal here, you will find that the currently used nodejs is a built-in library in emsdk, and its version is old. Therefore, we need to re-add the latest version of nodejs that we installed before to the path.

export PATH=/usr/local/lib/nodejs/node-v14.15.1-linux-x64/bin:$PATH

You can run node -v to check the version again.

Next, prepare to compile the NCNN library.

mkdir build && cd build

If you are compiling for a browser that supports experimental WebAssembly features such as SIMD and SSE2, please use the following code.

cmake -DCMAKE_TOOLCHAIN_FILE=../emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -DNCNN_SIMPLEOMP=ON -DNCNN_BUILD_TESTS=ON ..

If it is compiled for browsers such as Safari in iOS without SIMD SSE2, replace it with the following code.

cmake -DCMAKE_TOOLCHAIN_FILE=../emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -DNCNN_OPENMP=OFF -DNCNN_SIMPLEOMP=OFF -DNCNN_RUNTIME_CPU=OFF -DNCNN_SSE2=OFF -DNCNN_AVX2=OFF -DNCNN_BUILD_TESTS=ON ..

Then start make.

make

After the make ends, use the built-in test case of NCNN to check whether the compilation is successful.

With SIMD SSE2:

TESTS_EXECUTABLE_LOADER=node TESTS_EXECUTABLE_LOADER_ARGUMENTS="--experimental-wasm-simd;--experimental-wasm-threads;--experimental-wasm-bulk-memory" ctest --output-on-failure -j 2

Without SIMD SSE2:

TESTS_EXECUTABLE_LOADER=node ctest --output-on-failure -j 2

If all the tests are passed, generate the install file.

make install

So far, the NCNN with WebAssembly has been compiled, and the next step is to introduce its usage.

Compile C++ code

First, we need to write a C++ program that calls the NCNN model. The relevant files I have written have been uploaded in the facemask-detection repository, and I will compile it based on this.

Clone it to the specified folder.

cd ~/Documents 
git clone https://github.com/waittim/facemask-detection.git
cd facemask-detection

Note that if you are compiling a version without SIMD and SSE2, please delete the following content in CMakeLists.txt.

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fopenmp -s USE_PTHREADS=1 -s PTHREAD_POOL_SIZE=15") 
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fopenmp -s USE_PTHREADS=1 -s PTHREAD_POOL_SIZE=15") 
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fopenmp -s USE_PTHREADS=1 -s PTHREAD_POOL_SIZE=15")

And change target_link_libraries(yolo ncnn pthread) to be target_link_libraries(yolo ncnn).

Next, create an empty folder named ncnn in it, and copy the two files under build/install/ under ncnn master to this ncnn folder.

mkdir ncnn

Copy the emsdk folder under ncnn master and paste it to the master folder of facemask-detection.

At this time, I recommend to use node -v to ensure that the version of nodejs is correct.

Finally, use emsdk to compile.

emcmake cmake
emmake make

After compiling, you can get yolo.js, yolo.wasm, yolo.worker.js. These are the files needed to call WASM format functions in the JavaScript environment.


The complete CMakeLists.txt content is as follows:

project(facemask-detection)

cmake_minimum_required(VERSION 3.10)

set(CMAKE_BUILD_TYPE release)

set(ncnn_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ncnn/lib/cmake/ncnn")
find_package(ncnn REQUIRED)

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -s FORCE_FILESYSTEM=1 -s INITIAL_MEMORY=256MB -s EXIT_RUNTIME=1")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s FORCE_FILESYSTEM=1 -s INITIAL_MEMORY=256MB -s EXIT_RUNTIME=1")
set(CMAKE_EXECUTBLE_LINKER_FLAGS "${CMAKE_EXECUTBLE_LINKER_FLAGS} -s FORCE_FILESYSTEM=1 -s INITIAL_MEMORY=256MB -s EXIT_RUNTIME=1")

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fopenmp -s USE_PTHREADS=1 -s PTHREAD_POOL_SIZE=15")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fopenmp -s USE_PTHREADS=1 -s PTHREAD_POOL_SIZE=15")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fopenmp -s USE_PTHREADS=1 -s PTHREAD_POOL_SIZE=15")

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -sEXPORTED_FUNCTIONS=['_yolo_ncnn'] --preload-file ${CMAKE_CURRENT_SOURCE_DIR}/assets@.")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -sEXPORTED_FUNCTIONS=['_yolo_ncnn'] --preload-file ${CMAKE_CURRENT_SOURCE_DIR}/assets@.")
set(CMAKE_EXECUTBLE_LINKER_FLAGS "${CMAKE_EXECUTBLE_LINKER_FLAGS} -sEXPORTED_FUNCTIONS=['_yolo_ncnn'] --preload-file ${CMAKE_CURRENT_SOURCE_DIR}/assets@.")


add_executable(yolo yolo.cpp)
target_link_libraries(yolo ncnn pthread)

Note: Thanks to NCNN creator nihui for her help in this process.