Skip to content

Commit d91ba5e

Browse files
danieldksayakpaul
andauthored
Simplify handling of kernelized functions (#622)
* kernels: deprecate `FuncRepository` classes * Make `use_kernel_forward_from_hub` work with functions Change `use_kernel_forward_from_hub` to work with functions. `use_kernel_func_from_hub` is now deprecated and its implementation is now just a call to `use_kernel_forward_from_hub`. * kernels: add `use_kernelized_func` decorator from transformers This function attaches a function that is made kernelizeable to a module, so that it can be discovered by `kernelize`. * Sync documentation with kernel function changes * docs: fix markup * doc fixes Co-authored-by: Sayak Paul <spsayakpaul@gmail.com> * Clarify and check `use_kernelized_func` must be used with kernel funcs * Use admonitions for warnings * Add description `MyCustomLayers` * Remove stale comment * Change exception to an assertion * Stray print * Test for deprecation warnings --------- Co-authored-by: Sayak Paul <spsayakpaul@gmail.com>
1 parent 2841cb4 commit d91ba5e

8 files changed

Lines changed: 291 additions & 69 deletions

File tree

docs/source/api/layers.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010

1111
[[autodoc]] kernels.use_kernel_func_from_hub
1212

13+
### use_kernelized_func
14+
15+
[[autodoc]] kernels.use_kernelized_func
16+
1317
### replace_kernel_forward_from_hub
1418

1519
[[autodoc]] kernels.replace_kernel_forward_from_hub

docs/source/layers.md

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,32 +47,40 @@ compatible with layers from the hub.
4747

4848
Sometimes it can be useful to make a function extensible, for example
4949
because the function cannot be replaced by a layer. In such cases, you
50-
can annotate the function with the [`~kernels.use_kernel_func_from_hub`] decorator:
50+
can use the [`~kernels.use_kernel_forward_from_hub`] decorator on a
51+
function:
5152

5253
```python
53-
@use_kernel_func_from_hub("silu_and_mul")
54+
@use_kernel_forward_from_hub("silu_and_mul")
5455
def silu_and_mul(x: torch.Tensor) -> torch.Tensor:
5556
d = x.shape[-1] // 2
5657
return F.silu(x[..., :d]) * x[..., d:]
5758
```
5859

5960
This will replace the function by an instantiated `torch.nn.Module`
60-
(singleton) that calls the function itself in its forward method.
61+
(singleton) that calls the function itself in its forward method. It will
62+
still behave as a function, since `torch.nn.Module` provides an
63+
implementation of `__call__` that delegates to `forward`.
6164

62-
**Note:** for kernelization to see the function, it must be a member of
63-
another `torch.nn.Module` that is part of the model. For example:
65+
For [`~kernels.kernelize`] to see the function, you must use
66+
[`~kernels.use_kernelized_func`] on an `torch.nn.Module` that is part
67+
of the to-be kernlized model to make the function visible to
68+
[`~kernels.kernelize`]. The function is typically attached to the module
69+
that uses it. For example:
6470

6571
```python
72+
@use_kernelized_func(silu_and_mul)
6673
class FeedForward(nn.Module):
6774
def __init__(self, in_features: int, out_features: int):
6875
self.linear = nn.Linear(in_features, out_features)
69-
# Note: silu_and_mul is a Torch module.
70-
self.silu_and_mul = silu_and_mul
7176

7277
def forward(self, x: torch.Tensor) -> torch.Tensor:
73-
return self.silu_and_mul(self.linear(x))
78+
return silu_and_mul(self.linear(x))
7479
```
7580

81+
Functions used by [`~kernels.use_kernelized_func`] must always have a
82+
[`~kernels.use_kernel_forward_from_hub`] decorator.
83+
7684
## Kernelizing a model
7785

7886
A model will not use Hub kernels by default, even if it contains extensible

kernels/src/kernels/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use_kernel_forward_from_hub,
2424
use_kernel_func_from_hub,
2525
use_kernel_mapping,
26+
use_kernelized_func,
2627
)
2728
from kernels.utils import (
2829
LoadedKernel,
@@ -75,4 +76,5 @@
7576
"use_kernel_forward_from_hub",
7677
"use_kernel_func_from_hub",
7778
"use_kernel_mapping",
79+
"use_kernelized_func",
7880
]

kernels/src/kernels/layer/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
LockedLayerRepository,
1717
replace_kernel_forward_from_hub,
1818
use_kernel_forward_from_hub,
19+
use_kernelized_func,
1920
)
2021
from .mode import Mode
2122

@@ -36,4 +37,5 @@
3637
"use_kernel_forward_from_hub",
3738
"use_kernel_func_from_hub",
3839
"use_kernel_mapping",
40+
"use_kernelized_func",
3941
]

kernels/src/kernels/layer/func.py

Lines changed: 69 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
import functools
2-
import inspect
3-
from inspect import Parameter, Signature
2+
import warnings
43
from pathlib import Path
54
from types import ModuleType
6-
from typing import TYPE_CHECKING, Callable, Protocol, Type
7-
8-
from kernels.layer.repos import RepositoryProtocol
5+
from typing import TYPE_CHECKING, Protocol, Type
96

107
from .._versions import select_revision_or_version
118
from ..utils import (
@@ -14,6 +11,8 @@
1411
get_kernel,
1512
get_local_kernel,
1613
)
14+
from .layer import _create_func_module, use_kernel_forward_from_hub
15+
from .repos import RepositoryProtocol
1716

1817
if TYPE_CHECKING:
1918
from torch import nn
@@ -28,6 +27,10 @@ class FuncRepository:
2827
"""
2928
Repository and name of a function for kernel mapping.
3029
30+
> [!WARNING]
31+
> `FuncRepository` is deprecated and will be removed in kernels 0.17.
32+
> Use [`~kernels.LayerRepository`] instead.
33+
3134
Args:
3235
repo_id (`str`):
3336
The Hub repository containing the layer.
@@ -68,6 +71,12 @@ def __init__(
6871
version: int | None = None,
6972
trust_remote_code: bool | list[str] = False,
7073
):
74+
warnings.warn(
75+
"FuncRepository is deprecated and will be removed in kernels 0.17. Use LayerRepository instead.",
76+
DeprecationWarning,
77+
stacklevel=2,
78+
)
79+
7180
if revision is not None and version is not None:
7281
raise ValueError("Either a revision or a version must be specified, not both.")
7382
if revision is None and version is None:
@@ -92,7 +101,9 @@ def _resolve_revision(self) -> str:
92101

93102
def load(self) -> Type["nn.Module"]:
94103
kernel = get_kernel(
95-
self._repo_id, revision=self._resolve_revision(), trust_remote_code=self._trust_remote_code
104+
self._repo_id,
105+
revision=self._resolve_revision(),
106+
trust_remote_code=self._trust_remote_code,
96107
)
97108
return _get_kernel_func(self, kernel)
98109

@@ -107,7 +118,15 @@ def __eq__(self, other):
107118
)
108119

109120
def __hash__(self):
110-
return hash((self.func_name, self._repo_id, self._revision, self._version, self._trust_remote_code))
121+
return hash(
122+
(
123+
self.func_name,
124+
self._repo_id,
125+
self._revision,
126+
self._version,
127+
self._trust_remote_code,
128+
)
129+
)
111130

112131
def __str__(self) -> str:
113132
return f"`{self._repo_id}` (revision: {self._resolve_revision()}), function `{self.func_name}`"
@@ -117,6 +136,10 @@ class LocalFuncRepository:
117136
"""
118137
Repository and function name from a local directory for kernel mapping.
119138
139+
> [!WARNING]
140+
> `LocalFuncRepository` is deprecated and will be removed in kernels 0.17.
141+
> Use [`~kernels.LocalLayerRepository`] instead.
142+
120143
Args:
121144
repo_path (`Path`):
122145
The local repository containing the layer.
@@ -143,6 +166,12 @@ def __init__(
143166
*,
144167
func_name: str,
145168
):
169+
warnings.warn(
170+
"LocalFuncRepository is deprecated and will be removed in kernels 0.17. Use LocalLayerRepository instead.",
171+
DeprecationWarning,
172+
stacklevel=2,
173+
)
174+
146175
self._repo_path = repo_path
147176
self.func_name = func_name
148177

@@ -176,6 +205,10 @@ def use_kernel_func_from_hub(func_name: str):
176205
kernelized, it **must** be a member of another `torch.nn.Module` that is
177206
part of the model (see the example).
178207
208+
> [!WARNING]
209+
> `use_kernel_func_from_hub` is deprecated and will be removed in kernels 0.17.
210+
> Use [`~kernels.use_kernel_forward_from_hub`] instead.
211+
179212
Args:
180213
func_name (`str`):
181214
The name of the function name to use for kernel lookup in registered mappings.
@@ -210,13 +243,13 @@ def forward(self, x):
210243
# model = kernelize(model, mode=Mode.TRAINING | Mode.TORCH_COMPILE, device="cuda")
211244
```
212245
"""
246+
warnings.warn(
247+
"use_kernel_func_from_hub is deprecated and will be removed in kernels 0.17. Use [`use_kernel_forward_from_hub`] instead.",
248+
DeprecationWarning,
249+
stacklevel=2,
250+
)
213251

214-
def decorator(func):
215-
Func = _create_func_module(func)
216-
Func.kernel_layer_name = func_name
217-
return Func()
218-
219-
return decorator
252+
return use_kernel_forward_from_hub(func_name)
220253

221254

222255
class LockedFuncRepository:
@@ -225,6 +258,18 @@ class LockedFuncRepository:
225258
226259
In contrast to `FuncRepository`, this class uses repositories that
227260
are locked inside a project.
261+
262+
> [!WARNING]
263+
> `LockedFuncRepository` is deprecated and will be removed in kernels 0.17.
264+
> Use [`~kernels.LockedLayerRepository`] instead.
265+
266+
Args:
267+
repo_id (`str`): The Hub repository containing the function.
268+
lockfile (`Path`, *optional*): Path to the lockfile. If not provided,
269+
the lockfile will be inferred from the caller's context.
270+
func_name (`str`): The name of the function within the kernel repository.
271+
trust_remote_code (`bool`, *optional*, defaults to `False`):
272+
Whether to allow loading kernels from untrusted organisations.
228273
"""
229274

230275
def __init__(
@@ -238,14 +283,13 @@ def __init__(
238283
"""
239284
Construct a function repository.
240285
241-
Args:
242-
repo_id (`str`): The Hub repository containing the function.
243-
lockfile (`Path`, *optional*): Path to the lockfile. If not provided,
244-
the lockfile will be inferred from the caller's context.
245-
func_name (`str`): The name of the function within the kernel repository.
246-
trust_remote_code (`bool`, *optional*, defaults to `False`):
247-
Whether to allow loading kernels from untrusted organisations.
248286
"""
287+
warnings.warn(
288+
"LockedFuncRepository is deprecated and will be removed in kernels 0.17. Use LockedLayerRepository instead.",
289+
DeprecationWarning,
290+
stacklevel=2,
291+
)
292+
249293
self._repo_id = repo_id
250294
self._lockfile = lockfile
251295
self.func_name = func_name
@@ -265,7 +309,11 @@ def _resolve_revision(self) -> str:
265309
return locked_sha
266310

267311
def load(self) -> Type["nn.Module"]:
268-
kernel = get_kernel(repo_id=self._repo_id, revision=self._revision, trust_remote_code=self._trust_remote_code)
312+
kernel = get_kernel(
313+
repo_id=self._repo_id,
314+
revision=self._revision,
315+
trust_remote_code=self._trust_remote_code,
316+
)
269317
return _get_kernel_func(self, kernel)
270318

271319
def __eq__(self, other):
@@ -290,27 +338,3 @@ def _get_kernel_func(repo: FuncRepositoryProtocol, kernel: ModuleType) -> Type["
290338
raise ValueError(f"Function `{repo.func_name}` not found in `{repo}`")
291339

292340
return _create_func_module(func)
293-
294-
295-
def _create_func_module(func: Callable) -> Type["nn.Module"]:
296-
from torch import nn
297-
298-
class Func(nn.Module):
299-
# Same flags as possible with normal `nn.Module` objects that are exchanged
300-
can_torch_compile = getattr(func, "can_torch_compile", False)
301-
has_backward = getattr(func, "has_backward", True)
302-
303-
def forward(self, *args, **kwargs):
304-
return func(*args, **kwargs)
305-
306-
# Use function signature with args prepended by self to support
307-
# module validation.
308-
func_sig = inspect.signature(func)
309-
new_args = [Parameter("self", Parameter.POSITIONAL_OR_KEYWORD)]
310-
new_args.extend(func_sig.parameters.values())
311-
Func.forward.__signature__ = Signature( # type: ignore[attr-defined]
312-
parameters=new_args,
313-
return_annotation=func_sig.return_annotation,
314-
)
315-
316-
return Func

kernels/src/kernels/layer/kernelize.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -258,10 +258,18 @@ def forward(self, x: torch.Tensor) -> torch.Tensor:
258258

259259
for _, module in model.named_modules():
260260
module_class = type(module)
261-
if not hasattr(module_class, "kernel_layer_name"):
262-
continue
263261

264-
kernelize_layer(module, mode=mode, device_type=device_type, use_fallback=use_fallback)
262+
# Kernel functions attached to a module through `use_kernelized_func`.
263+
for kernel_func in getattr(module, "_kernel_funcs", {}).values():
264+
kernelize_layer(
265+
kernel_func,
266+
mode=mode,
267+
device_type=device_type,
268+
use_fallback=use_fallback,
269+
)
270+
271+
if hasattr(module_class, "kernel_layer_name"):
272+
kernelize_layer(module, mode=mode, device_type=device_type, use_fallback=use_fallback)
265273

266274
return model
267275

0 commit comments

Comments
 (0)