Skip to content

Commit 1768668

Browse files
author
pytorchbot
committed
2024-11-19 nightly release (04f6fcd)
1 parent 7059b27 commit 1768668

File tree

99 files changed

+2780
-749
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

99 files changed

+2780
-749
lines changed

.ci/docker/common/install_cache.sh

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,26 @@ set -ex
1212
# shellcheck source=/dev/null
1313
source "$(dirname "${BASH_SOURCE[0]}")/utils.sh"
1414

15+
install_ubuntu() {
16+
echo "Preparing to build sccache from source"
17+
apt-get update
18+
# libssl-dev will not work as it is upgraded to libssl3 in Ubuntu-22.04.
19+
# Instead use lib and headers from OpenSSL1.1 installed in `install_openssl.sh``
20+
apt-get install -y cargo
21+
echo "Checking out sccache repo"
22+
git clone https://github.com/mozilla/sccache -b v0.8.2
23+
24+
cd sccache
25+
echo "Building sccache"
26+
cargo build --release
27+
cp target/release/sccache /opt/cache/bin
28+
echo "Cleaning up"
29+
cd ..
30+
rm -rf sccache
31+
apt-get remove -y cargo rustc
32+
apt-get autoclean && apt-get clean
33+
}
34+
1535
install_binary() {
1636
echo "Downloading sccache binary from S3 repo"
1737
curl --retry 3 https://s3.amazonaws.com/ossci-linux/sccache -o /opt/cache/bin/sccache
@@ -22,15 +42,33 @@ mkdir -p /opt/cache/bin
2242
sed -e 's|PATH="\(.*\)"|PATH="/opt/cache/bin:\1"|g' -i /etc/environment
2343
export PATH="/opt/cache/bin:$PATH"
2444

25-
# NB: Install the pre-built binary from S3 as building from source
26-
# https://github.com/pytorch/sccache has started failing mysteriously
27-
# in which sccache server couldn't start with the following error:
28-
# sccache: error: Invalid argument (os error 22)
29-
install_binary
45+
install_ubuntu
3046

3147
function write_sccache_stub() {
3248
BINARY=$1
33-
printf "#!/bin/sh\nif [ \$(env -u LD_PRELOAD ps -p \$PPID -o comm=) != sccache ]; then\n exec sccache %s \"\$@\"\nelse\n exec %s \"\$@\"\nfi" "$(which "${BINARY}")" "$(which "${BINARY}")" > "/opt/cache/bin/${BINARY}"
49+
if [ $1 == "gcc" ]; then
50+
# Do not call sccache recursively when dumping preprocessor argument
51+
# For some reason it's very important for the first cached nvcc invocation
52+
cat >"/opt/cache/bin/$1" <<EOF
53+
#!/bin/sh
54+
if [ "\$1" = "-E" ] || [ "\$2" = "-E" ]; then
55+
exec $(which $1) "\$@"
56+
elif [ \$(env -u LD_PRELOAD ps -p \$PPID -o comm=) != sccache ]; then
57+
exec sccache $(which $1) "\$@"
58+
else
59+
exec $(which $1) "\$@"
60+
fi
61+
EOF
62+
else
63+
cat >"/opt/cache/bin/$1" <<EOF
64+
#!/bin/sh
65+
if [ \$(env -u LD_PRELOAD ps -p \$PPID -o comm=) != sccache ]; then
66+
exec sccache $(which $1) "\$@"
67+
else
68+
exec $(which $1) "\$@"
69+
fi
70+
EOF
71+
fi
3472
chmod a+x "/opt/cache/bin/${BINARY}"
3573
}
3674

@@ -44,7 +82,7 @@ init_sccache() {
4482

4583
# NB: This function is adopted from PyTorch core at
4684
# https://github.com/pytorch/pytorch/blob/main/.ci/pytorch/common-build.sh
47-
as_ci_user sccache --stop-server > /dev/null 2>&1 || true
85+
as_ci_user sccache --stop-server >/dev/null 2>&1 || true
4886
rm -f "${SCCACHE_ERROR_LOG}" || true
4987

5088
# Clear sccache stats before using it

.ci/docker/ubuntu/Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ COPY ./common/utils.sh utils.sh
5757
RUN bash ./install_cache.sh && rm install_cache.sh utils.sh
5858
ENV SCCACHE_BUCKET ossci-compiler-cache-circleci-v2
5959
ENV SCCACHE_S3_KEY_PREFIX executorch
60+
ENV SCCACHE_REGION us-east-1
6061

6162
ARG TORCH_VERSION
6263
COPY ./common/install_pytorch.sh install_pytorch.sh

.github/scripts/check_labels.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#!/usr/bin/env python3
2+
"""Check whether a PR has required labels."""
3+
4+
import sys
5+
from typing import Any
6+
7+
from github_utils import gh_delete_comment, gh_post_pr_comment
8+
from gitutils import get_git_remote_name, get_git_repo_dir, GitRepo
9+
from label_utils import has_required_labels, is_label_err_comment, LABEL_ERR_MSG
10+
from trymerge import GitHubPR
11+
12+
13+
def delete_all_label_err_comments(pr: "GitHubPR") -> None:
14+
for comment in pr.get_comments():
15+
if is_label_err_comment(comment):
16+
gh_delete_comment(pr.org, pr.project, comment.database_id)
17+
18+
19+
def add_label_err_comment(pr: "GitHubPR") -> None:
20+
# Only make a comment if one doesn't exist already
21+
if not any(is_label_err_comment(comment) for comment in pr.get_comments()):
22+
gh_post_pr_comment(pr.org, pr.project, pr.pr_num, LABEL_ERR_MSG)
23+
24+
25+
def parse_args() -> Any:
26+
from argparse import ArgumentParser
27+
28+
parser = ArgumentParser("Check PR labels")
29+
parser.add_argument("pr_num", type=int)
30+
# add a flag to return a non-zero exit code if the PR does not have the required labels
31+
parser.add_argument(
32+
"--exit-non-zero",
33+
action="store_true",
34+
help="Return a non-zero exit code if the PR does not have the required labels",
35+
)
36+
37+
return parser.parse_args()
38+
39+
40+
def main() -> None:
41+
args = parse_args()
42+
repo = GitRepo(get_git_repo_dir(), get_git_remote_name())
43+
org, project = repo.gh_owner_and_name()
44+
pr = GitHubPR(org, project, args.pr_num)
45+
46+
try:
47+
if not has_required_labels(pr):
48+
print(LABEL_ERR_MSG)
49+
add_label_err_comment(pr)
50+
if args.exit_non_zero:
51+
sys.exit(1)
52+
else:
53+
delete_all_label_err_comments(pr)
54+
except Exception as e:
55+
if args.exit_non_zero:
56+
sys.exit(1)
57+
58+
sys.exit(0)
59+
60+
61+
if __name__ == "__main__":
62+
main()

.github/workflows/check-labels.yml

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
name: Check Labels
2+
3+
on:
4+
# We need pull_request_target to be able to post comments on PRs from forks.
5+
# Only allow pull_request_target when merging to main, not some historical branch.
6+
#
7+
# Make sure to don't introduce explicit checking out and installing/running
8+
# untrusted user code into this workflow!
9+
pull_request_target:
10+
types: [opened, synchronize, reopened, labeled, unlabeled]
11+
branches: [main]
12+
13+
# To check labels on ghstack PRs.
14+
# Note: as pull_request doesn't trigger on PRs targeting main,
15+
# to test changes to the workflow itself one needs to create
16+
# a PR that targets a gh/**/base branch.
17+
pull_request:
18+
types: [opened, synchronize, reopened, labeled, unlabeled]
19+
branches: [gh/**/base]
20+
21+
workflow_dispatch:
22+
inputs:
23+
pr_number:
24+
description: 'PR number to check labels for'
25+
required: true
26+
27+
concurrency:
28+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}-${{ github.event_name == 'workflow_dispatch' }}
29+
cancel-in-progress: true
30+
31+
jobs:
32+
check-labels:
33+
permissions:
34+
contents: read
35+
pull-requests: write
36+
name: Check labels
37+
if: github.repository_owner == 'pytorch'
38+
runs-on: ubuntu-22.04
39+
steps:
40+
- uses: actions/checkout@v3
41+
with:
42+
fetch-depth: 0
43+
- uses: actions/setup-python@v4
44+
with:
45+
python-version: '3.10'
46+
# Not the direct dependencies but the script uses trymerge
47+
- run: pip install pyyaml==6.0 rockset==1.0.3
48+
- name: Check labels
49+
env:
50+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
51+
PR_NUM: ${{ github.event.number || github.event.inputs.pr_number }}
52+
run: |
53+
set -ex
54+
python3 .github/scripts/check_labels.py --exit-non-zero "${PR_NUM}"

.github/workflows/docker-builds.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ concurrency:
2626
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}-${{ github.event_name == 'workflow_dispatch' }}
2727
cancel-in-progress: true
2828

29+
env:
30+
AWS_DEFAULT_REGION: us-east-1
31+
2932
jobs:
3033
docker-build:
3134
runs-on: [self-hosted, linux.2xlarge]

backends/arm/test/misc/test_model_evaluator.py

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,14 @@
44
# This source code is licensed under the BSD-style license found in the
55
# LICENSE file in the root directory of this source tree.
66

7-
import random
87
import tempfile
98
import unittest
109

1110
import torch
1211
from executorch.backends.arm.util.arm_model_evaluator import GenericModelEvaluator
1312

14-
random.seed(0)
15-
1613
# Create an input that is hard to compress
17-
COMPRESSION_RATIO_TEST = bytearray(random.getrandbits(8) for _ in range(1000000))
14+
COMPRESSION_RATIO_TEST = torch.rand([1024, 1024])
1815

1916

2017
def mocked_model_1(input: torch.Tensor) -> torch.Tensor:
@@ -47,20 +44,16 @@ def test_get_model_error(self):
4744

4845
def test_get_compression_ratio(self):
4946
with tempfile.NamedTemporaryFile(delete=True) as temp_bin:
50-
temp_bin.write(COMPRESSION_RATIO_TEST)
51-
52-
# As the size of the file is quite small we need to call flush()
53-
temp_bin.flush()
54-
temp_bin_name = temp_bin.name
47+
torch.save(COMPRESSION_RATIO_TEST, temp_bin)
5548

5649
example_input = torch.tensor([[1.0, 2.0, 3.0, 4.0]])
5750
evaluator = GenericModelEvaluator(
5851
"dummy_model",
5952
mocked_model_1,
6053
mocked_model_2,
6154
example_input,
62-
temp_bin_name,
55+
temp_bin.name,
6356
)
6457

6558
ratio = evaluator.get_compression_ratio()
66-
self.assertAlmostEqual(ratio, 1.0, places=2)
59+
self.assertAlmostEqual(ratio, 1.1, places=1)

backends/arm/util/arm_model_evaluator.py

Lines changed: 106 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,25 @@
44
# This source code is licensed under the BSD-style license found in the
55
# LICENSE file in the root directory of this source tree.
66

7+
import logging
78
import os
9+
import random
810
import tempfile
911
import zipfile
12+
1013
from collections import defaultdict
11-
from typing import Optional, Tuple
14+
from pathlib import Path
15+
from typing import Any, Optional, Tuple
1216

1317
import torch
18+
from torch.nn.modules import Module
19+
from torch.utils.data import DataLoader
20+
from torchvision import datasets, transforms
21+
22+
23+
# Logger for outputting progress for longer running evaluation
24+
logger = logging.getLogger(__name__)
25+
logger.setLevel(logging.INFO)
1426

1527

1628
def flatten_args(args) -> tuple | list:
@@ -28,6 +40,8 @@ def flatten_args(args) -> tuple | list:
2840

2941

3042
class GenericModelEvaluator:
43+
REQUIRES_CONFIG = False
44+
3145
def __init__(
3246
self,
3347
model_name: str,
@@ -90,7 +104,7 @@ def get_compression_ratio(self) -> float:
90104

91105
return compression_ratio
92106

93-
def evaluate(self) -> dict[any]:
107+
def evaluate(self) -> dict[Any]:
94108
model_error_dict = self.get_model_error()
95109

96110
output_metrics = {"name": self.model_name, "metrics": dict(model_error_dict)}
@@ -103,3 +117,93 @@ def evaluate(self) -> dict[any]:
103117
] = self.get_compression_ratio()
104118

105119
return output_metrics
120+
121+
122+
class MobileNetV2Evaluator(GenericModelEvaluator):
123+
REQUIRES_CONFIG = True
124+
125+
def __init__(
126+
self,
127+
model_name: str,
128+
fp32_model: Module,
129+
int8_model: Module,
130+
example_input: Tuple[torch.Tensor],
131+
tosa_output_path: str | None,
132+
batch_size: int,
133+
validation_dataset_path: str,
134+
) -> None:
135+
super().__init__(
136+
model_name, fp32_model, int8_model, example_input, tosa_output_path
137+
)
138+
139+
self.__batch_size = batch_size
140+
self.__validation_set_path = validation_dataset_path
141+
142+
@staticmethod
143+
def __load_dataset(directory: str) -> datasets.ImageFolder:
144+
directory_path = Path(directory)
145+
if not directory_path.exists():
146+
raise FileNotFoundError(f"Directory: {directory} does not exist.")
147+
148+
transform = transforms.Compose(
149+
[
150+
transforms.Resize(256),
151+
transforms.CenterCrop(224),
152+
transforms.ToTensor(),
153+
transforms.Normalize(
154+
mean=[0.484, 0.454, 0.403], std=[0.225, 0.220, 0.220]
155+
),
156+
]
157+
)
158+
return datasets.ImageFolder(directory_path, transform=transform)
159+
160+
@staticmethod
161+
def get_calibrator(training_dataset_path: str) -> DataLoader:
162+
dataset = MobileNetV2Evaluator.__load_dataset(training_dataset_path)
163+
rand_indices = random.sample(range(len(dataset)), k=1000)
164+
165+
# Return a subset of the dataset to be used for calibration
166+
return torch.utils.data.DataLoader(
167+
torch.utils.data.Subset(dataset, rand_indices),
168+
batch_size=1,
169+
shuffle=False,
170+
)
171+
172+
def __evaluate_mobilenet(self) -> Tuple[float, float]:
173+
dataset = MobileNetV2Evaluator.__load_dataset(self.__validation_set_path)
174+
loaded_dataset = DataLoader(
175+
dataset,
176+
batch_size=self.__batch_size,
177+
shuffle=False,
178+
)
179+
180+
top1_correct = 0
181+
top5_correct = 0
182+
183+
for i, (image, target) in enumerate(loaded_dataset):
184+
prediction = self.int8_model(image)
185+
top1_prediction = torch.topk(prediction, k=1, dim=1).indices
186+
top5_prediction = torch.topk(prediction, k=5, dim=1).indices
187+
188+
top1_correct += (top1_prediction == target.view(-1, 1)).sum().item()
189+
top5_correct += (top5_prediction == target.view(-1, 1)).sum().item()
190+
191+
logger.info("Iteration: {}".format((i + 1) * self.__batch_size))
192+
logger.info(
193+
"Top 1: {}".format(top1_correct / ((i + 1) * self.__batch_size))
194+
)
195+
logger.info(
196+
"Top 5: {}".format(top5_correct / ((i + 1) * self.__batch_size))
197+
)
198+
199+
top1_accuracy = top1_correct / len(dataset)
200+
top5_accuracy = top5_correct / len(dataset)
201+
202+
return top1_accuracy, top5_accuracy
203+
204+
def evaluate(self) -> dict[str, Any]:
205+
top1_correct, top5_correct = self.__evaluate_mobilenet()
206+
output = super().evaluate()
207+
208+
output["metrics"]["accuracy"] = {"top-1": top1_correct, "top-5": top5_correct}
209+
return output

0 commit comments

Comments
 (0)