Skip to content

Commit 84fced9

Browse files
committed
kernels: add a function to get all kernelized layers in a model
This change adds a function to get all the kernelized layers and functions in a model, along with the repository the kernel was retrieved from. In order to do so, we add `_kernel_repo` to kernelized layers. One annoyance is that to keep layers pickable, we need to deal with the Rust data structures that are used inside the repo information. To do so, we use the existing JSON (de)serialization in pickling. Example use: ```python from kernels import get_kernelized_layers kernelized = get_kernelized_layers(model) kernelized.layers {'layers.0.mlp.act_fn': KernelizedLayer(module=..., repo=LayerRepository(...))} kernelized.funcs {('layers.0.attn', 'apply_rotary_pos_emb'): KernelizedFunc(module=..., repo=FuncRepository(...))} ```
1 parent a423dd8 commit 84fced9

8 files changed

Lines changed: 403 additions & 3 deletions

File tree

‎docs/source/api/layers.md‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@
3434

3535
[[autodoc]] kernels.kernelize
3636

37+
### get_kernelized_layers
38+
39+
[[autodoc]] kernels.get_kernelized_layers
40+
3741
## Classes
3842

3943
### Device
@@ -52,6 +56,18 @@
5256

5357
[[autodoc]] kernels.Mode
5458

59+
### KernelizedLayers
60+
61+
[[autodoc]] kernels.KernelizedLayers
62+
63+
### KernelizedLayer
64+
65+
[[autodoc]] kernels.KernelizedLayer
66+
67+
### KernelizedFunc
68+
69+
[[autodoc]] kernels.KernelizedFunc
70+
5571
### FuncRepository
5672

5773
[[autodoc]] kernels.FuncRepository

‎docs/source/layers.md‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,3 +429,21 @@ with use_kernel_mapping(
429429
):
430430
kernelize(model, mode=Mode.INFERENCE)
431431
```
432+
433+
## Inspecting a kernelized model
434+
435+
To check which layers are currently using a kernel, use
436+
[`~kernels.get_kernelized_layers`]. It returns a [`~kernels.KernelizedLayers`]
437+
object that contains a dict `layers` with kernelized layers and a dict
438+
`funcs` with kernelized functions. Each entry holds the kernelized module
439+
and the repository that its kernel was retrieved from:
440+
441+
```python
442+
from kernels import get_kernelized_layers
443+
444+
kernelized = get_kernelized_layers(model)
445+
kernelized.layers
446+
# {'layers.0.mlp.act_fn': KernelizedLayer(module=..., repo=LayerRepository(...))}
447+
kernelized.funcs
448+
# {('layers.0.attn', 'apply_rotary_pos_emb'): KernelizedFunc(module=..., repo=FuncRepository(...))}
449+
```

‎kernels/src/kernels/__init__.py‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
CUDAProperties,
1414
Device,
1515
FuncRepository,
16+
KernelizedFunc,
17+
KernelizedLayer,
18+
KernelizedLayers,
1619
KernelizeFallback,
1720
LayerRepository,
1821
LocalFuncRepository,
@@ -21,6 +24,7 @@
2124
LockedLayerRepository,
2225
Mode,
2326
ROCMProperties,
27+
get_kernelized_layers,
2428
kernelize,
2529
register_kernel_mapping,
2630
replace_kernel_forward_from_hub,
@@ -50,6 +54,9 @@
5054
"CUDAProperties",
5155
"Device",
5256
"KernelizeFallback",
57+
"KernelizedFunc",
58+
"KernelizedLayer",
59+
"KernelizedLayers",
5360
"ROCMProperties",
5461
"FuncRepository",
5562
"LayerRepository",
@@ -66,6 +73,7 @@
6673
"get_kernel",
6774
"get_kernel_dep",
6875
"get_kernel_variants",
76+
"get_kernelized_layers",
6977
"get_loaded_kernels",
7078
"get_local_kernel",
7179
"get_locked_kernel",

‎kernels/src/kernels/layer/__init__.py‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@
55
LockedFuncRepository,
66
use_kernel_func_from_hub,
77
)
8+
from .inspect import (
9+
KernelizedFunc,
10+
KernelizedLayer,
11+
KernelizedLayers,
12+
get_kernelized_layers,
13+
)
814
from .kernelize import (
915
kernelize,
1016
register_kernel_mapping,
@@ -25,6 +31,9 @@
2531
"CUDAProperties",
2632
"Device",
2733
"KernelizeFallback",
34+
"KernelizedFunc",
35+
"KernelizedLayer",
36+
"KernelizedLayers",
2837
"ROCMProperties",
2938
"FuncRepository",
3039
"LayerRepository",
@@ -33,6 +42,7 @@
3342
"LockedFuncRepository",
3443
"LockedLayerRepository",
3544
"Mode",
45+
"get_kernelized_layers",
3646
"kernelize",
3747
"register_kernel_mapping",
3848
"replace_kernel_forward_from_hub",

‎kernels/src/kernels/layer/func.py‎

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,15 @@
1515
get_local_kernel,
1616
)
1717
from ..locking import (
18+
_get_locked_kernel_revision,
1819
get_caller_locked_kernel_revision,
1920
get_locked_kernel_revision,
2021
)
2122
from ..validate import AllKernelValidator, AllMetadataValidator, default_kernel_validators, default_metadata_validators
22-
from .layer import _create_func_module, use_kernel_forward_from_hub
23+
from .layer import (
24+
_create_func_module,
25+
use_kernel_forward_from_hub,
26+
)
2327
from .repos import RepositoryProtocol
2428

2529
if TYPE_CHECKING:
@@ -361,6 +365,24 @@ def __str__(self) -> str:
361365
commit = self.kernel_locks[self.kernel_dep].commit
362366
return f"`{self._repo_id}` (revision: {commit}), function `{self.func_name}`"
363367

368+
def __getstate__(self):
369+
# `kernel_locks` is a Rust object that does not support pickling. So,
370+
# serialize it through JSON. `kernel_dep` is derived from it.
371+
return {
372+
"repo_id": self._repo_id,
373+
"lockfile": self._lockfile,
374+
"func_name": self.func_name,
375+
"trust_remote_code": self._trust_remote_code,
376+
"kernel_locks": self.kernel_locks.to_json(),
377+
}
378+
379+
def __setstate__(self, state: dict):
380+
self._repo_id = state["repo_id"]
381+
self._lockfile = state["lockfile"]
382+
self.func_name = state["func_name"]
383+
self._trust_remote_code = state["trust_remote_code"]
384+
self.kernel_locks, self.kernel_dep = _get_locked_kernel_revision(state["repo_id"], state["kernel_locks"])
385+
364386

365387
def _get_kernel_func(repo: FuncRepositoryProtocol, kernel: ModuleType) -> Type["nn.Module"]:
366388
func = getattr(kernel, repo.func_name, None)
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
from __future__ import annotations
2+
3+
from dataclasses import dataclass, field
4+
from typing import TYPE_CHECKING
5+
6+
from .repos import RepositoryProtocol
7+
8+
if TYPE_CHECKING:
9+
from torch import nn
10+
11+
12+
@dataclass(frozen=True)
13+
class KernelizedLayer:
14+
"""
15+
A kernelized layer and the repository that its kernel was retrieved from.
16+
17+
Attributes:
18+
module (`nn.Module`):
19+
The kernelized layer.
20+
repo (`RepositoryProtocol`):
21+
The repository that the kernel was retrieved from.
22+
"""
23+
24+
module: nn.Module
25+
repo: RepositoryProtocol
26+
27+
28+
@dataclass(frozen=True)
29+
class KernelizedFunc:
30+
"""
31+
A kernelized function and the repository that its kernel was retrieved from.
32+
33+
Attributes:
34+
module (`nn.Module`):
35+
The module that wraps the kernelized function.
36+
repo (`RepositoryProtocol`):
37+
The repository that the kernel was retrieved from.
38+
"""
39+
40+
module: nn.Module
41+
repo: RepositoryProtocol
42+
43+
44+
@dataclass(frozen=True)
45+
class KernelizedLayers:
46+
"""
47+
The kernels that are in use by a kernelized model, as returned by
48+
[`~kernels.get_kernelized_layers`].
49+
50+
Attributes:
51+
layers (`dict[str, KernelizedLayer]`):
52+
A mapping of each kernelized layer name to the layer and its repository. The
53+
names are the same as returned by Torch's `named_modules`.
54+
funcs (`dict[tuple[str, str], KernelizedFunc]`):
55+
A mapping of each kernelized function to the function and its repository. The
56+
key of the mapping is a tuple of the layer name that the function is attached
57+
to and the name of the function itself.
58+
"""
59+
60+
layers: dict[str, KernelizedLayer] = field(default_factory=dict)
61+
funcs: dict[tuple[str, str], KernelizedFunc] = field(default_factory=dict)
62+
63+
64+
def get_kernelized_layers(model: "nn.Module") -> KernelizedLayers:
65+
"""
66+
Return the repositories of the kernel layers and functions that are used by the model.
67+
68+
Args:
69+
model (`nn.Module`):
70+
The kernelized model.
71+
72+
Returns:
73+
[`~kernels.KernelizedLayers`]: The kernelized layers.
74+
75+
Example:
76+
```python
77+
import torch
78+
import torch.nn as nn
79+
import torch.nn.functional as F
80+
81+
from kernels import LayerRepository, Mode, get_kernelized_layers, kernelize
82+
from kernels import use_kernel_forward_from_hub, use_kernel_mapping
83+
84+
@use_kernel_forward_from_hub("SiluAndMul")
85+
class SiluAndMul(nn.Module):
86+
def forward(self, x: torch.Tensor) -> torch.Tensor:
87+
d = x.shape[-1] // 2
88+
return F.silu(x[..., :d]) * x[..., d:]
89+
90+
mapping = {
91+
"SiluAndMul": {
92+
"cuda": LayerRepository(
93+
repo_id="kernels-community/activation",
94+
layer_name="SiluAndMul",
95+
version=1,
96+
)
97+
}
98+
}
99+
100+
class MLP(nn.Module):
101+
def __init__(self):
102+
super().__init__()
103+
self.up_proj = nn.Linear(1024, 2048)
104+
self.act_fn = SiluAndMul()
105+
106+
def forward(self, x: torch.Tensor) -> torch.Tensor:
107+
return self.act_fn(self.up_proj(x))
108+
109+
model = MLP().to("cuda")
110+
111+
with use_kernel_mapping(mapping):
112+
model = kernelize(model, mode=Mode.INFERENCE)
113+
114+
kernelized = get_kernelized_layers(model)
115+
print(kernelized.layers)
116+
# {'act_fn': KernelizedLayer(module=..., repo=LayerRepository(...))}
117+
print(kernelized.funcs)
118+
# {} (no kernel functions are attached to this model)
119+
```
120+
"""
121+
kernelized = KernelizedLayers()
122+
123+
for module_name, module in model.named_modules():
124+
repo = module.__dict__.get("_kernel_repo")
125+
if repo is not None:
126+
kernelized.layers[module_name] = KernelizedLayer(module, repo)
127+
128+
# Kernel functions attached through `use_kernelized_func` are plain
129+
# attributes, so they are not part of `named_modules`.
130+
for func_name, kernel_func in getattr(module, "_kernel_funcs", {}).items():
131+
repo = kernel_func.__dict__.get("_kernel_repo")
132+
if repo is not None:
133+
kernelized.funcs[(module_name, func_name)] = KernelizedFunc(kernel_func, repo)
134+
135+
return kernelized

‎kernels/src/kernels/layer/layer.py‎

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
get_local_kernel,
2525
)
2626
from ..locking import (
27+
_get_locked_kernel_revision,
2728
get_caller_locked_kernel_revision,
2829
get_locked_kernel_revision,
2930
)
@@ -308,6 +309,24 @@ def __str__(self) -> str:
308309
commit = self.kernel_locks[self.kernel_dep].commit
309310
return f"`{self._repo_id}` (revision: {commit}), layer `{self.layer_name}`)"
310311

312+
def __getstate__(self):
313+
# `kernel_locks` is a Rust object that does not support pickling. So,
314+
# serialize it through JSON. `kernel_dep` is derived from it.
315+
return {
316+
"repo_id": self._repo_id,
317+
"lockfile": self._lockfile,
318+
"layer_name": self.layer_name,
319+
"trust_remote_code": self._trust_remote_code,
320+
"kernel_locks": self.kernel_locks.to_json(),
321+
}
322+
323+
def __setstate__(self, state: dict):
324+
self._repo_id = state["repo_id"]
325+
self._lockfile = state["lockfile"]
326+
self.layer_name = state["layer_name"]
327+
self._trust_remote_code = state["trust_remote_code"]
328+
self.kernel_locks, self.kernel_dep = _get_locked_kernel_revision(state["repo_id"], state["kernel_locks"])
329+
311330

312331
def replace_kernel_forward_from_hub(cls, layer_name: str, condition: Callable[["nn.Module"], bool] | None = None):
313332
"""
@@ -626,6 +645,7 @@ def kernelize_layer(module: "nn.Module", *, mode: Mode, device_type: Device, use
626645
module=module,
627646
layer=layer,
628647
mode=mode,
648+
repo=repo,
629649
use_fallback=use_fallback,
630650
)
631651

@@ -688,6 +708,7 @@ def _conditionally_replace_forward(
688708
module: "nn.Module",
689709
layer: Type["nn.Module"],
690710
mode: Mode,
711+
repo: RepositoryProtocol,
691712
use_fallback: KernelizeFallback,
692713
):
693714
module_class = type(module)
@@ -710,12 +731,23 @@ def _conditionally_replace_forward(
710731
else:
711732
raise ValueError(f"Available kernel does not support mode: {mode}")
712733
else:
713-
_replace_forward(module, layer)
734+
_replace_forward(module, layer, repo=repo)
714735

715736

716-
def _replace_forward(module: "nn.Module", layer: Type["nn.Module"]):
737+
def _replace_forward(
738+
module: "nn.Module",
739+
layer: Type["nn.Module"],
740+
*,
741+
repo: RepositoryProtocol | None = None,
742+
):
717743
module.forward = MethodType(layer.forward, module) # type: ignore[method-assign]
718744

745+
if repo is None:
746+
# No repository, typically when reverting to the original forward.
747+
module.__dict__.pop("_kernel_repo", None)
748+
else:
749+
module.__dict__["_kernel_repo"] = repo
750+
719751

720752
def _validate_layer_has_mode(
721753
*,

0 commit comments

Comments
 (0)