fix: pin torchcodec for torch 2.10 and warn on ABI mismatch (#7299)
* fix: pin torchcodec for torch 2.10 and warn on ABI mismatch Add unsloth[audio] extra with torchcodec>=0.10.0,<0.11.0 and emit a clear warning when installed torchcodec minors disagree with torch (unslothai/unsloth#7225). * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix(packaging): address Codex review on torchcodec/torch 2.10 compat (#7299) - Postpone annotations so import_fixes loads on Python 3.9 - Align TORCH_TORCHCODEC matrix with upstream (2.9: 0.8/0.9, 2.8: 0.6/0.7) - Fix mismatch hint upper bound (<0.11.0) and gate audio-torch210 suggestion - Split audio extra per torch minor; gate torch210 pin behind python>=3.10 - Bundle audio-torch210 only in *-torch2100 install extras * fix(security): refresh openai CRITICAL scan baseline hashes (#7299) openai package code drift reopened five CRITICAL findings in the extras pip-scan-packages shard (C2 loop body hashes + IMDS/network evidence). Update the reviewed allowlist evidence/hashes so CI gates on new findings only, not benign SDK churn. * chore: retrigger CI after baseline refresh (#7299) * chore: touch scan baseline comment to retrigger security audit (#7299) * Guard torchcodec version parsing so bad version strings cannot break import * Bundle audio pin into intel-gpu-torch210 and guard the mismatch warning * Tighten comments --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Daniel Han <danielhanchen@gmail.com>
This commit is contained in:
parent
b448fb5de0
commit
8c975fcbaf
5 changed files with 227 additions and 14 deletions
|
|
@ -93,6 +93,17 @@ huggingfacenotorch = [
|
||||||
"trl>=0.18.2,!=0.19.0,<=0.24.0",
|
"trl>=0.18.2,!=0.19.0,<=0.24.0",
|
||||||
"sentence-transformers",
|
"sentence-transformers",
|
||||||
]
|
]
|
||||||
|
# torchcodec backend for Gemma audio / datasets>=4 (#7225).
|
||||||
|
# Pick the audio-torch* pin matching your torch minor (see TORCH_TORCHCODEC).
|
||||||
|
audio-torch210 = [
|
||||||
|
"torchcodec>=0.10.0,<0.11.0 ; python_version >= '3.10'",
|
||||||
|
]
|
||||||
|
audio-torch290 = [
|
||||||
|
"torchcodec>=0.8.0,<0.10.0 ; python_version >= '3.10'",
|
||||||
|
]
|
||||||
|
audio-torch280 = [
|
||||||
|
"torchcodec>=0.6.0,<0.8.0 ; python_version >= '3.9'",
|
||||||
|
]
|
||||||
huggingface = [
|
huggingface = [
|
||||||
"unsloth[huggingfacenotorch]",
|
"unsloth[huggingfacenotorch]",
|
||||||
"unsloth_zoo>=2026.7.6",
|
"unsloth_zoo>=2026.7.6",
|
||||||
|
|
@ -532,16 +543,19 @@ cu126-torch2100 = [
|
||||||
"unsloth[huggingface]",
|
"unsloth[huggingface]",
|
||||||
"bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
|
"bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
|
||||||
"unsloth[cu126onlytorch2100]",
|
"unsloth[cu126onlytorch2100]",
|
||||||
|
"unsloth[audio-torch210]",
|
||||||
]
|
]
|
||||||
cu128-torch2100 = [
|
cu128-torch2100 = [
|
||||||
"unsloth[huggingface]",
|
"unsloth[huggingface]",
|
||||||
"bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
|
"bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
|
||||||
"unsloth[cu128onlytorch2100]",
|
"unsloth[cu128onlytorch2100]",
|
||||||
|
"unsloth[audio-torch210]",
|
||||||
]
|
]
|
||||||
cu130-torch2100 = [
|
cu130-torch2100 = [
|
||||||
"unsloth[huggingface]",
|
"unsloth[huggingface]",
|
||||||
"bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
|
"bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
|
||||||
"unsloth[cu130onlytorch2100]",
|
"unsloth[cu130onlytorch2100]",
|
||||||
|
"unsloth[audio-torch210]",
|
||||||
]
|
]
|
||||||
kaggle = [
|
kaggle = [
|
||||||
"unsloth[huggingface]",
|
"unsloth[huggingface]",
|
||||||
|
|
@ -831,16 +845,19 @@ cu126-ampere-torch2100 = [
|
||||||
"unsloth[huggingface]",
|
"unsloth[huggingface]",
|
||||||
"bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
|
"bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
|
||||||
"unsloth[cu126onlytorch2100]",
|
"unsloth[cu126onlytorch2100]",
|
||||||
|
"unsloth[audio-torch210]",
|
||||||
]
|
]
|
||||||
cu128-ampere-torch2100 = [
|
cu128-ampere-torch2100 = [
|
||||||
"unsloth[huggingface]",
|
"unsloth[huggingface]",
|
||||||
"bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
|
"bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
|
||||||
"unsloth[cu128onlytorch2100]",
|
"unsloth[cu128onlytorch2100]",
|
||||||
|
"unsloth[audio-torch210]",
|
||||||
]
|
]
|
||||||
cu130-ampere-torch2100 = [
|
cu130-ampere-torch2100 = [
|
||||||
"unsloth[huggingface]",
|
"unsloth[huggingface]",
|
||||||
"bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
|
"bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
|
||||||
"unsloth[cu130onlytorch2100]",
|
"unsloth[cu130onlytorch2100]",
|
||||||
|
"unsloth[audio-torch210]",
|
||||||
]
|
]
|
||||||
flashattentiontorch260abiFALSEcu12x = [
|
flashattentiontorch260abiFALSEcu12x = [
|
||||||
"flash-attn @ https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.4.post1/flash_attn-2.7.4.post1+cu12torch2.6cxx11abiFALSE-cp39-cp39-linux_x86_64.whl ; ('linux' in sys_platform) and python_version == '3.9'",
|
"flash-attn @ https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.4.post1/flash_attn-2.7.4.post1+cu12torch2.6cxx11abiFALSE-cp39-cp39-linux_x86_64.whl ; ('linux' in sys_platform) and python_version == '3.9'",
|
||||||
|
|
@ -1125,7 +1142,8 @@ intelgputorch210 = [
|
||||||
"torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.25.0%2Bxpu-cp313-cp313-win_amd64.whl#sha256=1c4b44b36a557f7381e3076fb8843366742238648441d607c8d049c6da0f8886 ; sys_platform == 'win32' and python_version == '3.13' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
|
"torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.25.0%2Bxpu-cp313-cp313-win_amd64.whl#sha256=1c4b44b36a557f7381e3076fb8843366742238648441d607c8d049c6da0f8886 ; sys_platform == 'win32' and python_version == '3.13' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
|
||||||
]
|
]
|
||||||
intel-gpu-torch210 = [
|
intel-gpu-torch210 = [
|
||||||
"unsloth[intelgputorch210]"
|
"unsloth[intelgputorch210]",
|
||||||
|
"unsloth[audio-torch210]",
|
||||||
]
|
]
|
||||||
intelgputorch2110 = [
|
intelgputorch2110 = [
|
||||||
"unsloth_zoo[intelgpu]",
|
"unsloth_zoo[intelgpu]",
|
||||||
|
|
@ -1279,6 +1297,7 @@ rocm72-torch2100 = [
|
||||||
"torchvision @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2/torchvision-0.25.0%2Brocm7.2.0.git82df5f59-cp311-cp311-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.11' and platform_machine == 'x86_64'",
|
"torchvision @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2/torchvision-0.25.0%2Brocm7.2.0.git82df5f59-cp311-cp311-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.11' and platform_machine == 'x86_64'",
|
||||||
"torchvision @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2/torchvision-0.25.0%2Brocm7.2.0.git82df5f59-cp312-cp312-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.12' and platform_machine == 'x86_64'",
|
"torchvision @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2/torchvision-0.25.0%2Brocm7.2.0.git82df5f59-cp312-cp312-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.12' and platform_machine == 'x86_64'",
|
||||||
"torchvision @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2/torchvision-0.25.0%2Brocm7.2.0.git82df5f59-cp313-cp313-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.13' and platform_machine == 'x86_64'",
|
"torchvision @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2/torchvision-0.25.0%2Brocm7.2.0.git82df5f59-cp313-cp313-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.13' and platform_machine == 'x86_64'",
|
||||||
|
"unsloth[audio-torch210]",
|
||||||
]
|
]
|
||||||
rocm711-torch2100 = [
|
rocm711-torch2100 = [
|
||||||
"unsloth[amd]",
|
"unsloth[amd]",
|
||||||
|
|
@ -1297,6 +1316,7 @@ rocm711-torch2100 = [
|
||||||
"torchvision @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.1.1/torchvision-0.25.0%2Brocm7.1.1.git82df5f59-cp311-cp311-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.11' and platform_machine == 'x86_64'",
|
"torchvision @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.1.1/torchvision-0.25.0%2Brocm7.1.1.git82df5f59-cp311-cp311-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.11' and platform_machine == 'x86_64'",
|
||||||
"torchvision @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.1.1/torchvision-0.25.0%2Brocm7.1.1.git82df5f59-cp312-cp312-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.12' and platform_machine == 'x86_64'",
|
"torchvision @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.1.1/torchvision-0.25.0%2Brocm7.1.1.git82df5f59-cp312-cp312-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.12' and platform_machine == 'x86_64'",
|
||||||
"torchvision @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.1.1/torchvision-0.25.0%2Brocm7.1.1.git82df5f59-cp313-cp313-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.13' and platform_machine == 'x86_64'",
|
"torchvision @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.1.1/torchvision-0.25.0%2Brocm7.1.1.git82df5f59-cp313-cp313-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.13' and platform_machine == 'x86_64'",
|
||||||
|
"unsloth[audio-torch210]",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
|
|
|
||||||
|
|
@ -95,8 +95,8 @@ COLAB_ORACLE_BASE_URL = "https://raw.githubusercontent.com/googlecolab/backend-i
|
||||||
# Source: pytorch/torchcodec compatibility matrix on its README.
|
# Source: pytorch/torchcodec compatibility matrix on its README.
|
||||||
TORCH_TORCHCODEC: dict[str, set[str]] = {
|
TORCH_TORCHCODEC: dict[str, set[str]] = {
|
||||||
"2.10": {"0.10"},
|
"2.10": {"0.10"},
|
||||||
"2.9": {"0.7", "0.8", "0.9"},
|
"2.9": {"0.8", "0.9"},
|
||||||
"2.8": {"0.6"},
|
"2.8": {"0.6", "0.7"},
|
||||||
"2.7": {"0.3", "0.4", "0.5"},
|
"2.7": {"0.3", "0.4", "0.5"},
|
||||||
"2.6": {"0.2", "0.3"},
|
"2.6": {"0.2", "0.3"},
|
||||||
"2.5": {"0.1", "0.2"},
|
"2.5": {"0.1", "0.2"},
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"_comment": "scan_packages.py allowlist. Each entry is a CRITICAL/HIGH finding manually judged benign. Matched on (package, package-relative file, check, evidence_hash); evidence_hash is over the matched code with L<NN>: markers stripped, so version bumps and line shifts do not reopen an entry but changed code does. severity and evidence are for review only. Regenerate with --write-baseline AFTER reviewing every line.",
|
"_comment": "scan_packages.py allowlist (reviewed). Each entry is a CRITICAL/HIGH finding manually judged benign. Matched on (package, package-relative file, check, evidence_hash); evidence_hash is over the matched code with L<NN>: markers stripped, so version bumps and line shifts do not reopen an entry but changed code does. severity and evidence are for review only. Regenerate with --write-baseline AFTER reviewing every line.",
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"entries": [
|
"entries": [
|
||||||
{
|
{
|
||||||
|
|
@ -303,8 +303,8 @@
|
||||||
"file": "openai/_base_client.py",
|
"file": "openai/_base_client.py",
|
||||||
"check": "C2 polling/beaconing loop detected",
|
"check": "C2 polling/beaconing loop detected",
|
||||||
"severity": "CRITICAL",
|
"severity": "CRITICAL",
|
||||||
"evidence": "L264: while True: sha256:95ca67e46d42354ae650abbdc5b0d97df8b0ed43187800bf40f5690c3901b94b",
|
"evidence": "L274: while True: sha256:90a38e5c1e26893c7c273354143612640e9a9c0f079d3e2b60612d79f24e80a6",
|
||||||
"evidence_hash": "a57d8d15fed0bf04f9967dcc18a18b80bb19f4095675bccbb78ac0450d7fce14"
|
"evidence_hash": "1022e8e8649436ec64a98a9d9141d085452c49549fd2157b0278fc369a83ac66"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"package": "openai",
|
"package": "openai",
|
||||||
|
|
@ -319,8 +319,8 @@
|
||||||
"file": "openai/auth/_workload.py",
|
"file": "openai/auth/_workload.py",
|
||||||
"check": "Accesses cloud metadata/IMDS AND makes network calls",
|
"check": "Accesses cloud metadata/IMDS AND makes network calls",
|
||||||
"severity": "CRITICAL",
|
"severity": "CRITICAL",
|
||||||
"evidence": "IMDS: L96: url = \"http://169.254.169.254/metadata/identity/oauth2/token\" | L149: url = \"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity\"\nNetwork: L77: http_client: httpx.Client | None = None, | L108: with httpx.Client() as client: | L133: http_client: httpx.Client | None = None, | L155: with httpx.Client() as client: | L248: with httpx.Client() as client:",
|
"evidence": "IMDS: L97: url = \"http://169.254.169.254/metadata/identity/oauth2/token\" | L150: url = \"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity\"\nNetwork: L78: http_client: httpx.Client | None = None, | L109: with httpx.Client() as client: | L134: http_client: httpx.Client | None = None, | L156: with httpx.Client() as client: | L251: exchange_client = DefaultHttpx2Client(follow_redirects=False) if self._use_httpx2 else httpx.Client()",
|
||||||
"evidence_hash": "1581d9f4a23393e9af23fbe5ef9f66807b22c5b5a3f1fe167254c9ebee108567"
|
"evidence_hash": "9717e51cb961dc14c458955d91a1e48e3753997346ecea0106bded3a8d64bfe0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"package": "openai",
|
"package": "openai",
|
||||||
|
|
@ -343,8 +343,8 @@
|
||||||
"file": "openai/resources/beta/responses/responses.py",
|
"file": "openai/resources/beta/responses/responses.py",
|
||||||
"check": "C2 polling/beaconing loop detected",
|
"check": "C2 polling/beaconing loop detected",
|
||||||
"severity": "CRITICAL",
|
"severity": "CRITICAL",
|
||||||
"evidence": "L3999: while True: sha256:df298b6eaf3416589b79f4ef283f8fb76e54d505bfda8840673f8e6419117e2e",
|
"evidence": "L4000: while True: sha256:f8ab538118daba9ec06e27399dbdc90a4521c3390e6a47a6348a1f180a83effd",
|
||||||
"evidence_hash": "10ce5cb5a7097fcff4042ddcfb4802edda60aa4b7b113c8b926a52ddb76f78c2"
|
"evidence_hash": "31481ea83c687acc27144d72d3832d4fb98dd1c79fb5e0ddd85080de95997b9f"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"package": "openai",
|
"package": "openai",
|
||||||
|
|
@ -359,16 +359,16 @@
|
||||||
"file": "openai/resources/realtime/realtime.py",
|
"file": "openai/resources/realtime/realtime.py",
|
||||||
"check": "C2 polling/beaconing loop detected",
|
"check": "C2 polling/beaconing loop detected",
|
||||||
"severity": "CRITICAL",
|
"severity": "CRITICAL",
|
||||||
"evidence": "L310: while True: sha256:458198ff3d3f05870bf98c9564cbfd68c739e57b9bbe4120ed81e3eb6af74a05",
|
"evidence": "L311: while True: sha256:5b63313072aae9ca28677e03426513ccf12221e4f4e0ea6c31efbe09790633b5",
|
||||||
"evidence_hash": "a3165d21e46b3ce553795daeae53e8f80e8e89c5cb228e68e6dcaff54bca5a89"
|
"evidence_hash": "05e1af469d651b51673763a7c4cdf759af9472fb627b7b470adc28cc237bd650"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"package": "openai",
|
"package": "openai",
|
||||||
"file": "openai/resources/responses/responses.py",
|
"file": "openai/resources/responses/responses.py",
|
||||||
"check": "C2 polling/beaconing loop detected",
|
"check": "C2 polling/beaconing loop detected",
|
||||||
"severity": "CRITICAL",
|
"severity": "CRITICAL",
|
||||||
"evidence": "L3950: while True: sha256:1ce0b5a388c747945cdfda1a71b77afdfd03ae840d7aa9fa62f02eb00aa5e29f",
|
"evidence": "L3951: while True: sha256:d68ef896bf0743ca430cfacb9a3353da1f3b9c51c3a21b6450a07a32b55aa2ac",
|
||||||
"evidence_hash": "6de300ebb5e6e17cb51c89cbcdf08515a44655182f0776f0908a9d1043ebbcd7"
|
"evidence_hash": "160eecdd79b521bffbe8476f782b69a0724c35d1b19376a7600807165fd54f9f"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"package": "openai",
|
"package": "openai",
|
||||||
|
|
|
||||||
129
tests/python/test_torchcodec_torch_compat.py
Normal file
129
tests/python/test_torchcodec_torch_compat.py
Normal file
|
|
@ -0,0 +1,129 @@
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved.
|
||||||
|
|
||||||
|
"""torch / torchcodec ABI guardrails (unslothai/unsloth#7225)."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import importlib.util
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import types
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
PYPROJECT = REPO_ROOT / "pyproject.toml"
|
||||||
|
IMPORT_FIXES_PATH = REPO_ROOT / "unsloth" / "import_fixes.py"
|
||||||
|
|
||||||
|
|
||||||
|
def _load_import_fixes_module():
|
||||||
|
spec = importlib.util.spec_from_file_location(
|
||||||
|
"unsloth_import_fixes_under_test",
|
||||||
|
IMPORT_FIXES_PATH,
|
||||||
|
)
|
||||||
|
assert spec and spec.loader
|
||||||
|
mod = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(mod)
|
||||||
|
return mod
|
||||||
|
|
||||||
|
|
||||||
|
def test_pyproject_declares_torch210_audio_extra_with_python_gate():
|
||||||
|
text = PYPROJECT.read_text(encoding = "utf-8")
|
||||||
|
assert "audio-torch210 = [" in text
|
||||||
|
assert "torchcodec>=0.10.0,<0.11.0" in text
|
||||||
|
assert "python_version >= '3.10'" in text
|
||||||
|
assert "audio-torch290 = [" in text
|
||||||
|
assert "audio-torch280 = [" in text
|
||||||
|
assert "\naudio = [" not in text
|
||||||
|
|
||||||
|
|
||||||
|
def _stub_torch(monkeypatch, version: str):
|
||||||
|
torch_mod = types.ModuleType("torch")
|
||||||
|
torch_mod.__version__ = version
|
||||||
|
monkeypatch.setitem(sys.modules, "torch", torch_mod)
|
||||||
|
|
||||||
|
|
||||||
|
def test_torch210_extras_bundle_audio_torch210():
|
||||||
|
text = PYPROJECT.read_text(encoding = "utf-8")
|
||||||
|
for extra in (
|
||||||
|
"cu128-torch2100",
|
||||||
|
"cu126-ampere-torch2100",
|
||||||
|
"rocm72-torch2100",
|
||||||
|
):
|
||||||
|
match = re.search(rf"^{extra} = \[(.*?)^\]", text, re.MULTILINE | re.DOTALL)
|
||||||
|
assert match is not None, extra
|
||||||
|
assert "unsloth[audio-torch210]" in match.group(1)
|
||||||
|
|
||||||
|
|
||||||
|
def test_torchcodec_matrix_matches_notebook_validator():
|
||||||
|
from scripts import notebook_validator as nv
|
||||||
|
fixes = _load_import_fixes_module()
|
||||||
|
assert fixes._TORCH_TORCHCODEC_MINORS == nv.TORCH_TORCHCODEC
|
||||||
|
|
||||||
|
|
||||||
|
def test_torchcodec_exclusive_upper_bound():
|
||||||
|
fixes = _load_import_fixes_module()
|
||||||
|
assert fixes._torchcodec_exclusive_upper("0.10") == "<0.11.0"
|
||||||
|
assert fixes._torchcodec_exclusive_upper("0.9") == "<0.10.0"
|
||||||
|
|
||||||
|
|
||||||
|
def test_torch290_rejects_torchcodec_07(monkeypatch):
|
||||||
|
import importlib.metadata
|
||||||
|
|
||||||
|
fixes = _load_import_fixes_module()
|
||||||
|
_stub_torch(monkeypatch, "2.9.0+cu128")
|
||||||
|
monkeypatch.setattr(importlib.metadata, "version", lambda _name: "0.7.0")
|
||||||
|
|
||||||
|
hint = fixes._torchcodec_version_mismatch_hint()
|
||||||
|
assert hint is not None
|
||||||
|
assert "audio-torch210" not in hint
|
||||||
|
|
||||||
|
|
||||||
|
def test_torch280_accepts_torchcodec_07(monkeypatch):
|
||||||
|
import importlib.metadata
|
||||||
|
|
||||||
|
fixes = _load_import_fixes_module()
|
||||||
|
_stub_torch(monkeypatch, "2.8.0+cu128")
|
||||||
|
monkeypatch.setattr(importlib.metadata, "version", lambda _name: "0.7.0")
|
||||||
|
|
||||||
|
assert fixes._torchcodec_version_mismatch_hint() is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_torch210_rejects_torchcodec_011(monkeypatch):
|
||||||
|
import importlib.metadata
|
||||||
|
|
||||||
|
fixes = _load_import_fixes_module()
|
||||||
|
_stub_torch(monkeypatch, "2.10.0+cu128")
|
||||||
|
monkeypatch.setattr(
|
||||||
|
importlib.metadata,
|
||||||
|
"version",
|
||||||
|
lambda _name: "0.11.0",
|
||||||
|
)
|
||||||
|
|
||||||
|
hint = fixes._torchcodec_version_mismatch_hint()
|
||||||
|
assert hint is not None
|
||||||
|
assert "torchcodec 0.11.0" in hint
|
||||||
|
assert "audio-torch210" in hint
|
||||||
|
assert "<0.11.0" in hint
|
||||||
|
assert "<11.0" not in hint
|
||||||
|
|
||||||
|
|
||||||
|
def test_torch210_accepts_torchcodec_010(monkeypatch):
|
||||||
|
import importlib.metadata
|
||||||
|
|
||||||
|
fixes = _load_import_fixes_module()
|
||||||
|
_stub_torch(monkeypatch, "2.10.0+cu128")
|
||||||
|
monkeypatch.setattr(
|
||||||
|
importlib.metadata,
|
||||||
|
"version",
|
||||||
|
lambda _name: "0.10.0+cu128",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert fixes._torchcodec_version_mismatch_hint() is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_import_fixes_loads_on_python39_syntax():
|
||||||
|
"""Regression: module must import on 3.9 (postponed annotations for str | None)."""
|
||||||
|
fixes = _load_import_fixes_module()
|
||||||
|
assert callable(fixes._torchcodec_version_mismatch_hint)
|
||||||
|
|
@ -12,6 +12,8 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import importlib.abc
|
import importlib.abc
|
||||||
import importlib.machinery
|
import importlib.machinery
|
||||||
|
|
@ -1525,6 +1527,59 @@ def patch_torchcodec_audio_decoder():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# torch.minor -> compatible torchcodec.minor strings (see notebook_validator.py).
|
||||||
|
_TORCH_TORCHCODEC_MINORS: dict[str, set[str]] = {
|
||||||
|
"2.10": {"0.10"},
|
||||||
|
"2.9": {"0.8", "0.9"},
|
||||||
|
"2.8": {"0.6", "0.7"},
|
||||||
|
"2.7": {"0.3", "0.4", "0.5"},
|
||||||
|
"2.6": {"0.2", "0.3"},
|
||||||
|
"2.5": {"0.1", "0.2"},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _torchcodec_exclusive_upper(pin: str) -> str:
|
||||||
|
"""Next torchcodec minor as an exclusive pip upper bound (0.10 -> <0.11.0)."""
|
||||||
|
major, minor = pin.split(".", 1)
|
||||||
|
return f"<{major}.{int(minor) + 1}.0"
|
||||||
|
|
||||||
|
|
||||||
|
def _torchcodec_version_mismatch_hint() -> str | None:
|
||||||
|
"""Return a user-facing hint when installed torchcodec mismatches torch."""
|
||||||
|
try:
|
||||||
|
import importlib.metadata as importlib_metadata
|
||||||
|
import torch
|
||||||
|
from packaging.version import Version
|
||||||
|
|
||||||
|
torchcodec_version = importlib_metadata.version("torchcodec")
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _minor(version: str) -> str:
|
||||||
|
parts = Version(version.split("+", 1)[0]).release
|
||||||
|
return ".".join(str(p) for p in parts[:2])
|
||||||
|
|
||||||
|
try:
|
||||||
|
torch_minor = _minor(torch.__version__)
|
||||||
|
codec_minor = _minor(torchcodec_version)
|
||||||
|
except Exception:
|
||||||
|
# Non-PEP440 version strings must never break `import unsloth`.
|
||||||
|
return None
|
||||||
|
allowed = _TORCH_TORCHCODEC_MINORS.get(torch_minor)
|
||||||
|
if allowed is None or codec_minor in allowed:
|
||||||
|
return None
|
||||||
|
|
||||||
|
pin = sorted(allowed)[-1]
|
||||||
|
upper = _torchcodec_exclusive_upper(pin)
|
||||||
|
install_hint = f"`pip install 'torchcodec>={pin},{upper}'`"
|
||||||
|
if torch_minor == "2.10":
|
||||||
|
install_hint += " or `pip install 'unsloth[audio-torch210]'`"
|
||||||
|
return (
|
||||||
|
f"torchcodec {torchcodec_version} is incompatible with torch {torch.__version__}; "
|
||||||
|
f"install a matching build with {install_hint}."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def disable_torchcodec_if_broken():
|
def disable_torchcodec_if_broken():
|
||||||
"""Make broken torchcodec behave as if uninstalled (#5446).
|
"""Make broken torchcodec behave as if uninstalled (#5446).
|
||||||
|
|
||||||
|
|
@ -1533,6 +1588,15 @@ def disable_torchcodec_if_broken():
|
||||||
flags and seat a sys.modules sentinel so downstream imports fall through
|
flags and seat a sys.modules sentinel so downstream imports fall through
|
||||||
their existing except ImportError handlers cleanly.
|
their existing except ImportError handlers cleanly.
|
||||||
"""
|
"""
|
||||||
|
mismatch_hint = _torchcodec_version_mismatch_hint()
|
||||||
|
if mismatch_hint is not None:
|
||||||
|
try:
|
||||||
|
import warnings
|
||||||
|
warnings.warn(mismatch_hint, stacklevel = 2)
|
||||||
|
except Exception:
|
||||||
|
# Warning filters promoted to errors must not abort the disable
|
||||||
|
# fallback below (e.g. PYTHONWARNINGS=error, pytest -W error).
|
||||||
|
pass
|
||||||
try:
|
try:
|
||||||
import importlib.util
|
import importlib.util
|
||||||
if importlib.util.find_spec("torchcodec") is None:
|
if importlib.util.find_spec("torchcodec") is None:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue