feat: experimental kernel porting tool - #776
Conversation
Coverage report —
|
| Name | Stmts | Miss | Cover | Missing |
|---|---|---|---|---|
| src/kernels/__init__.py | 14 | 0 | 100% | |
| src/kernels/_system.py | 6 | 1 | 83% | 10 |
| src/kernels/_versions.py | 78 | 9 | 88% | 47, 53-54, 57-58, 97, 119, 130, 136 |
| src/kernels/backends.py | 212 | 62 | 71% | 40, 44, 48-51, 68, 90, 108, 117, 121, 125-127, 148, 157, 161, 165-167, 188, 199, 201, 208-211, 224, 228, 232-252, 260, 283-303 |
| src/kernels/compat.py | 8 | 1 | 88% | 5 |
| src/kernels/deps.py | 67 | 1 | 99% | 57 |
| src/kernels/hf_hub.py | 62 | 3 | 95% | 18, 20, 114 |
| src/kernels/importer.py | 50 | 3 | 94% | 110, 114, 117 |
| src/kernels/install.py | 21 | 7 | 67% | 78-102 |
| src/kernels/layer/__init__.py | 6 | 0 | 100% | |
| src/kernels/layer/_interval_tree.py | 103 | 4 | 96% | 23, 52, 147, 150 |
| src/kernels/layer/device.py | 48 | 14 | 71% | 42, 47-49, 91, 96-98, 101, 149, 152, 155-157 |
| src/kernels/layer/func.py | 86 | 6 | 93% | 90, 121, 193, 313, 338, 366 |
| src/kernels/layer/globals.py | 5 | 0 | 100% | |
| src/kernels/layer/kernelize.py | 74 | 8 | 89% | 255, 281, 289-290, 296, 300, 316-318 |
| src/kernels/layer/layer.py | 215 | 15 | 93% | 177, 222, 247, 365, 445-446, 458, 467, 475, 486, 515, 519, 532, 585, 615 |
| src/kernels/layer/mode.py | 14 | 0 | 100% | |
| src/kernels/layer/repos.py | 144 | 42 | 71% | 27, 33, 36-43, 63-64, 70, 73-76, 90, 94, 103-104, 110, 113-116, 123-124, 130, 133-136, 143-144, 150, 153-156, 163-164, 170, 173-176, 257 |
| src/kernels/load.py | 61 | 2 | 97% | 297, 335 |
| src/kernels/locking.py | 89 | 64 | 28% | 35-83, 91-98, 102-125, 137, 152-159, 165-175, 179-186 |
| src/kernels/python_deps.py | 58 | 6 | 90% | 59-60, 64-65, 101, 104 |
| src/kernels/resolver.py | 135 | 2 | 99% | 174, 180 |
| src/kernels/status.py | 49 | 2 | 96% | 23, 81 |
| src/kernels/variants.py | 278 | 19 | 93% | 64, 95, 116, 146, 255-256, 298-301, 303, 387-394, 400-406, 437-443, 455-461 |
| src/kernels/verify.py | 88 | 1 | 99% | 32 |
| TOTAL | 1971 | 272 | 86% |
Updated by the Test kernels workflow on commit 286cbb294d374287467263c2fe9f80320c4f9b4b.
We might want to elaborate on what overlay means in this context.
Is the manual specification for
How are those pins derived?
Do the users have to specify More comments
|
the docs on the readme may already fill this need https://github.com/huggingface/kernels/blob/b7062eed8821f6a1235680844ad4bbc4711d7a7c/kernel-port/README.md#overlay copied for reference Note
|
Very good idea, IMO. But would we have a simple way to quickly test the correctness of its implementation? Not a blocker but I think we should strive for simplicity here.
Maybe we need to distinguish between required pins and optional pins?
Cool, that works for me!
Not sure if I fully understood it. Why would it differ for an external repo from
Oh okay. I was under the impression that without the |
| # Local packages/hooks. | ||
| kernel-builder = final.callPackage ./pkgs/kernel-builder { inherit builderProvenance; }; | ||
|
|
||
| kernel-port = final.callPackage ./pkgs/kernel-port { }; |
There was a problem hiding this comment.
Any reasoning behind making it a part of our nix-builder? For future CI?
sayakpaul
left a comment
There was a problem hiding this comment.
Thanks just left a bunch of comments. I think we are headed in a good direction. Once this takes a bit more shape, we could think about how we wire this in the CI, etc.
sayakpaul
left a comment
There was a problem hiding this comment.
Left some comments. Thanks for the further updates!
Apart from the comments, I think we should log the dirty status in the port provenance.
| @@ -0,0 +1,65 @@ | |||
| use anyhow::{Context, Result, bail}; | |||
There was a problem hiding this comment.
Do we require users to always specify a SHA from which they would like to start the port? It could be beneficial to also let them specify a release tag or something so that one could say we're up-to-date upto this tag (particularly helpful for release tags).
But not strongly opinionated about this, definitely.
| @@ -0,0 +1,616 @@ | |||
| # kernel-port | |||
There was a problem hiding this comment.
README is fine but we should find a way to make this available in our docs so that it's discoverable (particularly helpful for agents). Cc: @stevhliu here.
| [package] | ||
| name = "kernel-port" | ||
| version = "0.1.0" | ||
| edition = "2024" |
| cargoTestFlags = cargoFlags; | ||
|
|
||
| meta = { | ||
| description = "Port third-party kernels to the Hugging Face kernel layout"; |
There was a problem hiding this comment.
| description = "Port third-party kernels to the Hugging Face kernel layout"; | |
| description = "Port third-party kernels to the Hugging Face Kernels layout"; |
| @@ -0,0 +1,992 @@ | |||
| // Python edits go through libcst, so comments, quoting and layout outside the | |||
There was a problem hiding this comment.
What is the formatting situation here? Do we follow it from upstream? In some of the port PRs on kernels-community, we saw a significant amount of formatting-related changes.
| - Every op is built before the first one runs, so an argument typo or a bad glob anywhere fails the recipe before anything is mutated. | ||
| - After the last op, every added or modified Python file must still parse. | ||
| - No absolute in-package import may remain under `torch-ext`. The Hub loads a kernel under a build-variant directory name, so only relative intra-package imports resolve at run time. | ||
| - `--out` runs write a `.port-provenance.json`: recipe hash, runner version, pinned sources, output tree hash. All deterministic, so the file reproduces byte-for-byte. |
There was a problem hiding this comment.
We're doing port-provenance.json right?
Warning
This is an experiment/draft. The recipe language, the op set, and the CLI are all subject to change without notice.
this pr adds
kernel-port, an experimental tool for porting kernel repos into the kernel-builder layout by running a recipe instead of doing it by hand.the idea is that porting an existing kernel to the kernel-builder is a set of deterministic rewrites/restructuring. a recipe is a list of those operations, which has the benefit of being able to be checked into the repo and provide a way to deterministically reproduce given a specific upstream commit.
one of the difficulties of maintaining a port is that the upstream repo can drift and currently keeping the port in sync is a manual process.
recipes are a way to codify the porting process, so that if the upstream repo drifts we can simply bump the commit in the recipe and re-run the porting process. if the upstream repo has changed in a way that breaks the porting process, the recipe will fail to apply and we can fix it before continuing.
a port is a
port.kdlrecipe plus an overlay dir of checked in files. same pins + same recipe gives a byte identical tree every time, and every op hard fails on drift rather than silently porting the wrong thing.recipes are kdl 2.0 documents, one node per op:
there are 15 ops (
source,vendor,prune,delete,move,overlay,replace,strip_suffix,expect,convert_import,remap_module,relativize_imports,ensure_init,kernel,manifest). python rewrites go through libcst so comments and formatting are preserved byte for byte.build.tomlis always generated by themanifestop, never overlaid.the pins are the whole point.
count=,files=andchanges=are literals that have to match exactly, so a new file upstream cannot be rewritten without someone looking at it:you can try an op without a checkout at all,
-etakes the recipe inline and--file path=contentbuilds the input tree in memorythe readme has a cookbook with one runnable command per op, plus the full arg list and failure modes for each. every example in it was run and pasted, not written by hand.
***NEXT STEPS are to target a JIT and AOT kernel in the kernels-community and experiment using this tool