From 5f3492de264474760c4e84daa64f5c24182bf321 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20de=20Kok?= Date: Mon, 14 Sep 2026 12:21:41 +0000 Subject: [PATCH 1/3] kernels: backport minimum version check --- docs/source/kernel-requirements.md | 5 ++ kernels/src/kernels/minver.py | 69 ++++++++++++++++++ kernels/src/kernels/utils.py | 2 + kernels/tests/test_minver.py | 112 +++++++++++++++++++++++++++++ 4 files changed, 188 insertions(+) create mode 100644 kernels/src/kernels/minver.py create mode 100644 kernels/tests/test_minver.py diff --git a/docs/source/kernel-requirements.md b/docs/source/kernel-requirements.md index c32347713..a1268da57 100644 --- a/docs/source/kernel-requirements.md +++ b/docs/source/kernel-requirements.md @@ -69,6 +69,11 @@ metadata. Currently the following top-level keys are supported: - `name` (`str`, required): then name of the kernel. Replacing dashes by underscores should result in the module name of the kernel. - `version` (`int`, required): the kernel version number. +- `kernels-minver` (`str`, optional): the minimum version of the `kernels` + Python library required to load the kernel (e.g. `"0.17.0"`). This key is + determined by the kernel builder from the features that the kernel uses, + it is not set by kernel authors. The `kernels` library warns when it loads + a kernel that requires a newer version than the one that is installed. - `license` (`str`, required): the kernel license in. Refer to the list of [supported license identifiers](https://huggingface.co/docs/hub/repositories-licenses). - `upstream` (`str`, optional): Git-compatible URL (passable to `git clone`) diff --git a/kernels/src/kernels/minver.py b/kernels/src/kernels/minver.py new file mode 100644 index 000000000..4884faa59 --- /dev/null +++ b/kernels/src/kernels/minver.py @@ -0,0 +1,69 @@ +import json +import warnings +from pathlib import Path + +from kernels_data import Metadata, Version +from packaging.version import InvalidVersion, parse + + +def _read_kernels_minver(variant_path: Path) -> Version | None: + """The minimum `kernels` version required by a build variant.""" + + # Note, for kernels >= 0.17, we read the version from the metadata, + # but to minimize the impact in the backport, we are reading from + # metadata.json here directly. + + try: + with open(variant_path / "metadata.json", "rb") as f: + minver = json.load(f).get("kernels-minver") + except (OSError, ValueError): + return None + + if not isinstance(minver, str): + return None + + try: + return Version.from_str(minver) + except ValueError: + return None + + +def _installed_version() -> Version | None: + """The installed `kernels` version as a numeric version.""" + # Avoid an import cycle. + from kernels import __version__ + + try: + release = parse(__version__).release + except InvalidVersion: + return None + + # packaging < 22 returns a LegacyVersion with `release = None` for + # non-PEP 440 versions instead of raising `InvalidVersion`. + if release is None: + return None + + return Version.from_str(".".join(str(part) for part in release)) + + +def _warn_if_below_minver(metadata: Metadata, variant_path: Path) -> None: + """Warn when the installed `kernels` library is older than the minimum + version required by a kernel. + """ + minver = _read_kernels_minver(variant_path) + if minver is None: + return + + installed = _installed_version() + if installed is not None and installed < minver: + # Report the verbatim installed version, not the normalized one used + # for comparison, so the message matches what `pip show` reports. + from kernels import __version__ + + warnings.warn( + f"Kernel '{metadata.name}' variant '{variant_path.name}' requires " + f"kernels>={minver}, but version {__version__} is installed. " + "The kernel may not load or work correctly; upgrade with: " + "pip install --upgrade kernels", + stacklevel=3, + ) diff --git a/kernels/src/kernels/utils.py b/kernels/src/kernels/utils.py index 03ff9ac1e..61b318616 100644 --- a/kernels/src/kernels/utils.py +++ b/kernels/src/kernels/utils.py @@ -24,6 +24,7 @@ from kernels.compat import has_torch, has_tvm_ffi from kernels.deps import validate_dependencies from kernels.lockfile import KernelLock, VariantLock +from kernels.minver import _warn_if_below_minver from kernels.status import resolve_status from kernels.variants import ( Decision, @@ -210,6 +211,7 @@ def _import_from_path(variant_path: Path, repo_info: RepoInfo | None = None) -> return loaded_kernel.module metadata = Metadata.read_from_file(variant_path / "metadata.json") + _warn_if_below_minver(metadata, variant_path) module_name = metadata.name.python_name file_path = variant_path / "__init__.py" diff --git a/kernels/tests/test_minver.py b/kernels/tests/test_minver.py new file mode 100644 index 000000000..0c8d3f413 --- /dev/null +++ b/kernels/tests/test_minver.py @@ -0,0 +1,112 @@ +import json +import re + +import pytest +from kernels_data import Metadata, Version + +import kernels +from kernels.minver import _installed_version, _warn_if_below_minver +from kernels.utils import _import_from_path, _loaded_kernels + + +def _write_variant(tmp_path, minver): + variant_dir = tmp_path / "build" / "torch28-cxx11-cu128-x86_64-linux" + variant_dir.mkdir(parents=True) + metadata = { + "id": "activation_1_cuda", + "name": "activation", + "version": 1, + "license": "Apache-2.0", + "python-depends": ["torch"], + "backend": {"type": "cuda"}, + } + if minver is not None: + metadata["kernels-minver"] = minver + (variant_dir / "metadata.json").write_text(json.dumps(metadata)) + return variant_dir + + +@pytest.mark.parametrize("minver", [None, "0.0.1"]) +def test_no_warning_when_minver_met(tmp_path, recwarn, minver): + variant_dir = _write_variant(tmp_path, minver) + metadata = Metadata.read_from_file(variant_dir / "metadata.json") + _warn_if_below_minver(metadata, variant_dir) + assert len(recwarn) == 0 + + +def test_no_warning_for_dev_version_of_required_release(tmp_path, recwarn, monkeypatch): + # A development version implements the release it leads up to, so + # `0.17.0.dev0` must satisfy a `0.17.0` requirement. + monkeypatch.setattr(kernels, "__version__", "0.17.0.dev0") + variant_dir = _write_variant(tmp_path, "0.17.0") + metadata = Metadata.read_from_file(variant_dir / "metadata.json") + _warn_if_below_minver(metadata, variant_dir) + assert len(recwarn) == 0 + + +@pytest.mark.parametrize( + "installed", + ["0.17.0.dev0", "0.17.0rc1", "0.17.0.post1", "0.17.0+cu121", "0.17"], +) +def test_installed_version_uses_release_segment(monkeypatch, installed): + monkeypatch.setattr(kernels, "__version__", installed) + assert _installed_version() == Version.from_str("0.17.0") + + +def test_installed_version_is_none_for_non_pep440_version(monkeypatch): + monkeypatch.setattr(kernels, "__version__", "0.17.0-dirty") + assert _installed_version() is None + + +def test_unparseable_installed_version_does_not_fail_load(tmp_path, recwarn, monkeypatch): + # A version that cannot be compared must not turn this advisory check into + # a hard failure. + monkeypatch.setattr(kernels, "__version__", "0.17.0-dirty") + variant_dir = _write_variant(tmp_path, "999.1.0") + metadata = Metadata.read_from_file(variant_dir / "metadata.json") + _warn_if_below_minver(metadata, variant_dir) + assert len(recwarn) == 0 + + +@pytest.mark.parametrize("minver", [42, "", "not-a-version"]) +def test_invalid_minver_is_ignored(tmp_path, recwarn, minver): + # A `kernels-minver` that cannot be parsed (e.g. written by a future + # version that changed the format) must not fail the load. + variant_dir = _write_variant(tmp_path, minver) + metadata = Metadata.read_from_file(variant_dir / "metadata.json") + _warn_if_below_minver(metadata, variant_dir) + assert len(recwarn) == 0 + + +def test_version_ordering_is_numeric_not_lexicographic(): + # `0.9 < 0.10` only holds for numeric comparison; string comparison would + # get this backwards. + assert Version.from_str("0.9") < Version.from_str("0.10") + assert Version.from_str("0.14") == Version.from_str("0.14.0") + assert Version.from_str("0.14.0") < Version.from_str("0.14.1") + + +def test_warns_when_minver_not_met(tmp_path): + variant_dir = _write_variant(tmp_path, "999.1.0") + metadata = Metadata.read_from_file(variant_dir / "metadata.json") + with pytest.warns(UserWarning, match="requires kernels>=999.1"): + _warn_if_below_minver(metadata, variant_dir) + + +def test_warning_mentions_installed_version(tmp_path): + variant_dir = _write_variant(tmp_path, "999.1.0") + metadata = Metadata.read_from_file(variant_dir / "metadata.json") + with pytest.warns(UserWarning, match=f"version {re.escape(kernels.__version__)} is installed"): + _warn_if_below_minver(metadata, variant_dir) + + +def test_import_from_path_warns_on_unmet_minver(tmp_path): + variant_dir = _write_variant(tmp_path, "999.1.0") + (variant_dir / "__init__.py").write_text("value = 42\n") + _loaded_kernels.pop(variant_dir, None) + try: + with pytest.warns(UserWarning, match="requires kernels>=999.1"): + module = _import_from_path(variant_dir) + assert module.value == 42 + finally: + _loaded_kernels.pop(variant_dir, None) From 2586472763b6fd09c17b2975ac740e267ac1690c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20de=20Kok?= Date: Mon, 14 Sep 2026 12:36:28 +0000 Subject: [PATCH 2/3] kernels: fix up arch test A test mocked CUDA capability 99.0, but ReLU would actually support it due to being compiled with PTX. So set to a lower version than the lowest supported version. --- kernels/tests/test_archs.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kernels/tests/test_archs.py b/kernels/tests/test_archs.py index fa57843d3..f521ec9e6 100644 --- a/kernels/tests/test_archs.py +++ b/kernels/tests/test_archs.py @@ -145,7 +145,8 @@ def test_get_kernel_rejects_unsupported_capability(monkeypatch): if not metadata.backend.archs: pytest.skip("kernel build does not declare archs") - monkeypatch.setattr(torch.cuda, "get_device_capability", lambda device=None: (99, 9)) + # ReLU kernel supports PTX, so we need an old version. + monkeypatch.setattr(torch.cuda, "get_device_capability", lambda device=None: (1, 0)) with pytest.raises(RuntimeError, match="does not support the current device"): get_kernel("kernels-community/relu", version=1) From 5dd30c8f5df3785dadb05b15ab1ec31c38687d93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20de=20Kok?= Date: Mon, 14 Sep 2026 12:37:41 +0000 Subject: [PATCH 3/3] Bump version to 0.16.2.dev0 --- Cargo.lock | 8 ++++---- kernel-abi-check/Cargo.toml | 2 +- kernel-builder/Cargo.toml | 6 +++--- kernels-data/Cargo.toml | 2 +- kernels-data/bindings/python/Cargo.toml | 4 ++-- kernels/pyproject.toml | 2 +- kernels/uv.lock | 4 ++-- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ad983aeed..1fe8f623f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1122,7 +1122,7 @@ dependencies = [ [[package]] name = "hf-kernel-builder" -version = "0.16.1" +version = "0.16.2-dev0" dependencies = [ "base32", "clap", @@ -1609,7 +1609,7 @@ dependencies = [ [[package]] name = "kernel-abi-check" -version = "0.16.1" +version = "0.16.2-dev0" dependencies = [ "cpp_demangle", "eyre", @@ -1623,7 +1623,7 @@ dependencies = [ [[package]] name = "kernels-data" -version = "0.16.1" +version = "0.16.2-dev0" dependencies = [ "base64", "digest 0.11.2", @@ -1642,7 +1642,7 @@ dependencies = [ [[package]] name = "kernels-data-python" -version = "0.16.1" +version = "0.16.2-dev0" dependencies = [ "kernels-data", "pyo3", diff --git a/kernel-abi-check/Cargo.toml b/kernel-abi-check/Cargo.toml index 1e0c25c08..b817d2a48 100644 --- a/kernel-abi-check/Cargo.toml +++ b/kernel-abi-check/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "kernel-abi-check" -version = "0.16.1" +version = "0.16.2-dev0" edition = "2021" description = "Check the ABI of Hub Kernels" homepage = "https://github.com/huggingface/kernel-builder" diff --git a/kernel-builder/Cargo.toml b/kernel-builder/Cargo.toml index edcaf24f4..537012427 100644 --- a/kernel-builder/Cargo.toml +++ b/kernel-builder/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hf-kernel-builder" -version = "0.16.1" +version = "0.16.2-dev0" edition = "2021" description = "Build Hugging Face Hub kernels" homepage = "https://github.com/huggingface/kernels" @@ -10,8 +10,8 @@ repository = "https://github.com/huggingface/kernels" [dependencies] base32 = "0.5" -kernel-abi-check = { path = "../kernel-abi-check", version = "0.16.1" } -kernels-data = { path = "../kernels-data", version = "0.16.1" } +kernel-abi-check = { path = "../kernel-abi-check", version = "0.16.2-dev0" } +kernels-data = { path = "../kernels-data", version = "0.16.2-dev0" } object = "0.36.7" clap = { version = "4", features = ["derive"] } clap-markdown = "0.1.5" diff --git a/kernels-data/Cargo.toml b/kernels-data/Cargo.toml index ed0922e53..ed1fc16cd 100644 --- a/kernels-data/Cargo.toml +++ b/kernels-data/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "kernels-data" -version = "0.16.1" +version = "0.16.2-dev0" edition = "2024" description = "Kernels data structures" homepage = "https://github.com/huggingface/kernels" diff --git a/kernels-data/bindings/python/Cargo.toml b/kernels-data/bindings/python/Cargo.toml index 1aeed0f7c..aa747833d 100644 --- a/kernels-data/bindings/python/Cargo.toml +++ b/kernels-data/bindings/python/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "kernels-data-python" -version = "0.16.1" +version = "0.16.2-dev0" edition = "2024" description = "Kernels data structures (Python bindings)" homepage = "https://github.com/huggingface/kernels" @@ -16,4 +16,4 @@ pyo3 = { version = "0.26", features = ["abi3", "abi3-py38"] } [dependencies.kernels-data] path = "../.." -version = "0.16.1" +version = "0.16.2-dev0" diff --git a/kernels/pyproject.toml b/kernels/pyproject.toml index 3692602f3..550a58b9a 100644 --- a/kernels/pyproject.toml +++ b/kernels/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "kernels" -version = "0.16.1" +version = "0.16.2.dev0" description = "Download compute kernels" authors = [ { name = "Daniel de Kok", email = "daniel@huggingface.co" }, diff --git a/kernels/uv.lock b/kernels/uv.lock index d0544390e..7d9bc6e90 100644 --- a/kernels/uv.lock +++ b/kernels/uv.lock @@ -8,7 +8,7 @@ resolution-markers = [ ] [options] -exclude-newer = "2026-08-17T06:30:33.953860523Z" +exclude-newer = "2026-09-07T12:37:31.762368758Z" exclude-newer-span = "P7D" [[package]] @@ -1158,7 +1158,7 @@ wheels = [ [[package]] name = "kernels" -version = "0.16.1" +version = "0.16.2.dev0" source = { editable = "." } dependencies = [ { name = "huggingface-hub" },