Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions docs/source/kernel-requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
Expand Down
2 changes: 1 addition & 1 deletion kernel-abi-check/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
6 changes: 3 additions & 3 deletions kernel-builder/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion kernels-data/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
4 changes: 2 additions & 2 deletions kernels-data/bindings/python/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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"
2 changes: 1 addition & 1 deletion kernels/pyproject.toml
Original file line number Diff line number Diff line change
@@ -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" },
Expand Down
69 changes: 69 additions & 0 deletions kernels/src/kernels/minver.py
Original file line number Diff line number Diff line change
@@ -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,
)
2 changes: 2 additions & 0 deletions kernels/src/kernels/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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"
Expand Down
3 changes: 2 additions & 1 deletion kernels/tests/test_archs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
112 changes: 112 additions & 0 deletions kernels/tests/test_minver.py
Original file line number Diff line number Diff line change
@@ -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)
4 changes: 2 additions & 2 deletions kernels/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading