|
| 1 | +#!/bin/bash |
| 2 | +# === CI Wheel Build & Test Script === |
| 3 | + |
| 4 | +# Exit immediately on error, print each command, and capture all output to build.log |
| 5 | +set -e |
| 6 | +set -x |
| 7 | +exec > >(tee -i build.log) 2>&1 |
| 8 | + |
| 9 | +# Save repo root |
| 10 | +REPO_ROOT=$(pwd) |
| 11 | + |
| 12 | +# ---------------------------- |
| 13 | +# Dynamically create script_qnn_wheel_test.py |
| 14 | +# ---------------------------- |
| 15 | +cat > "/tmp/script_qnn_wheel_test.py" << 'EOF' |
| 16 | +# pyre-ignore-all-errors |
| 17 | +import argparse |
| 18 | +
|
| 19 | +import torch |
| 20 | +from executorch.backends.qualcomm.quantizer.quantizer import QnnQuantizer |
| 21 | +from executorch.backends.qualcomm.utils.utils import ( |
| 22 | + generate_htp_compiler_spec, |
| 23 | + generate_qnn_executorch_compiler_spec, |
| 24 | + get_soc_to_chipset_map, |
| 25 | + to_edge_transform_and_lower_to_qnn, |
| 26 | +) |
| 27 | +from executorch.exir.backend.utils import format_delegated_graph |
| 28 | +from executorch.examples.models.model_factory import EagerModelFactory |
| 29 | +from executorch.exir.capture._config import ExecutorchBackendConfig |
| 30 | +from executorch.extension.export_util.utils import save_pte_program |
| 31 | +from torchao.quantization.pt2e.quantize_pt2e import convert_pt2e, prepare_pt2e, prepare_qat_pt2e |
| 32 | +
|
| 33 | +def main() -> None: |
| 34 | + parser = argparse.ArgumentParser() |
| 35 | + parser.add_argument("-f", "--output_folder", type=str, default="", help="The folder to store the exported program") |
| 36 | + parser.add_argument("--soc", type=str, default="SM8650", help="Specify the SoC model.") |
| 37 | + parser.add_argument("-q", "--quantization", choices=["ptq", "qat"], help="Run post-traininig quantization.") |
| 38 | + args = parser.parse_args() |
| 39 | +
|
| 40 | + class LinearModule(torch.nn.Module): |
| 41 | + def __init__(self): |
| 42 | + super().__init__() |
| 43 | + self.linear = torch.nn.Linear(3, 3) |
| 44 | + def forward(self, arg): |
| 45 | + return self.linear(arg) |
| 46 | + def get_example_inputs(self): |
| 47 | + return (torch.randn(3, 3),) |
| 48 | +
|
| 49 | + model = LinearModule() |
| 50 | + example_inputs = model.get_example_inputs() |
| 51 | +
|
| 52 | + if args.quantization: |
| 53 | + quantizer = QnnQuantizer() |
| 54 | + m = torch.export.export(model.eval(), example_inputs, strict=True).module() |
| 55 | + if args.quantization == "qat": |
| 56 | + m = prepare_qat_pt2e(m, quantizer) |
| 57 | + m(*example_inputs) |
| 58 | + elif args.quantization == "ptq": |
| 59 | + m = prepare_pt2e(m, quantizer) |
| 60 | + m(*example_inputs) |
| 61 | + m = convert_pt2e(m) |
| 62 | + else: |
| 63 | + m = model |
| 64 | +
|
| 65 | + use_fp16 = True if args.quantization is None else False |
| 66 | + backend_options = generate_htp_compiler_spec(use_fp16=use_fp16) |
| 67 | + compile_spec = generate_qnn_executorch_compiler_spec( |
| 68 | + soc_model=get_soc_to_chipset_map()[args.soc], |
| 69 | + backend_options=backend_options, |
| 70 | + ) |
| 71 | + delegated_program = to_edge_transform_and_lower_to_qnn(m, example_inputs, compile_spec) |
| 72 | + output_graph = format_delegated_graph(delegated_program.exported_program().graph_module) |
| 73 | + # Ensure QnnBackend is in the output graph |
| 74 | + assert "QnnBackend" in output_graph |
| 75 | + executorch_program = delegated_program.to_executorch( |
| 76 | + config=ExecutorchBackendConfig(extract_delegate_segments=False) |
| 77 | + ) |
| 78 | + save_pte_program(executorch_program, "linear", args.output_folder) |
| 79 | +
|
| 80 | +if __name__ == "__main__": |
| 81 | + main() |
| 82 | +EOF |
| 83 | + |
| 84 | +# ---------------------------- |
| 85 | +# Wheel build and .so checks |
| 86 | +# ---------------------------- |
| 87 | +echo "=== Building Wheel Package ===" |
| 88 | +source .ci/scripts/utils.sh |
| 89 | +install_executorch |
| 90 | +EXECUTORCH_BUILDING_WHEEL=1 python setup.py bdist_wheel |
| 91 | +unset EXECUTORCH_BUILDING_WHEEL |
| 92 | + |
| 93 | +WHEEL_FILE=$(ls dist/*.whl | head -n 1) |
| 94 | +echo "Found wheel: $WHEEL_FILE" |
| 95 | + |
| 96 | +PYTHON_VERSION=$1 |
| 97 | +# ---------------------------- |
| 98 | +# Check wheel does NOT contain qualcomm/sdk |
| 99 | +# ---------------------------- |
| 100 | +echo "Checking wheel does not contain qualcomm/sdk..." |
| 101 | +SDK_FILES=$(unzip -l "$WHEEL_FILE" | awk '{print $4}' | grep "executorch/backends/qualcomm/sdk" || true) |
| 102 | +if [ -n "$SDK_FILES" ]; then |
| 103 | + echo "ERROR: Wheel package contains unexpected qualcomm/sdk files:" |
| 104 | + echo "$SDK_FILES" |
| 105 | + exit 1 |
| 106 | +else |
| 107 | + echo "OK: No qualcomm/sdk files found in wheel" |
| 108 | +fi |
| 109 | + |
| 110 | +# ---------------------------- |
| 111 | +# Check .so files in the wheel |
| 112 | +# ---------------------------- |
| 113 | +echo "Checking for .so files inside the wheel..." |
| 114 | +WHEEL_SO_FILES=$(unzip -l "$WHEEL_FILE" | awk '{print $4}' | grep "executorch/backends/qualcomm/python" || true) |
| 115 | +if [ -z "$WHEEL_SO_FILES" ]; then |
| 116 | + echo "ERROR: No .so files found in wheel under executorch/backends/qualcomm/python" |
| 117 | + exit 1 |
| 118 | +else |
| 119 | + echo "Wheel contains the following .so files:" |
| 120 | + echo "$WHEEL_SO_FILES" |
| 121 | +fi |
| 122 | + |
| 123 | +# ---------------------------- |
| 124 | +# Helpers |
| 125 | +# ---------------------------- |
| 126 | +get_site_packages_dir () { |
| 127 | + local PYBIN="$1" |
| 128 | + "$PYBIN" - <<'PY' |
| 129 | +import sysconfig, sys |
| 130 | +print(sysconfig.get_paths().get("purelib") or sysconfig.get_paths().get("platlib")) |
| 131 | +PY |
| 132 | +} |
| 133 | + |
| 134 | +run_core_tests () { |
| 135 | + local PYBIN="$1" # path to python |
| 136 | + local PIPBIN="$2" # path to pip |
| 137 | + local LABEL="$3" # label to print (conda/venv) |
| 138 | + |
| 139 | + echo "=== [$LABEL] Installing wheel & deps ===" |
| 140 | + "$PIPBIN" install --upgrade pip |
| 141 | + "$PIPBIN" install "$WHEEL_FILE" |
| 142 | + "$PIPBIN" install torch=="2.9.0.dev20250906" --index-url "https://download.pytorch.org/whl/nightly/cpu" |
| 143 | + "$PIPBIN" install --pre torchao --index-url "https://download.pytorch.org/whl/nightly/cpu" |
| 144 | + |
| 145 | + echo "=== [$LABEL] Import smoke tests ===" |
| 146 | + "$PYBIN" -c "import executorch; print('executorch imported successfully')" |
| 147 | + "$PYBIN" -c "import executorch.backends.qualcomm; print('executorch.backends.qualcomm imported successfully')" |
| 148 | + |
| 149 | + echo "=== [$LABEL] List installed executorch/backends/qualcomm/python ===" |
| 150 | + local SITE_DIR |
| 151 | + SITE_DIR="$(get_site_packages_dir "$PYBIN")" |
| 152 | + local SO_DIR="$SITE_DIR/executorch/backends/qualcomm/python" |
| 153 | + ls -l "$SO_DIR" || echo "Folder does not exist!" |
| 154 | + |
| 155 | + echo "=== [$LABEL] Run export script to generate linear.pte ===" |
| 156 | + (cd "$REPO_ROOT" && "$PYBIN" "/tmp/script_qnn_wheel_test.py") |
| 157 | + |
| 158 | + if [ -f "$REPO_ROOT/linear.pte" ]; then |
| 159 | + echo "[$LABEL] Model file linear.pte successfully created" |
| 160 | + else |
| 161 | + echo "ERROR: [$LABEL] Model file linear.pte was not created" |
| 162 | + exit 1 |
| 163 | + fi |
| 164 | +} |
| 165 | + |
| 166 | +# ---------------------------- |
| 167 | +# Conda environment setup & tests |
| 168 | +# ---------------------------- |
| 169 | +echo "=== Testing in Conda env ===" |
| 170 | +TEMP_ENV_DIR=$(mktemp -d) |
| 171 | +echo "Using temporary directory for conda: $TEMP_ENV_DIR" |
| 172 | +conda create -y -p "$TEMP_ENV_DIR/env" python=$PYTHON_VERSION |
| 173 | +# derive python/pip paths inside the conda env |
| 174 | +CONDA_PY="$TEMP_ENV_DIR/env/bin/python" |
| 175 | +CONDA_PIP="$TEMP_ENV_DIR/env/bin/pip" |
| 176 | +# Some images require conda run; keep pip/python direct to simplify path math |
| 177 | +run_core_tests "$CONDA_PY" "$CONDA_PIP" "conda" |
| 178 | + |
| 179 | +# Cleanup conda env |
| 180 | +conda env remove -p "$TEMP_ENV_DIR/env" -y || true |
| 181 | +rm -rf "$TEMP_ENV_DIR" |
| 182 | + |
| 183 | +# ---------------------------- |
| 184 | +# Python venv setup & tests |
| 185 | +# ---------------------------- |
| 186 | +echo "=== Testing in Python venv ===" |
| 187 | +TEMP_VENV_DIR=$(mktemp -d) |
| 188 | +echo "Using temporary directory for venv: $TEMP_VENV_DIR" |
| 189 | +python3 -m venv "$TEMP_VENV_DIR/venv" |
| 190 | +VENV_PY="$TEMP_VENV_DIR/venv/bin/python" |
| 191 | +VENV_PIP="$TEMP_VENV_DIR/venv/bin/pip" |
| 192 | + |
| 193 | +# Ensure venv has wheel/build basics if needed |
| 194 | +"$VENV_PIP" install --upgrade pip |
| 195 | + |
| 196 | +run_core_tests "$VENV_PY" "$VENV_PIP" "venv" |
| 197 | + |
| 198 | +# Cleanup venv |
| 199 | +rm -rf "$TEMP_VENV_DIR" |
| 200 | + |
| 201 | +echo "=== All tests completed! ===" |
0 commit comments