torchcodec: warn when torch outruns the last lockstep row
The ABI-stable branch clears torch >= 2.11 paired with torchcodec >= 0.12, but everything else fell through to the lockstep matrix, which has no row past torch 2.11. So torch 2.12 or 2.13 with torchcodec 0.10 or 0.11 hit `allowed is None` and returned no hint at all, even though upstream pins 0.11 to torch 2.11 exactly and 0.10 to torch 2.10 exactly. torchcodec ships no `Requires-Dist: torch`, so pip cannot catch it either and the user only sees a raw "Could not load libtorchcodec" at first use. Split the lookup into three cases: a torch minor below the table stays silent as before, a torch minor with a row keeps the existing per-minor pin and audio-torch2xx extra hint, and a torch minor at or past the ABI-stable floor with no row now points at `torchcodec>=0.12.0`. Mirror the same three-way split in scripts/notebook_validator.py's R-INST-004 so the two stay in step, and cover both with tests plus a regression test that torch below the table still produces no finding.
This commit is contained in:
parent
8ca1764e46
commit
6df6f82557
3 changed files with 81 additions and 9 deletions
|
|
@ -630,7 +630,22 @@ def rule_inst_004_torchcodec_torch(
|
|||
c_minor = version_minor(codec_v)
|
||||
allowed = TORCH_TORCHCODEC.get(t_minor)
|
||||
if allowed is None:
|
||||
return findings # unknown torch minor — don't flag
|
||||
if cmp_versions(torch_v, TORCHCODEC_ABI_STABLE_TORCH) < 0:
|
||||
return findings # torch older than the table — don't flag
|
||||
# Torch at or past the ABI-stable floor with a pre-0.12 torchcodec: the
|
||||
# ABI-stable branch above already returned for 0.12+, so this pin is a
|
||||
# legacy build locked to an older torch minor and cannot load.
|
||||
findings.append(
|
||||
Finding(
|
||||
rule = "R-INST-004",
|
||||
file = file,
|
||||
cell = cell_idx,
|
||||
severity = "error",
|
||||
message = f"torch=={torch_v} (minor {t_minor}) is incompatible with torchcodec=={codec_v} (minor {c_minor}); torchcodec <{TORCHCODEC_ABI_STABLE_CODEC} is built against a single older torch minor",
|
||||
hint = f"pin `torchcodec>={TORCHCODEC_ABI_STABLE_CODEC}.0` (the ABI-stable line, which targets torch >={TORCHCODEC_ABI_STABLE_TORCH})",
|
||||
)
|
||||
)
|
||||
return findings
|
||||
if c_minor not in allowed:
|
||||
findings.append(
|
||||
Finding(
|
||||
|
|
|
|||
|
|
@ -228,6 +228,53 @@ def test_torch210_still_rejects_abi_stable_torchcodec(monkeypatch):
|
|||
assert "audio-torch210" in hint
|
||||
|
||||
|
||||
def test_torch_past_last_lockstep_row_rejects_legacy_torchcodec(monkeypatch):
|
||||
"""torch newer than the last lockstep row only pairs with the 0.12+ line.
|
||||
|
||||
torchcodec 0.11 is pinned to torch 2.11 exactly (upstream compatibility
|
||||
table), so torch 2.12/2.13 with a pre-0.12 codec must still be reported even
|
||||
though the matrix has no row for those torch minors.
|
||||
"""
|
||||
import importlib.metadata
|
||||
|
||||
fixes = _load_import_fixes_module()
|
||||
for torch_version in ("2.12.1+cu130", "2.13.0"):
|
||||
for codec_version in ("0.11.1", "0.10.0"):
|
||||
_stub_torch(monkeypatch, torch_version)
|
||||
monkeypatch.setattr(importlib.metadata, "version", lambda _name, _v = codec_version: _v)
|
||||
hint = fixes._torchcodec_version_mismatch_hint()
|
||||
assert hint is not None, f"{torch_version} + torchcodec {codec_version} must warn"
|
||||
assert "torchcodec>=0.12.0" in hint
|
||||
# No audio-torch2xx extra exists for these minors, so none is offered.
|
||||
assert "unsloth[audio-torch" not in hint
|
||||
|
||||
|
||||
def test_torch_below_the_table_stays_silent(monkeypatch):
|
||||
"""A torch minor older than the matrix keeps the original no-opinion behaviour."""
|
||||
import importlib.metadata
|
||||
|
||||
fixes = _load_import_fixes_module()
|
||||
_stub_torch(monkeypatch, "2.4.0")
|
||||
monkeypatch.setattr(importlib.metadata, "version", lambda _name: "0.0.3")
|
||||
assert fixes._torchcodec_version_mismatch_hint() is None
|
||||
|
||||
|
||||
def test_notebook_validator_rejects_legacy_codec_past_last_lockstep_row():
|
||||
"""The mirrored notebook rule must flag the same pairing as import_fixes."""
|
||||
nv = _load_notebook_validator_module()
|
||||
|
||||
cell = '!pip install --no-deps "torch==2.12.1" "torchcodec==0.11.1"'
|
||||
findings = nv.rule_inst_004_torchcodec_torch(cell, {}, "nb.ipynb", 0)
|
||||
assert len(findings) == 1
|
||||
assert findings[0].rule == "R-INST-004"
|
||||
assert findings[0].severity == "error"
|
||||
assert "torchcodec>=0.12.0" in findings[0].hint
|
||||
|
||||
# Torch older than the table is still out of scope.
|
||||
old = '!pip install --no-deps "torch==2.4.0" "torchcodec==0.0.3"'
|
||||
assert nv.rule_inst_004_torchcodec_torch(old, {}, "nb.ipynb", 0) == []
|
||||
|
||||
|
||||
def test_notebook_validator_allows_abi_stable_pairing():
|
||||
"""R-INST-004 is an error-severity rule: it must not fire on torch 2.11 + 0.12+."""
|
||||
nv = _load_notebook_validator_module()
|
||||
|
|
|
|||
|
|
@ -1601,15 +1601,25 @@ def _torchcodec_version_mismatch_hint() -> str | None:
|
|||
torch_minor = ".".join(str(p) for p in torch_release)
|
||||
codec_minor = ".".join(str(p) for p in codec_release)
|
||||
allowed = _TORCH_TORCHCODEC_MINORS.get(torch_minor)
|
||||
if allowed is None or codec_minor in allowed:
|
||||
if allowed is None:
|
||||
# No lockstep row for this torch minor. Torch older than the table is
|
||||
# out of scope, so stay silent. Torch at or past the ABI-stable floor
|
||||
# only pairs with the open-ended line (>= 0.12), and the early return
|
||||
# above already cleared those, so whatever reaches here is a legacy
|
||||
# single-minor codec built for an older torch: point at the 0.12+ line.
|
||||
if torch_release < _TORCHCODEC_ABI_STABLE_TORCH:
|
||||
return None
|
||||
abi_pin = ".".join(str(p) for p in _TORCHCODEC_ABI_STABLE_CODEC)
|
||||
install_hint = f"`pip install 'torchcodec>={abi_pin}.0'`"
|
||||
elif codec_minor in allowed:
|
||||
return None
|
||||
|
||||
pin = sorted(allowed)[-1]
|
||||
upper = _torchcodec_exclusive_upper(pin)
|
||||
install_hint = f"`pip install 'torchcodec>={pin},{upper}'`"
|
||||
extra = _TORCH_TORCHCODEC_EXTRAS.get(torch_minor)
|
||||
if extra is not None:
|
||||
install_hint += f" or `pip install 'unsloth[{extra}]'`"
|
||||
else:
|
||||
pin = sorted(allowed)[-1]
|
||||
upper = _torchcodec_exclusive_upper(pin)
|
||||
install_hint = f"`pip install 'torchcodec>={pin},{upper}'`"
|
||||
extra = _TORCH_TORCHCODEC_EXTRAS.get(torch_minor)
|
||||
if extra is not None:
|
||||
install_hint += f" or `pip install 'unsloth[{extra}]'`"
|
||||
return (
|
||||
f"torchcodec {torchcodec_version} is incompatible with torch {torch.__version__}; "
|
||||
f"install a matching build with {install_hint}."
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue