Skip to content

Commit ff17b83

Browse files
ljk53facebook-github-bot
authored andcommitted
[pytorch][ci] add custom selective build flow for android build (pytorch#40199)
Summary: Pull Request resolved: pytorch#40199 Mobile custom selective build has already been covered by `test/mobile/custom_build/build.sh`. It builds a CLI binary with host-toolchain and runs on host machine to check correctness of the result. But that custom build test doesn't cover the android/gradle build part. And we cannot use it to measure and track the in-APK size of custom build library. So this PR adds the selective build test coverage for android NDK build. Also integrate with the CI to upload the custom build size to scuba. TODO: Ideally it should build android/test_app and measure the in-APK size. But the test_app hasn't been covered by any CI yet and is currently broken, so build & measure AAR instead (which can be inaccurate as we plan to pack C++ header files into AAR soon). Sample result: https://fburl.com/scuba/pytorch_binary_size/skxwb1gh ``` +---------------------+-------------+-------------------+-----------+----------+ | build_mode | arch | lib | Build Num | Size | +---------------------+-------------+-------------------+-----------+----------+ | custom-build-single | armeabi-v7a | libpytorch_jni.so | 5901579 | 3.68 MiB | | prebuild | armeabi-v7a | libpytorch_jni.so | 5901014 | 6.23 MiB | | prebuild | x86_64 | libpytorch_jni.so | 5901014 | 7.67 MiB | +---------------------+-------------+-------------------+-----------+----------+ ``` Test Plan: Imported from OSS Differential Revision: D22111115 Pulled By: ljk53 fbshipit-source-id: 11d24efbc49a85f851ecd0e481d14123f405b3a9
1 parent 28e1d24 commit ff17b83

File tree

7 files changed

+199
-38
lines changed

7 files changed

+199
-38
lines changed

.circleci/cimodel/data/simple/android_definitions.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import cimodel.data.simple.util.branch_filters
1+
import cimodel.data.simple.util.branch_filters as branch_filters
22
from cimodel.data.simple.util.docker_constants import DOCKER_IMAGE_NDK
33

44

@@ -37,7 +37,7 @@ def gen_tree(self):
3737
}
3838

3939
if self.is_master_only:
40-
props_dict["filters"] = cimodel.data.simple.util.branch_filters.gen_filter_dict()
40+
props_dict["filters"] = branch_filters.gen_filter_dict(branch_filters.NON_PR_BRANCH_LIST)
4141

4242
return [{self.template_name: props_dict}]
4343

@@ -47,12 +47,14 @@ def __init__(self,
4747
job_name,
4848
template_name,
4949
dependencies,
50-
is_master_only=True):
50+
is_master_only=True,
51+
is_pr_only=False):
5152

5253
self.job_name = job_name
5354
self.template_name = template_name
5455
self.dependencies = dependencies
5556
self.is_master_only = is_master_only
57+
self.is_pr_only = is_pr_only
5658

5759
def gen_tree(self):
5860

@@ -62,7 +64,9 @@ def gen_tree(self):
6264
}
6365

6466
if self.is_master_only:
65-
props_dict["filters"] = cimodel.data.simple.util.branch_filters.gen_filter_dict()
67+
props_dict["filters"] = branch_filters.gen_filter_dict(branch_filters.NON_PR_BRANCH_LIST)
68+
elif self.is_pr_only:
69+
props_dict["filters"] = branch_filters.gen_filter_dict(branch_filters.PR_BRANCH_LIST)
6670

6771
return [{self.template_name: props_dict}]
6872

@@ -77,7 +81,14 @@ def gen_tree(self):
7781
"pytorch-linux-xenial-py3-clang5-android-ndk-r19c-gradle-build-x86_32",
7882
"pytorch_android_gradle_build-x86_32",
7983
["pytorch_linux_xenial_py3_clang5_android_ndk_r19c_x86_32_build"],
80-
is_master_only=False),
84+
is_master_only=False,
85+
is_pr_only=True),
86+
AndroidGradleJob(
87+
"pytorch-linux-xenial-py3-clang5-android-ndk-r19c-gradle-custom-build-single",
88+
"pytorch_android_gradle_custom_build_single",
89+
[],
90+
is_master_only=False,
91+
is_pr_only=True),
8192
AndroidGradleJob(
8293
"pytorch-linux-xenial-py3-clang5-android-ndk-r19c-gradle-build",
8394
"pytorch_android_gradle_build",

.circleci/cimodel/data/simple/util/branch_filters.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44
r"/release\/.*/",
55
]
66

7+
PR_BRANCH_LIST = [
8+
r"/gh\/.*\/head/",
9+
r"/pull\/.*/",
10+
]
11+
712
RC_PATTERN = r"/v[0-9]+(\.[0-9]+)*-rc[0-9]+/"
813

914
def gen_filter_dict(

.circleci/config.yml

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1624,14 +1624,6 @@ jobs:
16241624
image: ubuntu-1604:201903-01
16251625
steps:
16261626
- checkout
1627-
- run:
1628-
name: filter out not PR runs
1629-
no_output_timeout: "5m"
1630-
command: |
1631-
echo "CIRCLE_PULL_REQUEST: ${CIRCLE_PULL_REQUEST:-}"
1632-
if [ -z "${CIRCLE_PULL_REQUEST:-}" ]; then
1633-
circleci step halt
1634-
fi
16351627
- setup_linux_system_environment
16361628
- checkout
16371629
- setup_ci_environment
@@ -1663,6 +1655,46 @@ jobs:
16631655
path: ~/workspace/build_android_x86_32_artifacts/artifacts.tgz
16641656
destination: artifacts.tgz
16651657

1658+
pytorch_android_gradle_custom_build_single:
1659+
environment:
1660+
BUILD_ENVIRONMENT: pytorch-linux-xenial-py3-clang5-android-ndk-r19c-gradle-custom-build-single
1661+
DOCKER_IMAGE: "308535385114.dkr.ecr.us-east-1.amazonaws.com/pytorch/pytorch-linux-xenial-py3-clang5-android-ndk-r19c:209062ef-ab58-422a-b295-36c4eed6e906"
1662+
PYTHON_VERSION: "3.6"
1663+
resource_class: large
1664+
machine:
1665+
image: ubuntu-1604:201903-01
1666+
steps:
1667+
- checkout
1668+
- setup_linux_system_environment
1669+
- checkout
1670+
- setup_ci_environment
1671+
- run:
1672+
name: pytorch android gradle custom build single architecture (for PR)
1673+
no_output_timeout: "1h"
1674+
command: |
1675+
set -e
1676+
# Unlike other gradle jobs, it's not worth building libtorch in a separate CI job and share via docker, because:
1677+
# 1) Not shareable: it's custom selective build, which is different from default libtorch mobile build;
1678+
# 2) Not parallelizable by architecture: it only builds libtorch for one architecture;
1679+
1680+
echo "DOCKER_IMAGE: ${DOCKER_IMAGE}"
1681+
time docker pull ${DOCKER_IMAGE} >/dev/null
1682+
export id=$(docker run --cap-add=SYS_PTRACE --security-opt seccomp=unconfined --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -t -d -w /var/lib/jenkins ${DOCKER_IMAGE})
1683+
1684+
git submodule sync && git submodule update -q --init --recursive
1685+
1686+
VOLUME_MOUNTS="-v /home/circleci/project/:/var/lib/jenkins/workspace"
1687+
export id=$(docker run ${VOLUME_MOUNTS} --cap-add=SYS_PTRACE --security-opt seccomp=unconfined --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -t -d -w /var/lib/jenkins ${DOCKER_IMAGE})
1688+
1689+
export COMMAND='((echo "source ./workspace/env" && echo "export BUILD_ENVIRONMENT=${BUILD_ENVIRONMENT}" && echo "export GRADLE_OFFLINE=1" && echo "sudo chown -R jenkins workspace && cd workspace && ./.circleci/scripts/build_android_gradle.sh") | docker exec -u jenkins -i "$id" bash) 2>&1'
1690+
echo ${COMMAND} > ./command.sh && unbuffer bash ./command.sh | ts
1691+
1692+
# Skip docker push as this job is purely for size analysis purpose.
1693+
# Result binaries are already in `/home/circleci/project/` as it's mounted instead of copied.
1694+
1695+
- upload_binary_size_for_android_build:
1696+
build_type: custom-build-single
1697+
16661698
pytorch_ios_build:
16671699
<<: *pytorch_ios_params
16681700
macos:
@@ -7554,9 +7586,21 @@ workflows:
75547586
docker_image: "308535385114.dkr.ecr.us-east-1.amazonaws.com/pytorch/pytorch-linux-xenial-py3-clang5-android-ndk-r19c:fff7795428560442086f7b2bb6004b65245dc11a"
75557587
name: pytorch_linux_xenial_py3_clang5_android_ndk_r19c_vulkan_x86_32_build
75567588
- pytorch_android_gradle_build-x86_32:
7589+
filters:
7590+
branches:
7591+
only:
7592+
- /gh\/.*\/head/
7593+
- /pull\/.*/
75577594
name: pytorch-linux-xenial-py3-clang5-android-ndk-r19c-gradle-build-x86_32
75587595
requires:
75597596
- pytorch_linux_xenial_py3_clang5_android_ndk_r19c_x86_32_build
7597+
- pytorch_android_gradle_custom_build_single:
7598+
filters:
7599+
branches:
7600+
only:
7601+
- /gh\/.*\/head/
7602+
- /pull\/.*/
7603+
name: pytorch-linux-xenial-py3-clang5-android-ndk-r19c-gradle-custom-build-single
75607604
- pytorch_android_gradle_build:
75617605
filters:
75627606
branches:

.circleci/scripts/build_android_gradle.sh

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
#!/usr/bin/env bash
22
set -eux -o pipefail
33

4+
env
5+
echo "BUILD_ENVIRONMENT:$BUILD_ENVIRONMENT"
6+
47
export ANDROID_NDK_HOME=/opt/ndk
8+
export ANDROID_NDK=/opt/ndk
59
export ANDROID_HOME=/opt/android/sdk
610

711
# Must be in sync with GRADLE_VERSION in docker image for android
@@ -10,6 +14,31 @@ export GRADLE_VERSION=4.10.3
1014
export GRADLE_HOME=/opt/gradle/gradle-$GRADLE_VERSION
1115
export GRADLE_PATH=$GRADLE_HOME/bin/gradle
1216

17+
# touch gradle cache files to prevent expiration
18+
while IFS= read -r -d '' file
19+
do
20+
touch "$file" || true
21+
done < <(find /var/lib/jenkins/.gradle -type f -print0)
22+
23+
export GRADLE_LOCAL_PROPERTIES=~/workspace/android/local.properties
24+
rm -f $GRADLE_LOCAL_PROPERTIES
25+
echo "sdk.dir=/opt/android/sdk" >> $GRADLE_LOCAL_PROPERTIES
26+
echo "ndk.dir=/opt/ndk" >> $GRADLE_LOCAL_PROPERTIES
27+
echo "cmake.dir=/usr/local" >> $GRADLE_LOCAL_PROPERTIES
28+
29+
retry () {
30+
$* || (sleep 1 && $*) || (sleep 2 && $*) || (sleep 4 && $*) || (sleep 8 && $*)
31+
}
32+
33+
# Run custom build script
34+
if [[ "${BUILD_ENVIRONMENT}" == *-gradle-custom-build* ]]; then
35+
# Install torch & torchvision - used to download & dump used ops from test model.
36+
retry pip install torch torchvision --progress-bar off
37+
38+
exec "$(dirname "${BASH_SOURCE[0]}")/../../android/build_test_app_custom.sh" armeabi-v7a
39+
fi
40+
41+
# Run default build
1342
BUILD_ANDROID_INCLUDE_DIR_x86=~/workspace/build_android/install/include
1443
BUILD_ANDROID_LIB_DIR_x86=~/workspace/build_android/install/lib
1544

@@ -44,9 +73,6 @@ ln -s ${BUILD_ANDROID_INCLUDE_DIR_arm_v8a} ${JNI_INCLUDE_DIR}/arm64-v8a
4473
ln -s ${BUILD_ANDROID_LIB_DIR_arm_v8a} ${JNI_LIBS_DIR}/arm64-v8a
4574
fi
4675

47-
env
48-
echo "BUILD_ENVIRONMENT:$BUILD_ENVIRONMENT"
49-
5076
GRADLE_PARAMS="-p android assembleRelease --debug --stacktrace"
5177
if [[ "${BUILD_ENVIRONMENT}" == *-gradle-build-only-x86_32* ]]; then
5278
GRADLE_PARAMS+=" -PABI_FILTERS=x86"
@@ -56,20 +82,6 @@ if [ -n "{GRADLE_OFFLINE:-}" ]; then
5682
GRADLE_PARAMS+=" --offline"
5783
fi
5884

59-
# touch gradle cache files to prevent expiration
60-
while IFS= read -r -d '' file
61-
do
62-
touch "$file" || true
63-
done < <(find /var/lib/jenkins/.gradle -type f -print0)
64-
65-
env
66-
67-
export GRADLE_LOCAL_PROPERTIES=~/workspace/android/local.properties
68-
rm -f $GRADLE_LOCAL_PROPERTIES
69-
echo "sdk.dir=/opt/android/sdk" >> $GRADLE_LOCAL_PROPERTIES
70-
echo "ndk.dir=/opt/ndk" >> $GRADLE_LOCAL_PROPERTIES
71-
echo "cmake.dir=/usr/local" >> $GRADLE_LOCAL_PROPERTIES
72-
7385
$GRADLE_PATH $GRADLE_PARAMS
7486

7587
find . -type f -name "*.a" -exec ls -lh {} \;

.circleci/verbatim-sources/job-specs/job-specs-custom.yml

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -283,14 +283,6 @@
283283
image: ubuntu-1604:201903-01
284284
steps:
285285
- checkout
286-
- run:
287-
name: filter out not PR runs
288-
no_output_timeout: "5m"
289-
command: |
290-
echo "CIRCLE_PULL_REQUEST: ${CIRCLE_PULL_REQUEST:-}"
291-
if [ -z "${CIRCLE_PULL_REQUEST:-}" ]; then
292-
circleci step halt
293-
fi
294286
- setup_linux_system_environment
295287
- checkout
296288
- setup_ci_environment
@@ -322,6 +314,46 @@
322314
path: ~/workspace/build_android_x86_32_artifacts/artifacts.tgz
323315
destination: artifacts.tgz
324316

317+
pytorch_android_gradle_custom_build_single:
318+
environment:
319+
BUILD_ENVIRONMENT: pytorch-linux-xenial-py3-clang5-android-ndk-r19c-gradle-custom-build-single
320+
DOCKER_IMAGE: "308535385114.dkr.ecr.us-east-1.amazonaws.com/pytorch/pytorch-linux-xenial-py3-clang5-android-ndk-r19c:209062ef-ab58-422a-b295-36c4eed6e906"
321+
PYTHON_VERSION: "3.6"
322+
resource_class: large
323+
machine:
324+
image: ubuntu-1604:201903-01
325+
steps:
326+
- checkout
327+
- setup_linux_system_environment
328+
- checkout
329+
- setup_ci_environment
330+
- run:
331+
name: pytorch android gradle custom build single architecture (for PR)
332+
no_output_timeout: "1h"
333+
command: |
334+
set -e
335+
# Unlike other gradle jobs, it's not worth building libtorch in a separate CI job and share via docker, because:
336+
# 1) Not shareable: it's custom selective build, which is different from default libtorch mobile build;
337+
# 2) Not parallelizable by architecture: it only builds libtorch for one architecture;
338+
339+
echo "DOCKER_IMAGE: ${DOCKER_IMAGE}"
340+
time docker pull ${DOCKER_IMAGE} >/dev/null
341+
export id=$(docker run --cap-add=SYS_PTRACE --security-opt seccomp=unconfined --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -t -d -w /var/lib/jenkins ${DOCKER_IMAGE})
342+
343+
git submodule sync && git submodule update -q --init --recursive
344+
345+
VOLUME_MOUNTS="-v /home/circleci/project/:/var/lib/jenkins/workspace"
346+
export id=$(docker run ${VOLUME_MOUNTS} --cap-add=SYS_PTRACE --security-opt seccomp=unconfined --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -t -d -w /var/lib/jenkins ${DOCKER_IMAGE})
347+
348+
export COMMAND='((echo "source ./workspace/env" && echo "export BUILD_ENVIRONMENT=${BUILD_ENVIRONMENT}" && echo "export GRADLE_OFFLINE=1" && echo "sudo chown -R jenkins workspace && cd workspace && ./.circleci/scripts/build_android_gradle.sh") | docker exec -u jenkins -i "$id" bash) 2>&1'
349+
echo ${COMMAND} > ./command.sh && unbuffer bash ./command.sh | ts
350+
351+
# Skip docker push as this job is purely for size analysis purpose.
352+
# Result binaries are already in `/home/circleci/project/` as it's mounted instead of copied.
353+
354+
- upload_binary_size_for_android_build:
355+
build_type: custom-build-single
356+
325357
pytorch_ios_build:
326358
<<: *pytorch_ios_params
327359
macos:

android/build_test_app_custom.sh

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#!/bin/bash
2+
###############################################################################
3+
# This script tests the custom selective build flow for PyTorch Android, which
4+
# optimizes library size by only including ops used by a specific model.
5+
###############################################################################
6+
7+
set -eux
8+
9+
PYTORCH_DIR="$(cd $(dirname $0)/..; pwd -P)"
10+
PYTORCH_ANDROID_DIR="${PYTORCH_DIR}/android"
11+
BUILD_ROOT="${PYTORCH_DIR}/build_pytorch_android_custom"
12+
13+
source "${PYTORCH_ANDROID_DIR}/common.sh"
14+
15+
prepare_model_and_dump_root_ops() {
16+
cd "${BUILD_ROOT}"
17+
MODEL="${BUILD_ROOT}/MobileNetV2.pt"
18+
ROOT_OPS="${BUILD_ROOT}/MobileNetV2.yaml"
19+
python "${PYTORCH_ANDROID_DIR}/test_app/make_assets_custom.py"
20+
cp "${MODEL}" "${PYTORCH_ANDROID_DIR}/test_app/app/src/main/assets/mobilenet2.pt"
21+
}
22+
23+
# Start building
24+
mkdir -p "${BUILD_ROOT}"
25+
check_android_sdk
26+
check_gradle
27+
parse_abis_list "$@"
28+
prepare_model_and_dump_root_ops
29+
SELECTED_OP_LIST="${ROOT_OPS}" build_android
30+
31+
# TODO: change this to build test_app instead
32+
$GRADLE_PATH -PABI_FILTERS=$ABIS_LIST -p $PYTORCH_ANDROID_DIR clean assembleRelease
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
"""
2+
This is a script for PyTorch Android custom selective build test. It prepares
3+
MobileNetV2 TorchScript model, and dumps root ops used by the model for custom
4+
build script to create a tailored build which only contains these used ops.
5+
"""
6+
7+
import torch
8+
import torchvision
9+
import yaml
10+
11+
# Download and trace the model.
12+
model = torchvision.models.mobilenet_v2(pretrained=True)
13+
model.eval()
14+
example = torch.rand(1, 3, 224, 224)
15+
# TODO: create script model with `torch.jit.script`
16+
traced_script_module = torch.jit.trace(model, example)
17+
18+
# Save traced TorchScript model.
19+
traced_script_module.save("MobileNetV2.pt")
20+
21+
# Dump root ops used by the model (for custom build optimization).
22+
ops = torch.jit.export_opnames(traced_script_module)
23+
24+
with open('MobileNetV2.yaml', 'w') as output:
25+
yaml.dump(ops, output)

0 commit comments

Comments
 (0)