Skip to content

Commit cb11474

Browse files
committed
Update
[ghstack-poisoned]
2 parents fb59958 + ee95f0b commit cb11474

65 files changed

Lines changed: 1712 additions & 261 deletions

File tree

Some content is hidden

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

‎.ci/scripts/tests/vulkan/test_export_vulkan_llm.py‎

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@
1313
import shlex
1414
import subprocess
1515
import sys
16+
import time
17+
from datetime import datetime, timezone
18+
from http.server import BaseHTTPRequestHandler, HTTPServer
1619
from pathlib import Path
20+
from threading import Thread
1721
from types import SimpleNamespace
1822

1923
import pytest
@@ -128,6 +132,158 @@ def test_existing_hf_download_accepts_matching_hash(tmp_path, monkeypatch):
128132
) == str(tmp_path)
129133

130134

135+
@pytest.mark.parametrize(
136+
"headers, expected_wait",
137+
[
138+
(
139+
{
140+
"RateLimit": '"api";r=0;t=36',
141+
"RateLimit-Policy": '"fixed window";"api";q=500;w=300',
142+
},
143+
37,
144+
),
145+
({"Retry-After": "36"}, 37),
146+
({"Retry-After": "Mon, 19 Oct 2026 17:50:36 GMT"}, 37),
147+
({"RateLimit": '"api";r=0;t=36', "Retry-After": "50"}, 51),
148+
({"RateLimit": '"api";r=0;t=36', "Retry-After": "invalid"}, 37),
149+
({"RateLimit": '"api";r=10;t=900', "Retry-After": "36"}, 37),
150+
],
151+
)
152+
def test_hf_snapshot_retries_rate_limited_repo_tree(
153+
tmp_path, monkeypatch, capsys, headers, expected_wait
154+
):
155+
from huggingface_hub import HfApi
156+
157+
module = load_existing_module("examples/models/llama/hf_download.py")
158+
requests = []
159+
calls = []
160+
waits = []
161+
monkeypatch.setenv("NO_PROXY", "127.0.0.1,localhost")
162+
monkeypatch.setattr(time, "sleep", waits.append)
163+
monkeypatch.setattr(
164+
time,
165+
"time",
166+
lambda: datetime(2026, 10, 19, 17, 50, tzinfo=timezone.utc).timestamp(),
167+
)
168+
169+
class Handler(BaseHTTPRequestHandler):
170+
def do_GET(self):
171+
requests.append(self.path)
172+
self.send_response(429 if len(requests) == 1 else 200)
173+
if len(requests) == 1:
174+
for name, value in headers.items():
175+
self.send_header(name, value)
176+
self.send_header("Content-Type", "application/json")
177+
self.end_headers()
178+
self.wfile.write(
179+
b'{"error": "Rate limited"}' if len(requests) == 1 else b"[]"
180+
)
181+
182+
def log_message(self, *args):
183+
pass
184+
185+
server = HTTPServer(("127.0.0.1", 0), Handler)
186+
thread = Thread(target=server.serve_forever)
187+
thread.start()
188+
api = HfApi(endpoint=f"http://127.0.0.1:{server.server_port}")
189+
190+
def snapshot(**kwargs):
191+
calls.append(kwargs)
192+
list(
193+
api.list_repo_tree(
194+
kwargs["repo_id"],
195+
revision=kwargs["revision"],
196+
token=kwargs["token"],
197+
recursive=True,
198+
)
199+
)
200+
return str(tmp_path)
201+
202+
monkeypatch.setattr(module, "snapshot_download", snapshot)
203+
try:
204+
assert module.download_hf_snapshot("test/model", "r" * 40) == str(tmp_path)
205+
finally:
206+
server.shutdown()
207+
server.server_close()
208+
thread.join()
209+
assert len(requests) == 2 and requests[0] == requests[1]
210+
assert (
211+
calls
212+
== [
213+
{
214+
"repo_id": "test/model",
215+
"revision": "r" * 40,
216+
"allow_patterns": ["*.safetensors", "*.json"],
217+
"token": False,
218+
}
219+
]
220+
* 2
221+
)
222+
assert waits == [expected_wait]
223+
output = capsys.readouterr()
224+
assert output.out == ""
225+
assert str(expected_wait) in output.err
226+
227+
228+
@pytest.mark.parametrize(
229+
"headers", [{}, {"Retry-After": "invalid"}, {"RateLimit": '"api";r=10;t=900'}]
230+
)
231+
def test_hf_snapshot_rate_limit_retry_exhaustion(monkeypatch, headers):
232+
import httpx
233+
from huggingface_hub.errors import HfHubHTTPError
234+
235+
module = load_existing_module("examples/models/llama/hf_download.py")
236+
response = httpx.Response(
237+
429,
238+
headers=headers,
239+
request=httpx.Request("GET", "https://example.com"),
240+
)
241+
error = HfHubHTTPError("Rate limited", response=response)
242+
calls = []
243+
waits = []
244+
245+
def rate_limited(**kwargs):
246+
calls.append(kwargs)
247+
raise error
248+
249+
monkeypatch.setattr(module, "snapshot_download", rate_limited)
250+
monkeypatch.setattr(time, "sleep", waits.append)
251+
with pytest.raises(HfHubHTTPError) as raised:
252+
module.download_hf_snapshot("test/model", "r" * 40)
253+
assert raised.value is error
254+
assert len(calls) == 6
255+
assert waits == [60, 120, 240, 300, 300]
256+
257+
258+
@pytest.mark.parametrize("status", [401, 404, 503, None])
259+
def test_hf_snapshot_does_not_retry_other_http_errors(monkeypatch, status):
260+
import httpx
261+
from huggingface_hub.errors import HfHubHTTPError
262+
263+
module = load_existing_module("examples/models/llama/hf_download.py")
264+
response = httpx.Response(
265+
status or 429,
266+
request=httpx.Request("GET", "https://example.com"),
267+
)
268+
error = HfHubHTTPError("Download failed", response=response)
269+
if status is None:
270+
error.response = None
271+
calls = []
272+
waits = []
273+
274+
def failed(**kwargs):
275+
calls.append(kwargs)
276+
raise error
277+
278+
monkeypatch.setattr(module, "snapshot_download", failed)
279+
monkeypatch.setattr(time, "sleep", waits.append)
280+
with pytest.raises(HfHubHTTPError) as raised:
281+
module.download_hf_snapshot("test/model", "r" * 40)
282+
assert raised.value is error
283+
assert len(calls) == 1
284+
assert waits == []
285+
286+
131287
def test_snapshot_cli_exposes_pinned_download_and_checksum(
132288
tmp_path, monkeypatch, capsys
133289
):

‎.ci/scripts/utils.sh‎

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/bin/bash
22
# Copyright (c) Meta Platforms, Inc. and affiliates.
33
# All rights reserved.
4-
# Copyright 2024 Arm Limited and/or its affiliates.
4+
# Copyright 2024,2026 Arm Limited and/or its affiliates.
55
#
66
# This source code is licensed under the BSD-style license found in the
77
# LICENSE file in the root directory of this source tree.
@@ -50,8 +50,9 @@ dedupe_macos_loader_path_rpaths() {
5050
fi
5151

5252
local torch_lib_dir
53+
local python_executable=${PYTHON_EXECUTABLE:-python}
5354
pushd ..
54-
torch_lib_dir=$(python -c "import importlib.util; print(importlib.util.find_spec('torch').submodule_search_locations[0])")/lib
55+
torch_lib_dir=$("${python_executable}" -c "import importlib.util; print(importlib.util.find_spec('torch').submodule_search_locations[0])")/lib
5556
popd
5657

5758
if [[ -z "${torch_lib_dir}" || ! -d "${torch_lib_dir}" ]]; then
@@ -83,12 +84,16 @@ dedupe_macos_loader_path_rpaths() {
8384
}
8485

8586
install_domains() {
87+
local python_executable=${PYTHON_EXECUTABLE:-python}
8688
echo "Install torchvision and torchaudio"
87-
pip install --no-build-isolation --user "git+https://github.com/pytorch/audio.git@${TORCHAUDIO_VERSION}"
88-
pip install --no-build-isolation --user "git+https://github.com/pytorch/vision.git@${TORCHVISION_VERSION}"
89+
"${python_executable}" -m pip install --no-build-isolation \
90+
"git+https://github.com/pytorch/audio.git@${TORCHAUDIO_VERSION}"
91+
"${python_executable}" -m pip install --no-build-isolation \
92+
"git+https://github.com/pytorch/vision.git@${TORCHVISION_VERSION}"
8993
}
9094

9195
install_pytorch_and_domains() {
96+
local python_executable=${PYTHON_EXECUTABLE:-python}
9297
pushd .ci/docker || return
9398
TORCH_VERSION=$(cat ci_commit_pins/pytorch.txt)
9499
popd || return
@@ -101,9 +106,9 @@ install_pytorch_and_domains() {
101106

102107
local system_name=$(uname)
103108
if [[ "${system_name}" == "Darwin" ]]; then
104-
local platform=$(python -c 'import sysconfig; import platform; v=platform.mac_ver()[0].split(".")[0]; platform=sysconfig.get_platform().split("-"); platform[1]=f"{v}_0"; print("_".join(platform))')
109+
local platform=$("${python_executable}" -c 'import sysconfig; import platform; v=platform.mac_ver()[0].split(".")[0]; platform=sysconfig.get_platform().split("-"); platform[1]=f"{v}_0"; print("_".join(platform))')
105110
fi
106-
local python_version=$(python -c 'import platform; v=platform.python_version_tuple(); print(f"{v[0]}{v[1]}")')
111+
local python_version=$("${python_executable}" -c 'import platform; v=platform.python_version_tuple(); print(f"{v[0]}{v[1]}")')
107112
local torch_release=$(cat version.txt)
108113
# Download key must match the upload key below (basename of dist/*.whl,
109114
# which always carries the build's resolved +gitHASH). Branch-ref pins
@@ -118,7 +123,7 @@ install_pytorch_and_domains() {
118123
# of the Docker image
119124
local torch_wheel_not_found=0
120125
if [[ "${system_name}" == "Darwin" ]]; then
121-
pip install "${cached_torch_wheel}" || torch_wheel_not_found=1
126+
"${python_executable}" -m pip install "${cached_torch_wheel}" || torch_wheel_not_found=1
122127
else
123128
torch_wheel_not_found=1
124129
fi
@@ -138,14 +143,14 @@ install_pytorch_and_domains() {
138143
# Keep in sync with pytorch/pyproject.toml [build-system].requires.
139144
local build_venv=/tmp/pytorch-build-venv
140145
rm -rf "${build_venv}"
141-
python -m venv --system-site-packages "${build_venv}"
146+
"${python_executable}" -m venv --system-site-packages "${build_venv}"
142147
"${build_venv}/bin/pip" install build "scikit-build-core>=1.0" ninja \
143148
"packaging>=24.2" "typing-extensions>=4.10.0" pyyaml six numpy
144149
USE_DISTRIBUTED=1 "${build_venv}/bin/python" -m build --wheel --no-isolation
145150
rm -rf "${build_venv}"
146-
pip install "$(echo dist/*.whl)"
151+
"${python_executable}" -m pip install "$(echo dist/*.whl)"
147152
# A build with no BLAS succeeds silently, so check rather than assume.
148-
(cd / && python -c "
153+
(cd / && "${python_executable}" -c "
149154
import torch
150155
assert torch._C.has_lapack, 'built without LAPACK'
151156
torch.linalg.qr(torch.randn(4, 4))

‎.ci/scripts/wheel/test_cuda_linux.py‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,27 @@ def test_cuda_libraries_resolve_relatively() -> None:
143143
print(f"✓ {name} resolves the CUDA runtime relatively ({relative[0]})")
144144

145145

146+
def test_no_library_needs_cuda_torch() -> None:
147+
"""No shipped library may record a dependency on PyTorch's own CUDA libraries.
148+
149+
The CUDA delegate needs only the CUDA runtime. A library that names libtorch_cuda or
150+
libc10_cuda cannot load next to a CPU-only torch, so the whole wheel then fails to import
151+
there even though nothing in it calls into those libraries.
152+
"""
153+
package_dir = _package_dir()
154+
cuda_torch = {"libtorch_cuda.so", "libc10_cuda.so"}
155+
offenders = {}
156+
for library in test_shared_libraries._shipped_shared_objects(package_dir):
157+
needed = test_shared_libraries._recorded_dependencies(library) & cuda_torch
158+
if needed:
159+
offenders[str(library.relative_to(package_dir))] = sorted(needed)
160+
assert not offenders, (
161+
f"these libraries depend on PyTorch's CUDA libraries, so the wheel cannot be imported "
162+
f"with a CPU-only torch: {offenders}"
163+
)
164+
print("✓ no shipped library depends on PyTorch's CUDA libraries")
165+
166+
146167
def _row_architectures() -> list[str]:
147168
"""The architectures this row claims, from the same script the build uses.
148169
@@ -586,6 +607,7 @@ def test_a_model_runs_through_the_delegate() -> None:
586607
test_cuda_libraries_are_shipped()
587608
test_cuda_runtime_is_declared()
588609
test_cuda_libraries_resolve_relatively()
610+
test_no_library_needs_cuda_torch()
589611
test_device_code_covers_the_row()
590612
test_portable_device_code_is_present()
591613
test_the_delegate_registers()

‎.github/workflows/android-release-artifacts.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ jobs:
168168
source backends/qualcomm/scripts/qnn_config.sh
169169
export QNN_SDK_ROOT="/tmp/qnn/${QNN_VERSION}"
170170
export ANDROID_ABIS=arm64-v8a
171-
GRADLE_ARGS+=" -DqnnVersion=2.50.0"
171+
GRADLE_ARGS+=" -DqnnVersion=${QNN_VERSION%.*}"
172172
fi
173173
174174
# Build AAR Package

0 commit comments

Comments
 (0)