blob: 75ed9b2c02e6eb0e72fb48a03ece810b9b61852d [file] [log] [blame] [view]
Mobile apps are very sensitive to their download sizes as every increase in KB
may result in a user number decrease. Flutter engine (`libflutter.so`) has to be
included in every Flutter app and it has a size of several MBs. So we'll try to
reduce its size by using [MLGO][MLGO]. It's different from the previous Flutter
attempt of reducing sizes as MLGO does not require any code or dependency
removals.
Reducing engine size with MLGO needs to 1) train a model once, 2) apply that
model to compile the Flutter engine. Note that model training does not need to
happen too frequently - the model should 'hold up' to code changes over
weeks/months. On Ubuntu, do the following:
- **Follow the [Setting-up-the-Engine-development-environment][engine setup]**.
To check if this step is successful, try [compiling for Android][compile
android]. For size comparisons, we recommend using the release Android build
`./flutter/tools/gn --android --runtime-mode=release --no-goma`. (Option
`--no-goma` is needed if you're not a Googler, or if you're using a custom
Clang as we'll do later with MLGO.)
- **Set up [MLGO] LLVM**. (The steps are adapted from [MLGO demo][MLGO demo].)
1. Prerequisites: `sudo apt-get install cmake ninja-build lld`.
2. Create a root directory for everything MLGO related `mkdir ~/mlgo &&
export MLGO_DIR=~/mlgo`
3. Clone MLGO repo
`cd $MLGO_DIR && git clone https://github.com/google/ml-compiler-opt.git`
4. Tensorflow dependencies
```bash
cd $MLGO_DIR
sudo apt-get install python3-pip
python3 -m pip install --upgrade pip
python3 -m pip install --user -r ml-compiler-opt/requirements.txt
TF_PIP=$(python3 -m pip show tensorflow | grep Location | cut -d ' ' -f 2)
export TENSORFLOW_AOT_PATH="${TF_PIP}/tensorflow"
mkdir $MLGO_DIR/tensorflow
export TENSORFLOW_C_LIB_PATH=$MLGO_DIR/tensorflow
wget --quiet https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow-cpu-linux-x86_64-1.15.0.tar.gz
tar xfz libtensorflow-cpu-linux-x86_64-1.15.0.tar.gz -C "${TENSORFLOW_C_LIB_PATH}"
```
5. Clone llvm-project
```bash
cd $MLGO_DIR && git clone https://github.com/llvm/llvm-project.git
export LLVM_SRCDIR=$MLGO_DIR/llvm-project
export LLVM_INSTALLDIR=$MLGO_DIR/llvm-install
```
6. Build LLVM
```bash
cd ${LLVM_SRCDIR}
mkdir build
cd build
cmake -G Ninja \
-DLLVM_ENABLE_LTO=OFF \
-DCMAKE_INSTALL_PREFIX= \
-DTENSORFLOW_C_LIB_PATH=${TENSORFLOW_C_LIB_PATH} \
-DCMAKE_INSTALL_RPATH_USE_LINK_PATH=On \
-C ${LLVM_SRCDIR}/clang/cmake/caches/Fuchsia-stage2.cmake \
${LLVM_SRCDIR}/llvm
ninja distribution
DESTDIR=${LLVM_INSTALLDIR} ninja install-distribution-stripped
```
- **Build Flutter engine for MLGO training**
```bash
# Set your engine dir appropriately if it's not in the default location
export ENGINE_DIR=~/flutter/engine/src
cd $ENGINE_DIR
sed -i \
's/cflags += lto_flags/cflags += lto_flags + ["-Xclang", "-fembed-bitcode=all"]/' \
build/config/compiler/BUILD.gn
sed -i \
"s/prefix = rebase_path(\"\/\/buildtools\/\$host_dir\/clang\/bin\", root_build_dir)/prefix = \"${LLVM_INSTALLDIR//\//\\/}\/bin\"/" \
build/toolchain/android/BUILD.gn
./flutter/tools/gn --android --runtime-mode=release --no-goma --no-lto
ninja -C out/android_release
```
- **Train the model**
```bash
export CORPUS=$MLGO_DIR/corpus
cd $MLGO_DIR/ml-compiler-opt
python3 compiler_opt/tools/extract_ir.py \
--cmd_filter="^-Oz$" \
--input=$ENGINE_DIR/out/compile_commands.json \
--input_type=json \
--llvm_objcopy_path=$LLVM_INSTALLDIR/bin/llvm-objcopy \
--output_dir=$CORPUS
export DEFAULT_TRACE=$MLGO_DIR/default_trace
export WARMSTART_OUTPUT_DIR=$MLGO_DIR/warmstart
export OUTPUT_DIR=$MLGO_DIR/model
rm -rf $DEFAULT_TRACE && \
PYTHONPATH=$PYTHONPATH:. python3 \
compiler_opt/tools/generate_default_trace.py \
--data_path=$CORPUS \
--output_path=$DEFAULT_TRACE \
--compile_task=inlining \
--clang_path=$LLVM_INSTALLDIR/bin/clang \
--llvm_size_path=$LLVM_INSTALLDIR/bin/llvm-size \
--sampling_rate=0.2
rm -rf $WARMSTART_OUTPUT_DIR && \
PYTHONPATH=$PYTHONPATH:. python3 \
compiler_opt/rl/train_bc.py \
--root_dir=$WARMSTART_OUTPUT_DIR \
--data_path=$DEFAULT_TRACE \
--gin_files=compiler_opt/rl/inlining/gin_configs/behavioral_cloning_nn_agent.gin
# The following will take about half a day.
rm -rf $OUTPUT_DIR && \
PYTHONPATH=$PYTHONPATH:. python3 \
compiler_opt/rl/train_locally.py \
--root_dir=$OUTPUT_DIR \
--data_path=$CORPUS \
--clang_path=$LLVM_INSTALLDIR/bin/clang \
--llvm_size_path=$LLVM_INSTALLDIR/bin/llvm-size \
--num_modules=100 \
--gin_files=compiler_opt/rl/inlining/gin_configs/ppo_nn_agent.gin \
--gin_bindings=train_eval.warmstart_policy_dir=\"$WARMSTART_OUTPUT_DIR/saved_policy\"
```
- **Build LLVM with the trained model**
```bash
cd $LLVM_SRCDIR
rm -rf llvm/lib/Analysis/models/inliner/*
cp -rf $OUTPUT_DIR/saved_policy/* llvm/lib/Analysis/models/inliner/
mkdir build-release
cd build-release
cmake -G Ninja \
-DLLVM_ENABLE_LTO=OFF \
-DCMAKE_INSTALL_PREFIX= \
-DTENSORFLOW_AOT_PATH=${TENSORFLOW_AOT_PATH} \
-C ${LLVM_SRCDIR}/clang/cmake/caches/Fuchsia-stage2.cmake \
${LLVM_SRCDIR}/llvm
export LLVM_INSTALLDIR_RELEASE=$LLVM_INSTALLDIR-release
ninja distribution
DESTDIR=${LLVM_INSTALLDIR_RELEASE} ninja install-distribution-stripped
```
- **Build Flutter engine using LLVM with the trained model**
```bash
cd $ENGINE_DIR
git stash # Undo previous changes for model training
sed -i \
's/cflags += lto_flags/cflags += lto_flags + ["-mllvm", "-enable-ml-inliner=release"]/' \
build/config/compiler/BUILD.gn
sed -i \
"s/prefix = rebase_path(\"\/\/buildtools\/\$host_dir\/clang\/bin\", root_build_dir)/prefix = \"${LLVM_INSTALLDIR_RELEASE//\//\\/}\/bin\"/" \
build/toolchain/android/BUILD.gn
./flutter/tools/gn --android --runtime-mode=release --no-goma --no-lto
ninja -C out/android_release libflutter.so
```
- **Compare**. To compare the engine size with or without MLGO, one can add or
remove the `["-mllvm", "-enable-ml-inliner=release"]` flags in
`build/config/compiler/BUILD.gn`, compile the engine, and check the size of
`out/android_release/lib.stripped/libflutter.so`. As end-users will download
zipped engine, we also recommend comparing its zipped size.
```bash
export ENGINE_LIB_DIR=$ENGINE_DIR/out/android_release/lib.stripped
cd $ENGINE_DIR
./flutter/tools/gn --android --runtime-mode=release --no-goma --no-lto
ninja -C out/android_release libflutter.so
cd $ENGINE_LIB_DIR
mv libflutter.so libflutter.ml_nolto.so
zip libflutter.ml_nolto.so.zip libflutter.ml_nolto.so
cd $ENGINE_DIR
./flutter/tools/gn --android --runtime-mode=release --no-goma
ninja -C out/android_release libflutter.so
cd $ENGINE_LIB_DIR
mv libflutter.so libflutter.ml_lto.so
zip libflutter.ml_lto.so.zip libflutter.ml_lto.so
# Remove the ML flags to disable ML.
cd $ENGINE_DIR
sed -i \
's/cflags += lto_flags + \["-mllvm", "-enable-ml-inliner=release"\]/cflags += lto_flags/' \
build/config/compiler/BUILD.gn
cd $ENGINE_DIR
./flutter/tools/gn --android --runtime-mode=release --no-goma --no-lto
ninja -C out/android_release libflutter.so
cd $ENGINE_LIB_DIR
mv libflutter.so libflutter.noml_nolto.so
zip libflutter.noml_nolto.so.zip libflutter.noml_nolto.so
cd $ENGINE_DIR
./flutter/tools/gn --android --runtime-mode=release --no-goma
ninja -C out/android_release libflutter.so
cd $ENGINE_LIB_DIR
mv libflutter.so libflutter.noml_lto.so
zip libflutter.noml_lto.so.zip libflutter.noml_lto.so
ls -l
```
Here's the table of size comparisons for engine version b9ecd8a.
| Flutter engine size comparison | ML_LTO | ML_NOLTO | NOML_LTO | NOML_NOLTO |
| --- | --- | --- | --- | --- |
| unzipped size (bytes) | 6270960 | 6338580 | 6312012 | 6577684 |
| zipped size (bytes) | 3586091 | **3577604** | 3606484 | 3689468 |
| unzipped size change over NOML_LTO | -0.65% | 0.4% | 0 | 4.21% |
| zipped size change over NOML_LTO | -0.57% | **-0.80%** | 0 | 2.3% |
| unzipped size change over NOML_NOLTO | -4.66% | -3.64% | -4.04% | 0 |
| zipped size change over NOML_NOLTO | -2.80% | -3.03% | -2.25% | 0 |
## Conclusion
As shown in the table above, for the zipped size, the winner here is the
ML_NOLTO version which is even smaller than the ML_LTO version. It has a 0.8%
reduction over our previous art of NOML_LTO.
The ML_LTO version is not very good because currently the model can only be
trained without LTO. [MLGO][MLGO] is planning to allow ThinLTO in their
training. Hopefully, it will help achieve the MLGO's normal reduction of 3%-5%
(e.g., ML_NOLTO vs NOML_NOLTO) when the training and final build are in the same
condition.
[MLGO]: https://github.com/google/ml-compiler-opt
[engine setup]: ./contributing/Setting-up-the-Engine-development-environment
[compile android]: ./contributing/Compiling-the-engine#compiling-for-android-from-macos-or-linux
[MLGO demo]: https://github.com/google/ml-compiler-opt/blob/main/docs/demo/demo.md