| 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 |