Build Instructions | Mod9 ASR Engine

Building the Engine

This file documents how to build the Engine both using Docker and from the command line. It assumes a Linux operating system and the bash shell.

We strongly recommend building with Docker for deployment, since this provides a repeatable and consistent environment, and includes all prerequisites (other than Docker itself).

For development, it is often convenient to build on the command line, since this provides a faster edit/compile/debug loop.

Building with Docker

The Dockerfiles that support the Engine can be found under docker/mrp (the mrp namespace stands for "Meeting Recorder Project and is used in several places in the code to distinguish parts of the "core" or "upstream" Kaldi project from Engine-related extensions added by Mod9). The primary files we will cover here build Docker images for the build environment, runtime environment, models, and the Engine itself.

Some of the Dockerfiles depend on other base images already existing. By default, these dependent images would be expected to reside on Google Cloud Repository (GCR). Also in the same directory are files used to build images on Google Cloud Build (GCB) when code changes are pushed to a repository. This document does not cover GCB/GCR continuous integration.

Instead, this document will describe the process for building local images in the appropriate order so that dependent images are built before the images that depend on them.

Naming Conventions

All the Dockerfiles described in this document start with Dockerfile.engine. They can be followed with a suffix describing their function (e.g. Dockerfile.engine-builder). Although the images produced from these Dockerfiles can be named arbitrarily, our convention when building locally is to name the image e.g. local/engine-builder.

Note that the first comment line in each Dockerfile refers to images residing on GCR. For example, Dockerfile.engine-builder is related to gcr.io/remeeting/kaldi:engine-builder. Although you can use a similar convention if you wish, the steps below assume you are building locally and perhaps pushing to private remote Docker repository.

IMPORTANT: due to the proprietary nature of this software, it is not permissible to push images to a public Docker repository (e.g. docker.io or Docker Hub), unless you are the owner of the mod9/asr public repository hosting carefully licensed Engine images for evaluation purposes.

Dockerfile.engine-builder

This image contains various tools and external libraries that are needed to build later images. This includes the gcc compiler suite, GNU build tools, memory allocation libraries, math libraries, etc. It also builds various tools that are needed by Kaldi (e.g. OpenFST) and extra tools that Mod9 incorporated into the Engine (e.g. Boost and TensorFlow). Note that most of these tools are only used during the build process, and are not copied into the final Engine image.

This builder image is based on CentOS 7. Note that the versions of Linux (CentOS 7) and the compiler (gcc-11) were selected for maximum compatibility with older Linux distributions. CentOS 7 uses the oldest version of standard C/C++ libraries that is compatible with a version of gcc that supports C++17. If you have full control over the OS in which the Engine will ultimately run, you may consider upgrading the build environment to use more modern standard libraries -- but these Engine builds will not be backwards-compatible to run on older Linux distributions.

To build this image, navigate to the top level of a clean copy of the Kaldi fork containing the Engine source. There should be files COPYING, INSTALL, README.md, src, tools etc. in this directory. It's important that the copy has not been used for building on the command line (that is, that you have not tried to build or install the copy using other methods). A sanity check that the directory is clean is to run du -hs . while in the directory. It should return about 30M. If this Docker build context is larger, then it may include artifacts from prior command-line builds that may at best slow down the build, or at worst corrupt it. (To clean up, try cd src && make -j distclean; or better yet, delete and reinstall a fresh copy of the source code.)

Next, build the image with the command:

docker build -t local/engine-builder \
             -f docker/mrp/Dockerfile.engine-builder .

By default, this will use all available CPUs. If you have limited memory, you may need to reduce the number of CPUs used during the build. You can override the number of CPUs with a build argument. For example, to limit the number of simultaneous jobs to 10:

docker build --build-arg MAKE_JOBS=10 \
             -t local/engine-builder \
             -f docker/mrp/Dockerfile.engine-builder .

Dockerfile.engine-models

This image contains Mod9's ASR models. It copies the models from a URL, which by default points to a publicly readable Amazon S3 bucket. To build this image:

docker build -t local/engine-models \
             -f docker/mrp/Dockerfile.engine-models .

You can also build other model images (e.g. vosk, zamia) using similar commands, or construct your own models image by following the instructions in Dockerfile.engine-models.

It is worth noting that the final Docker image for the Engine is very intentionally structured so that these models are in the innermost layers. Because models are infrequently updated, this provides a significant advantage in terms of layer caching because updated versions of the Engine software result in a docker pull command reusing the model layers and only pulling the outermost layers with the updated software.

Dockerfile.engine-runtime

The runtime includes the OS environment and tools required to run the services included in the Engine. This includes Ubuntu 22.04, an Apache server (for the documentation, REST interfaces, etc.), a Python environment, and so on. You must also specify which builder image and models image will be used to build the runtime image.

docker build --build-arg BUILDER_IMAGE=local/engine-builder \
             --build-arg MODELS_IMAGE=local/engine-models \
             -t local/engine-runtime \
             -f docker/mrp/Dockerfile.engine-runtime .

Dockerfile.engine

The final Engine image performs the C++ compilation and library linking, and also uses the previously built images to produce an image that includes everything you need to run the Engine and associated tools (e.g. REST wrapper, documentation, etc.). To build the Engine image:

docker build --build-arg BUILDER_IMAGE=local/engine-builder \
             --build-arg RUNTIME_IMAGE=local/engine-runtime \
             -t local/engine \
             -f docker/mrp/Dockerfile.engine .

As with Dockerfile.engine-builder, this command will by default use all available CPUs, which could be problematic when building on a system with limited memory. You can restrict the parallelism of this step with --build-arg MAKE_JOBS=10, for example.

Running the Final Docker Image

Now that you have a final image, you can run the Engine as easily as:

docker run -it --rm -p 9900:9900 local/engine

Note that this locally build image named local/engine should be essentially the same as the publicly hosted remote Docker repository image named mod9/asr. Note also that the image's default command (but not its entrypoint) is the engine binary. To request non-default command-line arguments, be sure to specify the engine command explicitly, e.g.:

docker run -it --rm local/engine engine --version

See the deployment documentation (e.g. https://mod9.io/deployment) for more information.

Extracting the Engine Executable Binary

The easiest way to extract the Engine executable is to create a temporary Engine container and then use docker cp to copy it out. For example:

docker create --name=tmp-engine local/engine

Then, in another window:

docker cp tmp-engine:/opt/mod9-asr/bin/engine .

It may be helpful to also copy out the models from tmp-engine:/opt/mod9-asr/models.

Lastly, remember to clean up:

docker rm tmp-engine

Building on the Command Line

Although we strongly recommend using a Docker image for building the Engine for deployment, it's often better to use the command line for development. This section describes how to set up your environment and build the Engine. It assumes you're on Linux using the bash shell and have a clean directory tree containing the forked Kaldi source code. The top level directory should contain files COPYING, README.md, etc., and directories tools, src, etc.

Relationship to Kaldi

The ASR Engine uses the same directory structure and build system as Kaldi. If you're familiar with the Kaldi build process, the only difference is a few extra dependencies installed under the tools directory and building the mrpboostbin directory containing the Engine itself.

To install the extra dependencies, navigate to the tools directory and run the command extras/check_dependencies.sh. It will list the tools you need to install if they're not already installed. To build the Engine itself, navigate to the src directory, configure the build appropriately, and then run make -C mrpboostbin engine.

For those unfamiliar with Kaldi, the following sections provide step-by-step instructions.

Dependencies

There are a number of dependencies that you must install to build Kaldi and the Engine code. These include build tools (such as a compiler) and various libraries (such as OpenFST). Many dependencies are installed using scripts provided with the source code, but some (such as a compiler) are installed using your Linux distribution's installation system.

To check it the required build tools are installed, run the following from the top level directory:

cd tools
extras/check_dependencies.sh

This will report any missing system dependencies, perhaps reporting a line such as:

extras/check_dependencies.sh: Some prerequisites are missing; install
them using the command:

Copy and paste the line directly after this to install missing system tools (such as a compiler).

Next, there are a number of scripts under tools/extras to install other packages required either by Kaldi or by the Engine. The script extras/check_dependencies.sh should list them. You should copy/paste the commands for each of these dependencies. (If installing Tensorflow interactively, you should probably accept the suggested defaults.)

The dependencies checked by the above steps are not exhaustive; it is assumed you have a fairly fully featured Linux distribution installed. If you get command not found errors, it's usually easy to correct by searching for how to install the reported tool using your distribution's installation tools. It may also be helpful to refer to docker/mrp/Dockerfile.engine-builder for an example that sets up the build environment in a Centos/Fedora/RHEL Linux distribution (i.e. using the yum package manager). Mod9 generally uses an Ubuntu LTS release for local development.

Building OpenFST

Kaldi is a WFST-based ASR toolkit that critically depends on the OpenFST library. Mod9's fork of Kaldi upgrades this dependency to OpenFST 1.8.1, which requires C++17; the upstream Kaldi project uses an older version of OpenFST, along with C++14.

To install the required OpenFST library, navigate to the tools directory, and run:

make -j openfst

This will use all available cores to build openfst. If you want to run on fewer CPUs (e.g. if you don't have a lot of memory), you can run e.g.:

make -j 10 openfst

This will run on 10 cores rather than all available cores.

Configuring the Source

Once all the prerequisites are installed and built, the next step is to configure the source tree for Kaldi and the Engine. This uses the GNU autoconf package with a custom configure script.

To configure the source tree, navigate to the top-level src directory, and run:

cd src
./configure --static --use-cuda=no --enable-jemalloc=yes

This uses default values for most arguments, and our recommended settings to:

  • generate statically linked executables;
  • disable CUDA support;
  • enable a recommended memory allocator.

See the following subsections for a description of our recommended settings and other arguments you may want to specify. Run ./configure --help for a full list of all supported commands.

Support for CUDA

The Engine does not support CUDA. However, since the Engine uses the Kaldi infrastructure, and some Kaldi tools (notably training) use CUDA, the default is to include CUDA support. This will fail if you do not have the proper CUDA development tools already installed. We strongly recommend configuring the build environment without CUDA support. If you want to enable CUDA support, change the argument to --use-cuda=yes.

Static vs. Dynamic Linking

The build system can either produce self-contained executables (known as a static build), or the executables can point to shared libraries (known as a dynamic build). For deployment, we recommend a static build, since version skew between libraries and executables can cause difficulty in producing replicable builds. For development, it can be faster to use dynamic builds, though this introduces complexity and is only recommended if you're familiar with Linux shared libraries.

The ./configure script accepts several arguments related to static vs. dynamic builds, but we recommend using --static as shown above, or, if you want dynamic builds, using --shared in place of --static.

Memory Allocation Libraries

The default memory allocator on Linux is known to be inefficient. The Engine supports several replacement allocation libraries. The following flags to ./configure enable various memory allocation libraries. Call ./configure --help for more information on them.

--enable-jemalloc=yes
--enable-mimalloc=yes
--use-tcmalloc-mod9=yes

You should pass only one of the above. If you pass none of them, the standard malloc for your compiler will be used. Although each of the libraries has strengths and weaknesses, we recommend jemalloc as a good compromise on speed, memory overhead, and features. The Dockerfile build described above uses jemalloc by default.

License Type

By default, the Engine executable includes a custom license. That is, the license reported by the Engine assumes a custom license agreement has been negotiated and agreed to between the user of the Engine and Mod9. The other option is to compile with an evaluation license, which allows for 45 days of use (though this is not checked or enforced). You can change from the default custom license to the evaluation license by adding --license-type=evaluation to the arguments of ./configure.

Expiration

The Engine can be configured to expire at midnight Dec 31 the year after the year the Engine was built. That is, if the Engine was built on Apr 26 2022, then it expires on Dec 31 2023. You can configure the system to include an expiration date by adding --mod9-expiration=yes to the arguments of ./configure.

Other Configuration Options

The options to ./configure described in the previous sections include the most important options you can pass to ./configure. For a full list of options, call ./configure --help. The description of options unique to the Engine (rather than to Kaldi itself) are prefixed with MOD9:.

Note that the ./configure script also reads certain environment variables. When developing locally, it may be helpful to set CXXFLAGS=-Og for example, to override the default of -O3 and enable faster builds at a slight decrease in runtime performance. Alternatively, it may be necessary to set CXXFLAGS='-O3 -fno-omit-frame-pointer' to enable profiling with perf.

Building the Source

Once ./configure has run successfully, run the following commands to build the source:

make -j -C mrpboostbin engine

This will use all available CPUs. If you want to limit the number of CPUs (e.g. if you don't have a lot of memory), you can specify the number of concurrent jobs after -j, e.g.:

make -j 10 -C mrpboostbin engine

Note that this will only build the engine executable in mrpboostbin, which is generally what you want. However, you can also build all of the executables in mrpboostbin, including the mrp-crypt tool, by calling e.g. make -j 10 mrpboostbin. The executables in the rest of Kaldi are built with the usual make all (this excludes the Engine, under the make ext target).

If you change any of the source code, calling make -j 10 mrpboostbin will rebuild the changed files, the files that depend on the changed files, and the Engine. If you add new source code (and change the Makefiles appropriately), you will need to call e.g. make -j 10 depend so the build system can track which source files depend on which.

You can run make -j clean to remove all intermediate files, and make -j distclean will generally clean up the entire source tree.

Running the Engine on the Command Line

Once the build finishes successfully, the executable will be in src/mrpboostbin/engine. Assuming models are installed in the default location /opt/mod9-asr/models, you can start the Engine with:

cd mrpboostbin
./engine

See the deployment documentation (e.g. https://mod9.io/deployment) for more details.

Note that running engine --version will report metadata describing the build configuration.