diff --git a/.ci/scripts/test_model.ps1 b/.ci/scripts/test_model.ps1 index 2ff2e7c890d..19d70c591ac 100644 --- a/.ci/scripts/test_model.ps1 +++ b/.ci/scripts/test_model.ps1 @@ -34,7 +34,7 @@ function ExportModel-Xnnpack { [bool]$quantize ) - if $(quantize) { + if ($quantize) { python -m examples.xnnpack.aot_compiler --model_name="${MODEL_NAME}" --delegate --quantize | Write-Host $modelFile = "$($modelName)_xnnpack_q8.pte" } else { diff --git a/.ci/scripts/wheel/pre_build_script.sh b/.ci/scripts/wheel/pre_build_script.sh index 424529af864..54283bd9c0a 100755 --- a/.ci/scripts/wheel/pre_build_script.sh +++ b/.ci/scripts/wheel/pre_build_script.sh @@ -9,9 +9,26 @@ set -euxo pipefail # This script is run before building ExecuTorch binaries +# Clone nested submodules for tokenizers - this is a workaround for recursive +# submodule clone failing due to path length limitations on Windows. Eventually, +# we should update the core job in test-infra to enable long paths before +# checkout to avoid needing to do this. +pushd extension/llm/tokenizers +git submodule update --init +popd + +# On Windows, enable symlinks and re-checkout the current revision to create +# the symlinked src/ directory. This is needed to build the wheel. +UNAME_S=$(uname -s) +if [[ $UNAME_S == *"MINGW"* || $UNAME_S == *"MSYS"* ]]; then + echo "Enabling symlinks on Windows" + git config core.symlinks true + git checkout -f HEAD +fi + # Manually install build requirements because `python setup.py bdist_wheel` does # not install them. TODO(dbort): Switch to using `python -m build --wheel`, # which does install them. Though we'd need to disable build isolation to be # able to see the installed torch package. -"${GITHUB_WORKSPACE}/${REPOSITORY}/install_requirements.sh" --example +"${GITHUB_WORKSPACE}/${REPOSITORY}/install_requirements.sh" --example diff --git a/.ci/scripts/wheel/test_windows.py b/.ci/scripts/wheel/test_windows.py new file mode 100644 index 00000000000..6ae953b5df5 --- /dev/null +++ b/.ci/scripts/wheel/test_windows.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the BSD-style license found in the +# LICENSE file in the root directory of this source tree. + +from typing import List + +import torch +from executorch.backends.xnnpack.partition.xnnpack_partitioner import XnnpackPartitioner +from executorch.examples.models import Backend, Model, MODEL_NAME_TO_MODEL +from executorch.examples.models.model_factory import EagerModelFactory +from executorch.examples.xnnpack import MODEL_NAME_TO_OPTIONS +from executorch.examples.xnnpack.quantization.utils import quantize as quantize_xnn +from executorch.exir import EdgeCompileConfig, to_edge_transform_and_lower +from executorch.extension.pybindings.portable_lib import ( + _load_for_executorch_from_buffer, +) +from test_base import ModelTest + + +def test_model_xnnpack(model: Model, quantize: bool) -> None: + model_instance, example_inputs, _, _ = EagerModelFactory.create_model( + *MODEL_NAME_TO_MODEL[str(model)] + ) + + model_instance.eval() + ref_outputs = model_instance(*example_inputs) + + if quantize: + quant_type = MODEL_NAME_TO_OPTIONS[str(model)].quantization + model_instance = torch.export.export_for_training( + model_instance, example_inputs + ) + model_instance = quantize_xnn( + model_instance.module(), example_inputs, quant_type + ) + + lowered = to_edge_transform_and_lower( + torch.export.export(model_instance, example_inputs), + partitioner=[XnnpackPartitioner()], + compile_config=EdgeCompileConfig( + _check_ir_validity=False, + ), + ).to_executorch() + + loaded_model = _load_for_executorch_from_buffer(lowered.buffer) + et_outputs = loaded_model([*example_inputs]) + + if isinstance(ref_outputs, torch.Tensor): + ref_outputs = (ref_outputs,) + + assert len(ref_outputs) == len(et_outputs) + for i in range(len(ref_outputs)): + torch.testing.assert_close(ref_outputs[i], et_outputs[i], atol=1e-4, rtol=1e-5) + + +def run_tests(model_tests: List[ModelTest]) -> None: + for model_test in model_tests: + if model_test.backend == Backend.Xnnpack: + test_model_xnnpack(model_test.model, quantize=False) + else: + raise RuntimeError(f"Unsupported backend {model_test.backend}.") + + +if __name__ == "__main__": + run_tests( + model_tests=[ + ModelTest( + model=Model.Mv3, + backend=Backend.Xnnpack, + ), + ] + ) diff --git a/.ci/scripts/wheel/vc_env_helper.bat b/.ci/scripts/wheel/vc_env_helper.bat new file mode 100644 index 00000000000..d7fa2c1a596 --- /dev/null +++ b/.ci/scripts/wheel/vc_env_helper.bat @@ -0,0 +1,61 @@ +REM This is lightly modified from the torchvision Windows build logic. +REM See https://github.com/pytorch/vision/blob/main/packaging/windows/internal/vc_env_helper.bat + +@echo on + +set VC_VERSION_LOWER=17 +set VC_VERSION_UPPER=18 + +for /f "usebackq tokens=*" %%i in (`"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -legacy -products * -version [%VC_VERSION_LOWER%^,%VC_VERSION_UPPER%^) -property installationPath`) do ( + if exist "%%i" if exist "%%i\VC\Auxiliary\Build\vcvarsall.bat" ( + set "VS15INSTALLDIR=%%i" + set "VS15VCVARSALL=%%i\VC\Auxiliary\Build\vcvarsall.bat" + goto vswhere + ) +) + +:vswhere +if "%VSDEVCMD_ARGS%" == "" ( + call "%VS15VCVARSALL%" x64 || exit /b 1 +) else ( + call "%VS15VCVARSALL%" x64 %VSDEVCMD_ARGS% || exit /b 1 +) + +@echo on + +if "%CU_VERSION%" == "xpu" call "C:\Program Files (x86)\Intel\oneAPI\setvars.bat" + +set DISTUTILS_USE_SDK=1 + +set args=%1 +shift +:start +if [%1] == [] goto done +set args=%args% %1 +shift +goto start + +:done +if "%args%" == "" ( + echo Usage: vc_env_helper.bat [command] [args] + echo e.g. vc_env_helper.bat cl /c test.cpp +) + +set work_dir=%CD% +if exist setup.py ( + echo "Creating symlink..." + REM Setup a symlink to shorten the path length. + REM Note that the ET directory has to be named "executorch". + cd %GITHUB_WORKSPACE% + if not exist et\ ( + mkdir et + ) + cd et + echo Work dir: %work_dir% + if not exist executorch\ ( + mklink /d executorch %work_dir% + ) + cd executorch +) + +%args% || exit /b 1 diff --git a/.github/workflows/build-wheels-windows.yml b/.github/workflows/build-wheels-windows.yml new file mode 100644 index 00000000000..276edfb08d1 --- /dev/null +++ b/.github/workflows/build-wheels-windows.yml @@ -0,0 +1,59 @@ +name: Build Windows Wheels + +on: + pull_request: + push: + branches: + - nightly + - main + - release/* + tags: + # NOTE: Binary build pipelines should only get triggered on release candidate builds + # Release candidate tags look like: v1.11.0-rc1 + - v[0-9]+.[0-9]+.[0-9]+-rc[0-9]+ + workflow_dispatch: + +permissions: + id-token: write + contents: read + +jobs: + generate-matrix: + uses: pytorch/test-infra/.github/workflows/generate_binary_build_matrix.yml@main + with: + package-type: wheel + os: windows + test-infra-repository: pytorch/test-infra + test-infra-ref: main + with-cuda: disabled + with-rocm: disabled + python-versions: '["3.10", "3.11", "3.12"]' + + build: + needs: generate-matrix + strategy: + fail-fast: false + matrix: + include: + - repository: pytorch/executorch + pre-script: .ci\\scripts\\wheel\\pre_build_script.sh + env-script: .ci\\scripts\\wheel\\vc_env_helper.bat + post-script: .ci\\scripts\\wheel\\post_build_script.sh + smoke-test-script: .ci/scripts/wheel/test_windows.py + package-name: executorch + name: ${{ matrix.repository }} + uses: pytorch/test-infra/.github/workflows/build_wheels_windows.yml@main + with: + repository: ${{ matrix.repository }} + ref: "" + test-infra-repository: pytorch/test-infra + test-infra-ref: main + build-matrix: ${{ needs.generate-matrix.outputs.matrix }} + pre-script: ${{ matrix.pre-script }} + env-script: ${{ matrix.env-script }} + post-script: ${{ matrix.post-script }} + package-name: ${{ matrix.package-name }} + smoke-test-script: ${{ matrix.smoke-test-script }} + trigger-event: ${{ github.event_name }} + wheel-build-params: "--verbose" + submodules: true diff --git a/examples/models/__init__.py b/examples/models/__init__.py index c2212bbb218..b997ba1bf6e 100644 --- a/examples/models/__init__.py +++ b/examples/models/__init__.py @@ -44,6 +44,7 @@ def __str__(self) -> str: class Backend(str, Enum): + Xnnpack = "xnnpack" XnnpackQuantizationDelegation = "xnnpack-quantization-delegation" CoreMlExportOnly = "coreml" CoreMlExportAndTest = "coreml-test" # AOT export + test with runner diff --git a/setup.py b/setup.py index a112802f2a6..514d1af7726 100644 --- a/setup.py +++ b/setup.py @@ -142,6 +142,7 @@ def string(cls) -> str: @classmethod def write_to_python_file(cls, path: str) -> None: """Creates a file similar to PyTorch core's `torch/version.py`.""" + lines = [ "from typing import Optional", '__all__ = ["__version__", "git_version"]', @@ -773,7 +774,7 @@ def run(self): # noqa C901 # platform-specific files using InstallerBuildExt. ext_modules=[ BuiltFile( - src_dir="%CMAKE_CACHE_DIR%/third-party/flatc_proj/bin/", + src_dir="%CMAKE_CACHE_DIR%/third-party/flatc_ep/bin/", src_name="flatc", dst="executorch/data/bin/", is_executable=True, diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt index 8d3665c513c..767ac367e19 100644 --- a/third-party/CMakeLists.txt +++ b/third-party/CMakeLists.txt @@ -29,7 +29,8 @@ endif() # Otherwise, flatc will target the project's toolchain (i.e. iOS, or Android). ExternalProject_Add( flatbuffers_ep - PREFIX ${CMAKE_CURRENT_BINARY_DIR}/flatc_proj + PREFIX ${CMAKE_CURRENT_BINARY_DIR}/flatc_ep + BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/flatc_ep/src/build SOURCE_DIR ${PROJECT_SOURCE_DIR}/third-party/flatbuffers CMAKE_ARGS -DFLATBUFFERS_BUILD_FLATC=ON -DFLATBUFFERS_INSTALL=ON @@ -82,6 +83,7 @@ ExternalProject_Add( flatcc_ep PREFIX ${CMAKE_CURRENT_BINARY_DIR}/flatcc_ep SOURCE_DIR ${PROJECT_SOURCE_DIR}/third-party/flatcc + BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/flatcc_ep/src/build CMAKE_ARGS -DFLATCC_RTONLY=OFF -DFLATCC_TEST=OFF -DFLATCC_REFLECTION=OFF