kernels 0.16.0__tar.gz → 0.16.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {kernels-0.16.0 → kernels-0.16.2}/PKG-INFO +1 -1
- {kernels-0.16.0 → kernels-0.16.2}/pyproject.toml +1 -1
- kernels-0.16.2/src/kernels/archs.py +115 -0
- kernels-0.16.2/src/kernels/minver.py +69 -0
- {kernels-0.16.0 → kernels-0.16.2}/src/kernels/utils.py +46 -7
- {kernels-0.16.0 → kernels-0.16.2}/src/kernels.egg-info/PKG-INFO +1 -1
- {kernels-0.16.0 → kernels-0.16.2}/src/kernels.egg-info/SOURCES.txt +4 -0
- kernels-0.16.2/tests/test_archs.py +158 -0
- kernels-0.16.2/tests/test_minver.py +112 -0
- {kernels-0.16.0 → kernels-0.16.2}/README.md +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/setup.cfg +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/src/kernels/__init__.py +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/src/kernels/_system.py +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/src/kernels/_versions.py +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/src/kernels/_windows.py +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/src/kernels/backends.py +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/src/kernels/benchmark.py +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/src/kernels/benchmarks/__init__.py +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/src/kernels/benchmarks/activation.py +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/src/kernels/benchmarks/attention.py +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/src/kernels/benchmarks/layer_norm.py +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/src/kernels/cli/__init__.py +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/src/kernels/cli/benchmark.py +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/src/kernels/cli/benchmark_graphics.py +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/src/kernels/cli/verify_signature.py +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/src/kernels/cli/versions.py +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/src/kernels/compat.py +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/src/kernels/deps.py +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/src/kernels/layer/__init__.py +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/src/kernels/layer/_interval_tree.py +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/src/kernels/layer/device.py +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/src/kernels/layer/func.py +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/src/kernels/layer/globals.py +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/src/kernels/layer/kernelize.py +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/src/kernels/layer/layer.py +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/src/kernels/layer/mode.py +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/src/kernels/layer/repos.py +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/src/kernels/lockfile.py +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/src/kernels/python_depends.json +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/src/kernels/status.py +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/src/kernels/variants.py +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/src/kernels/verify.py +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/src/kernels.egg-info/dependency_links.txt +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/src/kernels.egg-info/entry_points.txt +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/src/kernels.egg-info/requires.txt +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/src/kernels.egg-info/top_level.txt +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/tests/test_basic.py +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/tests/test_benchmarks.py +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/tests/test_deps.py +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/tests/test_doctest.py +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/tests/test_func.py +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/tests/test_interval_tree.py +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/tests/test_kernel_locking.py +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/tests/test_layer.py +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/tests/test_loaded_kernels.py +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/tests/test_status.py +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/tests/test_tvm_ffi.py +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/tests/test_user_agent.py +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/tests/test_variants.py +0 -0
- {kernels-0.16.0 → kernels-0.16.2}/tests/test_verify.py +0 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
from kernels_data import Backend, Metadata
|
|
2
|
+
|
|
3
|
+
from kernels.compat import has_torch
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _parse_cuda_arch(arch: str) -> tuple[int, int, str, bool] | None:
|
|
7
|
+
"""Parse a CUDA arch into its capability, suffix, and PTX flag.
|
|
8
|
+
|
|
9
|
+
CUDA archs are compute capabilities like `9.0`, optionally with an
|
|
10
|
+
architecture-specific (`9.0a`) or family-specific (`10.0f`) suffix
|
|
11
|
+
and/or a `+PTX` flag (`9.0+PTX`). Returns `None` when the arch string
|
|
12
|
+
is not in this format.
|
|
13
|
+
"""
|
|
14
|
+
ptx = False
|
|
15
|
+
if arch.endswith("+PTX"):
|
|
16
|
+
ptx = True
|
|
17
|
+
arch = arch[: -len("+PTX")]
|
|
18
|
+
|
|
19
|
+
suffix = ""
|
|
20
|
+
if arch.endswith(("a", "f")):
|
|
21
|
+
suffix = arch[-1]
|
|
22
|
+
arch = arch[:-1]
|
|
23
|
+
|
|
24
|
+
arch_major, sep, arch_minor = arch.partition(".")
|
|
25
|
+
if not sep or not arch_major.isdigit() or not arch_minor.isdigit():
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
return int(arch_major), int(arch_minor), suffix, ptx
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _cuda_arch_supports(arch: str, capability: tuple[int, int]) -> bool | None:
|
|
32
|
+
"""Check whether a single declared CUDA arch supports a compute capability.
|
|
33
|
+
|
|
34
|
+
Returns `None` when the arch string is not in a known format.
|
|
35
|
+
"""
|
|
36
|
+
parsed = _parse_cuda_arch(arch)
|
|
37
|
+
if parsed is None:
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
arch_major, arch_minor, suffix, ptx = parsed
|
|
41
|
+
major, minor = capability
|
|
42
|
+
|
|
43
|
+
if suffix == "a":
|
|
44
|
+
# Architecture-specific builds only run on that exact capability.
|
|
45
|
+
return (major, minor) == (arch_major, arch_minor)
|
|
46
|
+
|
|
47
|
+
if suffix == "f":
|
|
48
|
+
# Family-specific builds run on capabilities of the same generation
|
|
49
|
+
# with the same or a newer minor version.
|
|
50
|
+
return major == arch_major and minor >= arch_minor
|
|
51
|
+
|
|
52
|
+
if ptx:
|
|
53
|
+
# PTX is JIT-compiled for the current device, so the build also runs
|
|
54
|
+
# on any newer capability.
|
|
55
|
+
return (major, minor) >= (arch_major, arch_minor)
|
|
56
|
+
|
|
57
|
+
# Base builds run on capabilities of the same generation with the same
|
|
58
|
+
# or a newer minor version.
|
|
59
|
+
return major == arch_major and minor >= arch_minor
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _supports_cuda_capability(archs: list[str], capability: tuple[int, int]) -> bool:
|
|
63
|
+
supports = [_cuda_arch_supports(arch, capability) for arch in archs]
|
|
64
|
+
if all(support is None for support in supports):
|
|
65
|
+
# None of the arch strings are in a known format (e.g. produced by a
|
|
66
|
+
# newer kernel-builder), so compatibility cannot be determined.
|
|
67
|
+
return True
|
|
68
|
+
return any(supports)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _check_arch_incompatibility(metadata: Metadata, variant: str) -> None:
|
|
72
|
+
"""Check a kernel build against the architecture of the current device.
|
|
73
|
+
|
|
74
|
+
Raises `RuntimeError` when the build does not support the device.
|
|
75
|
+
"""
|
|
76
|
+
archs = metadata.backend.archs
|
|
77
|
+
if not archs or not has_torch:
|
|
78
|
+
return
|
|
79
|
+
|
|
80
|
+
import torch
|
|
81
|
+
|
|
82
|
+
reason = None
|
|
83
|
+
backend_type = metadata.backend.backend_type
|
|
84
|
+
if backend_type == Backend.CUDA:
|
|
85
|
+
if torch.version.cuda is None or not torch.cuda.is_available():
|
|
86
|
+
return
|
|
87
|
+
major, minor = torch.cuda.get_device_capability()
|
|
88
|
+
if not _supports_cuda_capability(archs, (major, minor)):
|
|
89
|
+
reason = (
|
|
90
|
+
f"CUDA capability {major}.{minor} of the current device is not "
|
|
91
|
+
f"supported by the architectures of the build: {', '.join(archs)}"
|
|
92
|
+
)
|
|
93
|
+
elif backend_type == Backend.ROCm:
|
|
94
|
+
if torch.version.hip is None or not torch.cuda.is_available():
|
|
95
|
+
return
|
|
96
|
+
gcn_arch = torch.cuda.get_device_properties(torch.cuda.current_device()).gcnArchName
|
|
97
|
+
# Strip feature flags, e.g. `gfx90a:sramecc+:xnack-` -> `gfx90a`.
|
|
98
|
+
gcn_arch = gcn_arch.split(":")[0]
|
|
99
|
+
if gcn_arch not in archs:
|
|
100
|
+
reason = (
|
|
101
|
+
f"ROCm arch {gcn_arch} of the current device is not supported "
|
|
102
|
+
f"by the architectures of the build: {', '.join(archs)}"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
if reason is None:
|
|
106
|
+
return
|
|
107
|
+
|
|
108
|
+
raise RuntimeError(
|
|
109
|
+
f"Kernel '{metadata.name}' variant '{variant}' does not support the "
|
|
110
|
+
f"current device: {reason}. The declared architectures can be "
|
|
111
|
+
"incomplete (e.g. when a kernel has a Triton fallback), pass "
|
|
112
|
+
"`check_arch=False` to skip this check. Warning: the kernel may "
|
|
113
|
+
"fail or crash the process at run time when it does not support "
|
|
114
|
+
"the device."
|
|
115
|
+
)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import warnings
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from kernels_data import Metadata, Version
|
|
6
|
+
from packaging.version import InvalidVersion, parse
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _read_kernels_minver(variant_path: Path) -> Version | None:
|
|
10
|
+
"""The minimum `kernels` version required by a build variant."""
|
|
11
|
+
|
|
12
|
+
# Note, for kernels >= 0.17, we read the version from the metadata,
|
|
13
|
+
# but to minimize the impact in the backport, we are reading from
|
|
14
|
+
# metadata.json here directly.
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
with open(variant_path / "metadata.json", "rb") as f:
|
|
18
|
+
minver = json.load(f).get("kernels-minver")
|
|
19
|
+
except (OSError, ValueError):
|
|
20
|
+
return None
|
|
21
|
+
|
|
22
|
+
if not isinstance(minver, str):
|
|
23
|
+
return None
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
return Version.from_str(minver)
|
|
27
|
+
except ValueError:
|
|
28
|
+
return None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _installed_version() -> Version | None:
|
|
32
|
+
"""The installed `kernels` version as a numeric version."""
|
|
33
|
+
# Avoid an import cycle.
|
|
34
|
+
from kernels import __version__
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
release = parse(__version__).release
|
|
38
|
+
except InvalidVersion:
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
# packaging < 22 returns a LegacyVersion with `release = None` for
|
|
42
|
+
# non-PEP 440 versions instead of raising `InvalidVersion`.
|
|
43
|
+
if release is None:
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
return Version.from_str(".".join(str(part) for part in release))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _warn_if_below_minver(metadata: Metadata, variant_path: Path) -> None:
|
|
50
|
+
"""Warn when the installed `kernels` library is older than the minimum
|
|
51
|
+
version required by a kernel.
|
|
52
|
+
"""
|
|
53
|
+
minver = _read_kernels_minver(variant_path)
|
|
54
|
+
if minver is None:
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
installed = _installed_version()
|
|
58
|
+
if installed is not None and installed < minver:
|
|
59
|
+
# Report the verbatim installed version, not the normalized one used
|
|
60
|
+
# for comparison, so the message matches what `pip show` reports.
|
|
61
|
+
from kernels import __version__
|
|
62
|
+
|
|
63
|
+
warnings.warn(
|
|
64
|
+
f"Kernel '{metadata.name}' variant '{variant_path.name}' requires "
|
|
65
|
+
f"kernels>={minver}, but version {__version__} is installed. "
|
|
66
|
+
"The kernel may not load or work correctly; upgrade with: "
|
|
67
|
+
"pip install --upgrade kernels",
|
|
68
|
+
stacklevel=3,
|
|
69
|
+
)
|
|
@@ -14,15 +14,17 @@ from pathlib import Path
|
|
|
14
14
|
from types import ModuleType
|
|
15
15
|
|
|
16
16
|
from huggingface_hub import HfApi, constants
|
|
17
|
-
from huggingface_hub.errors import LocalEntryNotFoundError
|
|
17
|
+
from huggingface_hub.errors import EntryNotFoundError, LocalEntryNotFoundError
|
|
18
18
|
from kernels_data import Metadata
|
|
19
19
|
|
|
20
20
|
from kernels._system import glibc_version
|
|
21
21
|
from kernels._versions import select_revision_or_version
|
|
22
|
+
from kernels.archs import _check_arch_incompatibility
|
|
22
23
|
from kernels.backends import _backend, _select_backend
|
|
23
24
|
from kernels.compat import has_torch, has_tvm_ffi
|
|
24
25
|
from kernels.deps import validate_dependencies
|
|
25
26
|
from kernels.lockfile import KernelLock, VariantLock
|
|
27
|
+
from kernels.minver import _warn_if_below_minver
|
|
26
28
|
from kernels.status import resolve_status
|
|
27
29
|
from kernels.variants import (
|
|
28
30
|
Decision,
|
|
@@ -209,6 +211,7 @@ def _import_from_path(variant_path: Path, repo_info: RepoInfo | None = None) ->
|
|
|
209
211
|
return loaded_kernel.module
|
|
210
212
|
|
|
211
213
|
metadata = Metadata.read_from_file(variant_path / "metadata.json")
|
|
214
|
+
_warn_if_below_minver(metadata, variant_path)
|
|
212
215
|
module_name = metadata.name.python_name
|
|
213
216
|
|
|
214
217
|
file_path = variant_path / "__init__.py"
|
|
@@ -455,6 +458,7 @@ def get_kernel(
|
|
|
455
458
|
backend: str | None = None,
|
|
456
459
|
user_agent: str | dict | None = None,
|
|
457
460
|
trust_remote_code: bool | list[str] = False,
|
|
461
|
+
check_arch: bool = True,
|
|
458
462
|
) -> ModuleType:
|
|
459
463
|
"""
|
|
460
464
|
Load a kernel from the kernel hub.
|
|
@@ -481,6 +485,12 @@ def get_kernel(
|
|
|
481
485
|
repositories are allowed. A list of strings will be used to verify signing
|
|
482
486
|
identities in a future release; for now it emits a warning and falls
|
|
483
487
|
back to the default trust check.
|
|
488
|
+
check_arch (`bool`, *optional*, defaults to `True`):
|
|
489
|
+
Whether to check that the kernel build supports the architecture
|
|
490
|
+
(e.g. CUDA compute capability) of the current device. Kernels can
|
|
491
|
+
support more architectures than they declare (e.g. through a
|
|
492
|
+
Triton fallback), `check_arch=False` skips the check for such
|
|
493
|
+
kernels.
|
|
484
494
|
|
|
485
495
|
Returns:
|
|
486
496
|
`ModuleType`: The imported kernel module.
|
|
@@ -514,6 +524,9 @@ def get_kernel(
|
|
|
514
524
|
user_agent=user_agent,
|
|
515
525
|
validate_dependencies=True,
|
|
516
526
|
)
|
|
527
|
+
if check_arch:
|
|
528
|
+
metadata = Metadata.read_from_file(variant_path / "metadata.json")
|
|
529
|
+
_check_arch_incompatibility(metadata, variant_path.name)
|
|
517
530
|
return _import_from_path(variant_path, repo_info=repo_info)
|
|
518
531
|
|
|
519
532
|
|
|
@@ -558,6 +571,7 @@ def has_kernel(
|
|
|
558
571
|
revision: str | None = None,
|
|
559
572
|
version: int | None = None,
|
|
560
573
|
backend: str | None = None,
|
|
574
|
+
check_arch: bool = True,
|
|
561
575
|
) -> bool:
|
|
562
576
|
"""
|
|
563
577
|
Check whether a kernel build exists for the current environment (Torch version and compute framework).
|
|
@@ -573,6 +587,12 @@ def has_kernel(
|
|
|
573
587
|
backend (`str`, *optional*):
|
|
574
588
|
The backend to load the kernel for. Can only be `cpu` or the backend that Torch is compiled for.
|
|
575
589
|
The backend will be detected automatically if not provided.
|
|
590
|
+
check_arch (`bool`, *optional*, defaults to `True`):
|
|
591
|
+
Whether to check that the kernel build supports the architecture
|
|
592
|
+
(e.g. CUDA compute capability) of the current device. Kernels can
|
|
593
|
+
support more architectures than they declare (e.g. through a
|
|
594
|
+
Triton fallback), `check_arch=False` skips the check for such
|
|
595
|
+
kernels.
|
|
576
596
|
|
|
577
597
|
Returns:
|
|
578
598
|
`bool`: `True` if a kernel is available for the current environment.
|
|
@@ -586,12 +606,31 @@ def has_kernel(
|
|
|
586
606
|
if variant is None:
|
|
587
607
|
return False
|
|
588
608
|
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
609
|
+
if not check_arch:
|
|
610
|
+
return api.file_exists(
|
|
611
|
+
repo_id,
|
|
612
|
+
repo_type="kernel",
|
|
613
|
+
revision=revision,
|
|
614
|
+
filename=f"build/{variant.variant_str}/metadata.json",
|
|
615
|
+
)
|
|
616
|
+
|
|
617
|
+
try:
|
|
618
|
+
metadata_path = api.hf_hub_download(
|
|
619
|
+
repo_id,
|
|
620
|
+
repo_type="kernel",
|
|
621
|
+
revision=revision,
|
|
622
|
+
filename=f"build/{variant.variant_str}/metadata.json",
|
|
623
|
+
cache_dir=CACHE_DIR,
|
|
624
|
+
)
|
|
625
|
+
except EntryNotFoundError:
|
|
626
|
+
return False
|
|
627
|
+
|
|
628
|
+
try:
|
|
629
|
+
_check_arch_incompatibility(Metadata.read_from_file(metadata_path), variant.variant_str)
|
|
630
|
+
except RuntimeError:
|
|
631
|
+
return False
|
|
632
|
+
|
|
633
|
+
return True
|
|
595
634
|
|
|
596
635
|
|
|
597
636
|
def get_kernel_variants(
|
|
@@ -4,11 +4,13 @@ src/kernels/__init__.py
|
|
|
4
4
|
src/kernels/_system.py
|
|
5
5
|
src/kernels/_versions.py
|
|
6
6
|
src/kernels/_windows.py
|
|
7
|
+
src/kernels/archs.py
|
|
7
8
|
src/kernels/backends.py
|
|
8
9
|
src/kernels/benchmark.py
|
|
9
10
|
src/kernels/compat.py
|
|
10
11
|
src/kernels/deps.py
|
|
11
12
|
src/kernels/lockfile.py
|
|
13
|
+
src/kernels/minver.py
|
|
12
14
|
src/kernels/python_depends.json
|
|
13
15
|
src/kernels/status.py
|
|
14
16
|
src/kernels/utils.py
|
|
@@ -38,6 +40,7 @@ src/kernels/layer/kernelize.py
|
|
|
38
40
|
src/kernels/layer/layer.py
|
|
39
41
|
src/kernels/layer/mode.py
|
|
40
42
|
src/kernels/layer/repos.py
|
|
43
|
+
tests/test_archs.py
|
|
41
44
|
tests/test_basic.py
|
|
42
45
|
tests/test_benchmarks.py
|
|
43
46
|
tests/test_deps.py
|
|
@@ -47,6 +50,7 @@ tests/test_interval_tree.py
|
|
|
47
50
|
tests/test_kernel_locking.py
|
|
48
51
|
tests/test_layer.py
|
|
49
52
|
tests/test_loaded_kernels.py
|
|
53
|
+
tests/test_minver.py
|
|
50
54
|
tests/test_status.py
|
|
51
55
|
tests/test_tvm_ffi.py
|
|
52
56
|
tests/test_user_agent.py
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
import torch
|
|
5
|
+
from kernels_data import Metadata
|
|
6
|
+
|
|
7
|
+
from kernels import get_kernel, has_kernel, install_kernel
|
|
8
|
+
from kernels.archs import _check_arch_incompatibility, _supports_cuda_capability
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def make_metadata(backend_type: str, archs: list[str] | None) -> Metadata:
|
|
12
|
+
return Metadata.from_bytes(
|
|
13
|
+
json.dumps(
|
|
14
|
+
{
|
|
15
|
+
"name": "test-kernel",
|
|
16
|
+
"id": "test_id",
|
|
17
|
+
"version": 1,
|
|
18
|
+
"license": "mit",
|
|
19
|
+
"python-depends": [],
|
|
20
|
+
"backend": {"type": backend_type, "archs": archs},
|
|
21
|
+
}
|
|
22
|
+
).encode("utf-8")
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@pytest.mark.parametrize(
|
|
27
|
+
"archs,capability,supported",
|
|
28
|
+
[
|
|
29
|
+
# Base archs run on the same generation with the same or a newer
|
|
30
|
+
# minor capability.
|
|
31
|
+
(["8.0"], (8, 0), True),
|
|
32
|
+
(["8.0"], (8, 6), True),
|
|
33
|
+
(["8.6"], (8, 0), False),
|
|
34
|
+
(["8.0"], (7, 5), False),
|
|
35
|
+
(["8.0"], (9, 0), False),
|
|
36
|
+
(["8.0", "9.0"], (9, 0), True),
|
|
37
|
+
# Architecture-specific archs only run on that exact capability.
|
|
38
|
+
(["9.0a"], (9, 0), True),
|
|
39
|
+
(["9.0a"], (9, 1), False),
|
|
40
|
+
(["8.0", "9.0a"], (10, 0), False),
|
|
41
|
+
# Family-specific archs run on the same generation with the same or
|
|
42
|
+
# a newer minor capability.
|
|
43
|
+
(["10.0f"], (10, 0), True),
|
|
44
|
+
(["10.0f"], (10, 3), True),
|
|
45
|
+
(["10.0f"], (12, 0), False),
|
|
46
|
+
# PTX is JIT-compiled for the current device, so builds with `+PTX`
|
|
47
|
+
# also run on any newer capability.
|
|
48
|
+
(["9.0+PTX"], (9, 0), True),
|
|
49
|
+
(["9.0+PTX"], (9, 1), True),
|
|
50
|
+
(["9.0+PTX"], (10, 0), True),
|
|
51
|
+
(["9.0+PTX"], (12, 1), True),
|
|
52
|
+
(["9.0+PTX"], (8, 6), False),
|
|
53
|
+
# ...but the `a` and `f` suffixes keep their own semantics.
|
|
54
|
+
(["9.0a+PTX"], (9, 0), True),
|
|
55
|
+
(["9.0a+PTX"], (10, 0), False),
|
|
56
|
+
(["10.0f+PTX"], (10, 3), True),
|
|
57
|
+
(["10.0f+PTX"], (12, 0), False),
|
|
58
|
+
# Arch strings in an unknown format do not count as a match...
|
|
59
|
+
(["garbage", "8.0"], (9, 0), False),
|
|
60
|
+
# ...but when no arch string can be parsed, compatibility cannot be
|
|
61
|
+
# determined and the build is not rejected.
|
|
62
|
+
(["garbage"], (9, 0), True),
|
|
63
|
+
],
|
|
64
|
+
)
|
|
65
|
+
def test_supports_cuda_capability(archs, capability, supported):
|
|
66
|
+
assert _supports_cuda_capability(archs, capability) == supported
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@pytest.fixture
|
|
70
|
+
def fake_cuda_device(monkeypatch):
|
|
71
|
+
monkeypatch.setattr(torch.version, "cuda", "12.8", raising=False)
|
|
72
|
+
monkeypatch.setattr(torch.version, "hip", None, raising=False)
|
|
73
|
+
monkeypatch.setattr(torch.cuda, "is_available", lambda: True)
|
|
74
|
+
monkeypatch.setattr(torch.cuda, "get_device_capability", lambda device=None: (10, 0))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@pytest.fixture
|
|
78
|
+
def fake_rocm_device(monkeypatch):
|
|
79
|
+
class FakeProperties:
|
|
80
|
+
gcnArchName = "gfx90a:sramecc+:xnack-"
|
|
81
|
+
|
|
82
|
+
monkeypatch.setattr(torch.version, "cuda", None, raising=False)
|
|
83
|
+
monkeypatch.setattr(torch.version, "hip", "6.4.0", raising=False)
|
|
84
|
+
monkeypatch.setattr(torch.cuda, "is_available", lambda: True)
|
|
85
|
+
monkeypatch.setattr(torch.cuda, "current_device", lambda: 0)
|
|
86
|
+
monkeypatch.setattr(torch.cuda, "get_device_properties", lambda device: FakeProperties())
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_cuda_incompatible_arch_is_rejected(fake_cuda_device):
|
|
90
|
+
with pytest.raises(RuntimeError) as exc_info:
|
|
91
|
+
_check_arch_incompatibility(make_metadata("cuda", ["8.0", "9.0a"]), "test-variant")
|
|
92
|
+
assert "test-variant" in str(exc_info.value)
|
|
93
|
+
assert "CUDA capability 10.0" in str(exc_info.value)
|
|
94
|
+
assert "8.0, 9.0a" in str(exc_info.value)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def test_cuda_compatible_arch_is_accepted(fake_cuda_device):
|
|
98
|
+
for archs in (["8.0", "10.0"], ["10.0a"], ["10.0f"]):
|
|
99
|
+
_check_arch_incompatibility(make_metadata("cuda", archs), "test-variant")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def test_noarch_build_is_accepted(fake_cuda_device):
|
|
103
|
+
# Backends that support archs (e.g. CUDA) can have builds that do not
|
|
104
|
+
# declare any (noarch kernels, e.g. pure Triton builds). Such builds are
|
|
105
|
+
# never rejected.
|
|
106
|
+
_check_arch_incompatibility(make_metadata("cuda", None), "test-variant")
|
|
107
|
+
_check_arch_incompatibility(make_metadata("cuda", []), "test-variant")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def test_rocm_arch_check(fake_rocm_device):
|
|
111
|
+
_check_arch_incompatibility(make_metadata("rocm", ["gfx90a", "gfx942"]), "test-variant")
|
|
112
|
+
_check_arch_incompatibility(make_metadata("rocm", None), "test-variant")
|
|
113
|
+
with pytest.raises(RuntimeError) as exc_info:
|
|
114
|
+
_check_arch_incompatibility(make_metadata("rocm", ["gfx942"]), "test-variant")
|
|
115
|
+
assert "ROCm arch gfx90a" in str(exc_info.value)
|
|
116
|
+
assert "gfx942" in str(exc_info.value)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def test_check_skipped_without_device(monkeypatch):
|
|
120
|
+
monkeypatch.setattr(torch.version, "cuda", "12.8", raising=False)
|
|
121
|
+
monkeypatch.setattr(torch.cuda, "is_available", lambda: False)
|
|
122
|
+
_check_arch_incompatibility(make_metadata("cuda", ["8.0"]), "test-variant")
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def test_check_skipped_for_backends_without_archs(fake_cuda_device):
|
|
126
|
+
# Archs of other backends cannot be checked against the current device.
|
|
127
|
+
_check_arch_incompatibility(make_metadata("cpu", None), "test-variant")
|
|
128
|
+
_check_arch_incompatibility(make_metadata("metal", ["applegpu_g13"]), "test-variant")
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def test_issue_707_fa3_on_b200(fake_cuda_device):
|
|
132
|
+
# https://github.com/huggingface/kernels/issues/707: flash-attn3 only
|
|
133
|
+
# declares sm_80/sm_90a archs, but loading it on a B200 (capability 10.0)
|
|
134
|
+
# succeeded and the first launch exited the process. The declared archs
|
|
135
|
+
# must be rejected for this device.
|
|
136
|
+
metadata = make_metadata("cuda", ["8.0", "9.0a"])
|
|
137
|
+
with pytest.raises(RuntimeError, match="does not support the current device"):
|
|
138
|
+
_check_arch_incompatibility(metadata, "test-variant")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@pytest.mark.cuda_only
|
|
142
|
+
def test_get_kernel_rejects_unsupported_capability(monkeypatch):
|
|
143
|
+
variant_path = install_kernel("kernels-community/relu", revision="v1")
|
|
144
|
+
metadata = Metadata.read_from_file(variant_path / "metadata.json")
|
|
145
|
+
if not metadata.backend.archs:
|
|
146
|
+
pytest.skip("kernel build does not declare archs")
|
|
147
|
+
|
|
148
|
+
# ReLU kernel supports PTX, so we need an old version.
|
|
149
|
+
monkeypatch.setattr(torch.cuda, "get_device_capability", lambda device=None: (1, 0))
|
|
150
|
+
|
|
151
|
+
with pytest.raises(RuntimeError, match="does not support the current device"):
|
|
152
|
+
get_kernel("kernels-community/relu", version=1)
|
|
153
|
+
assert not has_kernel("kernels-community/relu", version=1)
|
|
154
|
+
|
|
155
|
+
# The opt-out only checks that a compatible build variant exists.
|
|
156
|
+
assert has_kernel("kernels-community/relu", version=1, check_arch=False)
|
|
157
|
+
kernel = get_kernel("kernels-community/relu", version=1, check_arch=False)
|
|
158
|
+
assert kernel is not None
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
from kernels_data import Metadata, Version
|
|
6
|
+
|
|
7
|
+
import kernels
|
|
8
|
+
from kernels.minver import _installed_version, _warn_if_below_minver
|
|
9
|
+
from kernels.utils import _import_from_path, _loaded_kernels
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _write_variant(tmp_path, minver):
|
|
13
|
+
variant_dir = tmp_path / "build" / "torch28-cxx11-cu128-x86_64-linux"
|
|
14
|
+
variant_dir.mkdir(parents=True)
|
|
15
|
+
metadata = {
|
|
16
|
+
"id": "activation_1_cuda",
|
|
17
|
+
"name": "activation",
|
|
18
|
+
"version": 1,
|
|
19
|
+
"license": "Apache-2.0",
|
|
20
|
+
"python-depends": ["torch"],
|
|
21
|
+
"backend": {"type": "cuda"},
|
|
22
|
+
}
|
|
23
|
+
if minver is not None:
|
|
24
|
+
metadata["kernels-minver"] = minver
|
|
25
|
+
(variant_dir / "metadata.json").write_text(json.dumps(metadata))
|
|
26
|
+
return variant_dir
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@pytest.mark.parametrize("minver", [None, "0.0.1"])
|
|
30
|
+
def test_no_warning_when_minver_met(tmp_path, recwarn, minver):
|
|
31
|
+
variant_dir = _write_variant(tmp_path, minver)
|
|
32
|
+
metadata = Metadata.read_from_file(variant_dir / "metadata.json")
|
|
33
|
+
_warn_if_below_minver(metadata, variant_dir)
|
|
34
|
+
assert len(recwarn) == 0
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_no_warning_for_dev_version_of_required_release(tmp_path, recwarn, monkeypatch):
|
|
38
|
+
# A development version implements the release it leads up to, so
|
|
39
|
+
# `0.17.0.dev0` must satisfy a `0.17.0` requirement.
|
|
40
|
+
monkeypatch.setattr(kernels, "__version__", "0.17.0.dev0")
|
|
41
|
+
variant_dir = _write_variant(tmp_path, "0.17.0")
|
|
42
|
+
metadata = Metadata.read_from_file(variant_dir / "metadata.json")
|
|
43
|
+
_warn_if_below_minver(metadata, variant_dir)
|
|
44
|
+
assert len(recwarn) == 0
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@pytest.mark.parametrize(
|
|
48
|
+
"installed",
|
|
49
|
+
["0.17.0.dev0", "0.17.0rc1", "0.17.0.post1", "0.17.0+cu121", "0.17"],
|
|
50
|
+
)
|
|
51
|
+
def test_installed_version_uses_release_segment(monkeypatch, installed):
|
|
52
|
+
monkeypatch.setattr(kernels, "__version__", installed)
|
|
53
|
+
assert _installed_version() == Version.from_str("0.17.0")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_installed_version_is_none_for_non_pep440_version(monkeypatch):
|
|
57
|
+
monkeypatch.setattr(kernels, "__version__", "0.17.0-dirty")
|
|
58
|
+
assert _installed_version() is None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_unparseable_installed_version_does_not_fail_load(tmp_path, recwarn, monkeypatch):
|
|
62
|
+
# A version that cannot be compared must not turn this advisory check into
|
|
63
|
+
# a hard failure.
|
|
64
|
+
monkeypatch.setattr(kernels, "__version__", "0.17.0-dirty")
|
|
65
|
+
variant_dir = _write_variant(tmp_path, "999.1.0")
|
|
66
|
+
metadata = Metadata.read_from_file(variant_dir / "metadata.json")
|
|
67
|
+
_warn_if_below_minver(metadata, variant_dir)
|
|
68
|
+
assert len(recwarn) == 0
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@pytest.mark.parametrize("minver", [42, "", "not-a-version"])
|
|
72
|
+
def test_invalid_minver_is_ignored(tmp_path, recwarn, minver):
|
|
73
|
+
# A `kernels-minver` that cannot be parsed (e.g. written by a future
|
|
74
|
+
# version that changed the format) must not fail the load.
|
|
75
|
+
variant_dir = _write_variant(tmp_path, minver)
|
|
76
|
+
metadata = Metadata.read_from_file(variant_dir / "metadata.json")
|
|
77
|
+
_warn_if_below_minver(metadata, variant_dir)
|
|
78
|
+
assert len(recwarn) == 0
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def test_version_ordering_is_numeric_not_lexicographic():
|
|
82
|
+
# `0.9 < 0.10` only holds for numeric comparison; string comparison would
|
|
83
|
+
# get this backwards.
|
|
84
|
+
assert Version.from_str("0.9") < Version.from_str("0.10")
|
|
85
|
+
assert Version.from_str("0.14") == Version.from_str("0.14.0")
|
|
86
|
+
assert Version.from_str("0.14.0") < Version.from_str("0.14.1")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_warns_when_minver_not_met(tmp_path):
|
|
90
|
+
variant_dir = _write_variant(tmp_path, "999.1.0")
|
|
91
|
+
metadata = Metadata.read_from_file(variant_dir / "metadata.json")
|
|
92
|
+
with pytest.warns(UserWarning, match="requires kernels>=999.1"):
|
|
93
|
+
_warn_if_below_minver(metadata, variant_dir)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def test_warning_mentions_installed_version(tmp_path):
|
|
97
|
+
variant_dir = _write_variant(tmp_path, "999.1.0")
|
|
98
|
+
metadata = Metadata.read_from_file(variant_dir / "metadata.json")
|
|
99
|
+
with pytest.warns(UserWarning, match=f"version {re.escape(kernels.__version__)} is installed"):
|
|
100
|
+
_warn_if_below_minver(metadata, variant_dir)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def test_import_from_path_warns_on_unmet_minver(tmp_path):
|
|
104
|
+
variant_dir = _write_variant(tmp_path, "999.1.0")
|
|
105
|
+
(variant_dir / "__init__.py").write_text("value = 42\n")
|
|
106
|
+
_loaded_kernels.pop(variant_dir, None)
|
|
107
|
+
try:
|
|
108
|
+
with pytest.warns(UserWarning, match="requires kernels>=999.1"):
|
|
109
|
+
module = _import_from_path(variant_dir)
|
|
110
|
+
assert module.value == 42
|
|
111
|
+
finally:
|
|
112
|
+
_loaded_kernels.pop(variant_dir, None)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|