From 5b0a046fdbe6f602c10c959d6eab5f30c8dc02fe Mon Sep 17 00:00:00 2001 From: sayakpaul Date: Wed, 14 Jan 2026 10:54:52 +0530 Subject: [PATCH 01/23] start kernels card generation. --- example_usage.py | 36 ++++++++ src/kernels/card_template.md | 26 ++++++ src/kernels/kernel_card_utils.py | 136 +++++++++++++++++++++++++++++++ 3 files changed, 198 insertions(+) create mode 100644 example_usage.py create mode 100644 src/kernels/card_template.md create mode 100644 src/kernels/kernel_card_utils.py diff --git a/example_usage.py b/example_usage.py new file mode 100644 index 00000000..afb04dde --- /dev/null +++ b/example_usage.py @@ -0,0 +1,36 @@ +from pathlib import Path +from kernels.kernel_card_utils import _load_or_create_model_card, _update_model_card_usage +import argparse + + +def main(args): + kernel_dir = Path(args.kernels_dir) + + # Repository ID on Hugging Face Hub + repo_id = "your-org/layer_norm" + + model_card = _load_or_create_model_card( + repo_id_or_path=repo_id, + kernel_description="A fast layer normalization kernel implementation.", + license="apache-2.0" + ) + + updated_card = _update_model_card_usage( + model_card=model_card, + local_path=kernel_dir, + repo_id=repo_id + ) + + card_path = args.card_path or "README.md" + updated_card.save(card_path) + print("Model card updated successfully!") + print("\nUpdated content preview:") + print(updated_card.content[:500] + "...") + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--kernels_dir", type=str, required=True, help="Path to the kernels source.") + parser.add_argument("--card_path", type=str, default=None, help="Path to save the card to.") + args = parser.parse_args() + + main(args) diff --git a/src/kernels/card_template.md b/src/kernels/card_template.md new file mode 100644 index 00000000..c139ead4 --- /dev/null +++ b/src/kernels/card_template.md @@ -0,0 +1,26 @@ +--- +{{ card_data }} +--- + + + +{{ model_description }} + +## How to use + +```python +# TODO: add an example code snippet for running this kernel +``` + +## Benchmarks + +[TODO: provide benchmarks if available] + +## Code source + +[TODO: provide original code source and other relevant citations if available] + +## Notes + +[TODO: provide additional notes about this kernel if needed] diff --git a/src/kernels/kernel_card_utils.py b/src/kernels/kernel_card_utils.py new file mode 100644 index 00000000..25bf5bae --- /dev/null +++ b/src/kernels/kernel_card_utils.py @@ -0,0 +1,136 @@ +import ast +import re +from pathlib import Path + +from huggingface_hub import ModelCard, ModelCardData +from huggingface_hub.errors import EntryNotFoundError, RepositoryNotFoundError + +MODEL_CARD_TEMPLATE_PATH = Path(__file__).parent / "card_template.md" +DESCRIPTION = """ +This is the repository card of {repo_id} that has been pushed on the Hub. It was built to be used with the [`kernels` library](https://github.com/huggingface/kernels). This card was automatically generated. +""" +EXAMPLE_CODE = """```python +# make sure `kernels` is installed: `pip install -U kernels` +from kernels import get_kernel + +kernel_module = get_kernel("{repo_id}") +{func_name} = kernel_module.{func_name} + +{func_name}(...) +```""" + +is_jinja_available = False +try: + import jinja2 + + is_jinja_available = True +except ImportError: + pass + + +def _load_or_create_model_card( + repo_id_or_path: str = None, + token: str | None = None, + kernel_description: str | None = None, + license: str | None = None, +) -> ModelCard: + """TODO""" + if not is_jinja_available: + raise ValueError( + "Modelcard rendering is based on Jinja templates." + " Please make sure to have `jinja` installed before using `load_or_create_model_card`." + " To install it, please run `pip install Jinja2`." + ) + + try: + # Check if the model card is present on the remote repo + model_card = ModelCard.load(repo_id_or_path, token=token) + except (EntryNotFoundError, RepositoryNotFoundError): + # Otherwise create a model card from template + kernel_description = kernel_description or DESCRIPTION + model_card = ModelCard.from_template( + # Card metadata object that will be converted to YAML block + card_data=ModelCardData(license=license, library_name="kernels"), + template_path=MODEL_CARD_TEMPLATE_PATH, + model_description=kernel_description, + ) + + return model_card + + +def _find_torch_ext_init(local_path: str | Path) -> Path | None: + local_path = Path(local_path) + + torch_ext_dirs = list(local_path.rglob("torch-ext")) + + if not torch_ext_dirs: + return None + + for torch_ext_dir in torch_ext_dirs: + init_files = list(torch_ext_dir.rglob("__init__.py")) + # Filter to get the kernel's __init__.py (not nested test files, etc.) + for init_file in init_files: + # Should be directly under torch-ext/kernel_name/__init__.py + if init_file.parent.parent == torch_ext_dir: + return init_file + + return None + + +def _extract_function_from_all(init_file_path: Path) -> str | None: + try: + content = init_file_path.read_text() + + # Parse the file as an AST + tree = ast.parse(content) + + # Find the __all__ assignment + for node in ast.walk(tree): + if isinstance(node, ast.Assign): + for target in node.targets: + if isinstance(target, ast.Name) and target.id == "__all__": + # Extract the list values + if isinstance(node.value, ast.List): + for elt in node.value.elts: + if isinstance(elt, ast.Constant): + func_name = elt.value + # Skip module names, return the first function-like name + if not func_name.endswith("s") or "_" in func_name: + return func_name + # Fallback: return the first item if no function found + if node.value.elts: + first_elt = node.value.elts[0] + if isinstance(first_elt, ast.Constant): + return first_elt.value + return None + except Exception: + return None + + +def _update_model_card_usage( + model_card: ModelCard, + local_path: str | Path, + repo_id: str = "REPO_ID", +) -> ModelCard: + """TODO""" + init_file = _find_torch_ext_init(local_path) + + if not init_file: + return model_card + + func_name = _extract_function_from_all(init_file) + + if not func_name: + return model_card + + example_code = EXAMPLE_CODE.format(repo_id=repo_id, func_name=func_name) + + # Update the model card content + card_content = str(model_card.content) + pattern = r"(## How to use\s*\n\n)```python\n# TODO: add an example code snippet for running this kernel\n```" + + if re.search(pattern, card_content): + updated_content = re.sub(pattern, r"\1" + example_code, card_content) + model_card.content = updated_content + + return model_card From f0c87c258e0db7bf022e13118a3dc75be33cc61e Mon Sep 17 00:00:00 2001 From: sayakpaul Date: Wed, 14 Jan 2026 11:18:52 +0530 Subject: [PATCH 02/23] up --- example_usage.py | 14 +++----------- src/kernels/kernel_card_utils.py | 4 ++-- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/example_usage.py b/example_usage.py index afb04dde..25fd3bb8 100644 --- a/example_usage.py +++ b/example_usage.py @@ -6,20 +6,11 @@ def main(args): kernel_dir = Path(args.kernels_dir) - # Repository ID on Hugging Face Hub - repo_id = "your-org/layer_norm" - model_card = _load_or_create_model_card( - repo_id_or_path=repo_id, - kernel_description="A fast layer normalization kernel implementation.", - license="apache-2.0" + kernel_description=args.description, license="apache-2.0" ) - updated_card = _update_model_card_usage( - model_card=model_card, - local_path=kernel_dir, - repo_id=repo_id - ) + updated_card = _update_model_card_usage(model_card=model_card, local_path=kernel_dir) card_path = args.card_path or "README.md" updated_card.save(card_path) @@ -31,6 +22,7 @@ def main(args): parser = argparse.ArgumentParser() parser.add_argument("--kernels_dir", type=str, required=True, help="Path to the kernels source.") parser.add_argument("--card_path", type=str, default=None, help="Path to save the card to.") + parser.add_argument("--description", type=str, default=None) args = parser.parse_args() main(args) diff --git a/src/kernels/kernel_card_utils.py b/src/kernels/kernel_card_utils.py index 25bf5bae..21d16509 100644 --- a/src/kernels/kernel_card_utils.py +++ b/src/kernels/kernel_card_utils.py @@ -13,7 +13,7 @@ # make sure `kernels` is installed: `pip install -U kernels` from kernels import get_kernel -kernel_module = get_kernel("{repo_id}") +kernel_module = get_kernel("{repo_id}") # <- change the ID if needed {func_name} = kernel_module.{func_name} {func_name}(...) @@ -29,7 +29,7 @@ def _load_or_create_model_card( - repo_id_or_path: str = None, + repo_id_or_path: str = "REPO_ID", token: str | None = None, kernel_description: str | None = None, license: str | None = None, From 8b0aa5d3687179e911a9b8fd0c3ba1e8dc28e1db Mon Sep 17 00:00:00 2001 From: sayakpaul Date: Wed, 14 Jan 2026 13:57:19 +0530 Subject: [PATCH 03/23] address review feedback. --- src/kernels/kernel_card_utils.py | 38 ++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/src/kernels/kernel_card_utils.py b/src/kernels/kernel_card_utils.py index 21d16509..7acf7e25 100644 --- a/src/kernels/kernel_card_utils.py +++ b/src/kernels/kernel_card_utils.py @@ -21,7 +21,7 @@ is_jinja_available = False try: - import jinja2 + import jinja2 # noqa is_jinja_available = True except ImportError: @@ -61,20 +61,34 @@ def _load_or_create_model_card( def _find_torch_ext_init(local_path: str | Path) -> Path | None: local_path = Path(local_path) - torch_ext_dirs = list(local_path.rglob("torch-ext")) - - if not torch_ext_dirs: + build_toml_path = local_path / "build.toml" + if not build_toml_path.exists(): return None - for torch_ext_dir in torch_ext_dirs: - init_files = list(torch_ext_dir.rglob("__init__.py")) - # Filter to get the kernel's __init__.py (not nested test files, etc.) - for init_file in init_files: - # Should be directly under torch-ext/kernel_name/__init__.py - if init_file.parent.parent == torch_ext_dir: - return init_file + try: + # Import tomli for parsing TOML (Python 3.11+ has tomllib in stdlib) + try: + import tomllib + except ImportError: + import tomli as tomllib + + with open(build_toml_path, "rb") as f: + config = tomllib.load(f) + + # Get kernel name from general.name + kernel_name = config.get("general", {}).get("name") + if not kernel_name: + return None + + module_name = kernel_name.replace("-", "_") + init_file = local_path / "torch-ext" / module_name / "__init__.py" - return None + if init_file.exists(): + return init_file + + return None + except Exception: + return None def _extract_function_from_all(init_file_path: Path) -> str | None: From d5ee2e55b0ee5f7dc600b5d7f684bf79e47dc860 Mon Sep 17 00:00:00 2001 From: sayakpaul Date: Wed, 14 Jan 2026 14:32:48 +0530 Subject: [PATCH 04/23] support backend logging. --- example_usage.py | 3 +- src/kernels/card_template.md | 4 +++ src/kernels/kernel_card_utils.py | 50 +++++++++++++++++++++++++++----- 3 files changed, 48 insertions(+), 9 deletions(-) diff --git a/example_usage.py b/example_usage.py index 25fd3bb8..7905ddc9 100644 --- a/example_usage.py +++ b/example_usage.py @@ -1,5 +1,5 @@ from pathlib import Path -from kernels.kernel_card_utils import _load_or_create_model_card, _update_model_card_usage +from kernels.kernel_card_utils import _load_or_create_model_card, _update_model_card_usage, _update_model_card_backends import argparse @@ -11,6 +11,7 @@ def main(args): ) updated_card = _update_model_card_usage(model_card=model_card, local_path=kernel_dir) + updated_card = _update_model_card_backends(model_card=updated_card, local_path=kernel_dir) card_path = args.card_path or "README.md" updated_card.save(card_path) diff --git a/src/kernels/card_template.md b/src/kernels/card_template.md index c139ead4..e30b4895 100644 --- a/src/kernels/card_template.md +++ b/src/kernels/card_template.md @@ -13,6 +13,10 @@ should probably proofread and complete it, then remove this comment. --> # TODO: add an example code snippet for running this kernel ``` +## Supported backends + +[TODO: add the backends this kernel supports] + ## Benchmarks [TODO: provide benchmarks if available] diff --git a/src/kernels/kernel_card_utils.py b/src/kernels/kernel_card_utils.py index 7acf7e25..a801bd0d 100644 --- a/src/kernels/kernel_card_utils.py +++ b/src/kernels/kernel_card_utils.py @@ -34,7 +34,6 @@ def _load_or_create_model_card( kernel_description: str | None = None, license: str | None = None, ) -> ModelCard: - """TODO""" if not is_jinja_available: raise ValueError( "Modelcard rendering is based on Jinja templates." @@ -58,10 +57,10 @@ def _load_or_create_model_card( return model_card -def _find_torch_ext_init(local_path: str | Path) -> Path | None: +def _parse_build_toml(local_path: str | Path) -> dict | None: local_path = Path(local_path) - build_toml_path = local_path / "build.toml" + if not build_toml_path.exists(): return None @@ -73,8 +72,19 @@ def _find_torch_ext_init(local_path: str | Path) -> Path | None: import tomli as tomllib with open(build_toml_path, "rb") as f: - config = tomllib.load(f) + return tomllib.load(f) + except Exception: + return None + +def _find_torch_ext_init(local_path: str | Path) -> Path | None: + local_path = Path(local_path) + + config = _parse_build_toml(local_path) + if not config: + return None + + try: # Get kernel name from general.name kernel_name = config.get("general", {}).get("name") if not kernel_name: @@ -122,11 +132,8 @@ def _extract_function_from_all(init_file_path: Path) -> str | None: def _update_model_card_usage( - model_card: ModelCard, - local_path: str | Path, - repo_id: str = "REPO_ID", + model_card: ModelCard, local_path: str | Path, repo_id: str = "REPO_ID", ) -> ModelCard: - """TODO""" init_file = _find_torch_ext_init(local_path) if not init_file: @@ -148,3 +155,30 @@ def _update_model_card_usage( model_card.content = updated_content return model_card + + +def _update_model_card_backends(model_card: ModelCard, local_path: str | Path) -> ModelCard: + config = _parse_build_toml(local_path).get("general", {}) + if not config: + return model_card + + card_content = str(model_card.content) + + backends = config.get("backends") + if backends: + backends_list = "\n".join(f"- {backend}" for backend in backends) + pattern = r"(## Supported backends\s*\n\n)\[TODO: add the backends this kernel supports\]" + if re.search(pattern, card_content): + card_content = re.sub(pattern, r"\1" + backends_list, card_content) + + # TODO: should we consider making it a separate utility? + cuda_capabilities = config.get("cuda-capabilities") + if cuda_capabilities: + cuda_list = "\n".join(f"- {cap}" for cap in cuda_capabilities) + cuda_section = f"## CUDA Capabilities\n\n{cuda_list}\n\n" + pattern = r"(## Benchmarks)" + if re.search(pattern, card_content): + card_content = re.sub(pattern, cuda_section + r"\1", card_content) + + model_card.content = card_content + return model_card From c5deecd5394b8ad5556449a8ed41df7e3f6c795d Mon Sep 17 00:00:00 2001 From: sayakpaul Date: Wed, 14 Jan 2026 14:36:53 +0530 Subject: [PATCH 05/23] black --- src/kernels/kernel_card_utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/kernels/kernel_card_utils.py b/src/kernels/kernel_card_utils.py index a801bd0d..aa7cd3a1 100644 --- a/src/kernels/kernel_card_utils.py +++ b/src/kernels/kernel_card_utils.py @@ -21,7 +21,7 @@ is_jinja_available = False try: - import jinja2 # noqa + import jinja2 # noqa is_jinja_available = True except ImportError: @@ -132,7 +132,9 @@ def _extract_function_from_all(init_file_path: Path) -> str | None: def _update_model_card_usage( - model_card: ModelCard, local_path: str | Path, repo_id: str = "REPO_ID", + model_card: ModelCard, + local_path: str | Path, + repo_id: str = "REPO_ID", ) -> ModelCard: init_file = _find_torch_ext_init(local_path) From 00f970ad2ef9525426b1ad6c0a67f8f6ffdb5631 Mon Sep 17 00:00:00 2001 From: sayakpaul Date: Wed, 14 Jan 2026 17:00:31 +0530 Subject: [PATCH 06/23] up --- src/kernels/kernel_card_utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/kernels/kernel_card_utils.py b/src/kernels/kernel_card_utils.py index aa7cd3a1..b71c7f69 100644 --- a/src/kernels/kernel_card_utils.py +++ b/src/kernels/kernel_card_utils.py @@ -159,7 +159,9 @@ def _update_model_card_usage( return model_card -def _update_model_card_backends(model_card: ModelCard, local_path: str | Path) -> ModelCard: +def _update_model_card_backends( + model_card: ModelCard, local_path: str | Path +) -> ModelCard: config = _parse_build_toml(local_path).get("general", {}) if not config: return model_card From 23adda1d681657dfd6523286cb4136e200388e05 Mon Sep 17 00:00:00 2001 From: sayakpaul Date: Wed, 14 Jan 2026 17:16:13 +0530 Subject: [PATCH 07/23] make mypy happy. --- src/kernels/kernel_card_utils.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/kernels/kernel_card_utils.py b/src/kernels/kernel_card_utils.py index b71c7f69..71b34bab 100644 --- a/src/kernels/kernel_card_utils.py +++ b/src/kernels/kernel_card_utils.py @@ -50,7 +50,7 @@ def _load_or_create_model_card( model_card = ModelCard.from_template( # Card metadata object that will be converted to YAML block card_data=ModelCardData(license=license, library_name="kernels"), - template_path=MODEL_CARD_TEMPLATE_PATH, + template_path=str(MODEL_CARD_TEMPLATE_PATH), model_description=kernel_description, ) @@ -117,7 +117,7 @@ def _extract_function_from_all(init_file_path: Path) -> str | None: if isinstance(node.value, ast.List): for elt in node.value.elts: if isinstance(elt, ast.Constant): - func_name = elt.value + func_name = str(elt.value) # Skip module names, return the first function-like name if not func_name.endswith("s") or "_" in func_name: return func_name @@ -125,7 +125,7 @@ def _extract_function_from_all(init_file_path: Path) -> str | None: if node.value.elts: first_elt = node.value.elts[0] if isinstance(first_elt, ast.Constant): - return first_elt.value + return str(first_elt.value) return None except Exception: return None @@ -162,10 +162,12 @@ def _update_model_card_usage( def _update_model_card_backends( model_card: ModelCard, local_path: str | Path ) -> ModelCard: - config = _parse_build_toml(local_path).get("general", {}) + config = _parse_build_toml(local_path) if not config: return model_card + config = config.get("general", {}) + card_content = str(model_card.content) backends = config.get("backends") From 15d1382a5fca77a7e3e9967b8189bb9ae9f8a47d Mon Sep 17 00:00:00 2001 From: sayakpaul Date: Wed, 14 Jan 2026 17:18:00 +0530 Subject: [PATCH 08/23] compat tomllib --- src/kernels/kernel_card_utils.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/kernels/kernel_card_utils.py b/src/kernels/kernel_card_utils.py index 71b34bab..f142f762 100644 --- a/src/kernels/kernel_card_utils.py +++ b/src/kernels/kernel_card_utils.py @@ -2,6 +2,7 @@ import re from pathlib import Path +from .compat import tomllib from huggingface_hub import ModelCard, ModelCardData from huggingface_hub.errors import EntryNotFoundError, RepositoryNotFoundError @@ -65,12 +66,6 @@ def _parse_build_toml(local_path: str | Path) -> dict | None: return None try: - # Import tomli for parsing TOML (Python 3.11+ has tomllib in stdlib) - try: - import tomllib - except ImportError: - import tomli as tomllib - with open(build_toml_path, "rb") as f: return tomllib.load(f) except Exception: From 6c2109e53616141e8c25a18377363734f7ad5214 Mon Sep 17 00:00:00 2001 From: sayakpaul Date: Fri, 16 Jan 2026 10:59:47 +0530 Subject: [PATCH 09/23] implement force_update_content flag --- src/kernels/kernel_card_utils.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/kernels/kernel_card_utils.py b/src/kernels/kernel_card_utils.py index f142f762..c5f23524 100644 --- a/src/kernels/kernel_card_utils.py +++ b/src/kernels/kernel_card_utils.py @@ -34,6 +34,7 @@ def _load_or_create_model_card( token: str | None = None, kernel_description: str | None = None, license: str | None = None, + force_update_content: bool = False, ) -> ModelCard: if not is_jinja_available: raise ValueError( @@ -42,11 +43,15 @@ def _load_or_create_model_card( " To install it, please run `pip install Jinja2`." ) - try: - # Check if the model card is present on the remote repo - model_card = ModelCard.load(repo_id_or_path, token=token) - except (EntryNotFoundError, RepositoryNotFoundError): - # Otherwise create a model card from template + model_card = None + + if not force_update_content: + try: + model_card = ModelCard.load(repo_id_or_path, token=token) + except (EntryNotFoundError, RepositoryNotFoundError): + pass # Will create from template below + + if model_card is None: kernel_description = kernel_description or DESCRIPTION model_card = ModelCard.from_template( # Card metadata object that will be converted to YAML block @@ -154,9 +159,7 @@ def _update_model_card_usage( return model_card -def _update_model_card_backends( - model_card: ModelCard, local_path: str | Path -) -> ModelCard: +def _update_model_card_backends(model_card: ModelCard, local_path: str | Path) -> ModelCard: config = _parse_build_toml(local_path) if not config: return model_card From 667c14c0be9b07b40890e603b6dec1d868800739 Mon Sep 17 00:00:00 2001 From: sayakpaul Date: Fri, 16 Jan 2026 11:17:36 +0530 Subject: [PATCH 10/23] implement utility for license update --- example_usage.py | 11 ++++--- src/kernels/kernel_card_utils.py | 55 ++++++++++++++++++++------------ 2 files changed, 40 insertions(+), 26 deletions(-) diff --git a/example_usage.py b/example_usage.py index 7905ddc9..c9357486 100644 --- a/example_usage.py +++ b/example_usage.py @@ -1,21 +1,22 @@ from pathlib import Path -from kernels.kernel_card_utils import _load_or_create_model_card, _update_model_card_usage, _update_model_card_backends +from kernels.kernel_card_utils import _update_kernel_card_license, _load_or_create_kernel_card, _update_kernel_card_usage, _update_kernel_card_backends import argparse def main(args): kernel_dir = Path(args.kernels_dir) - model_card = _load_or_create_model_card( + kernel_card = _load_or_create_kernel_card( kernel_description=args.description, license="apache-2.0" ) - updated_card = _update_model_card_usage(model_card=model_card, local_path=kernel_dir) - updated_card = _update_model_card_backends(model_card=updated_card, local_path=kernel_dir) + updated_card = _update_kernel_card_usage(kernel_card=kernel_card, local_path=kernel_dir) + updated_card = _update_kernel_card_backends(kernel_card=updated_card, local_path=kernel_dir) + updated_card = _update_kernel_card_license(kernel_card, kernel_dir) card_path = args.card_path or "README.md" updated_card.save(card_path) - print("Model card updated successfully!") + print("Kernel card updated successfully!") print("\nUpdated content preview:") print(updated_card.content[:500] + "...") diff --git a/src/kernels/kernel_card_utils.py b/src/kernels/kernel_card_utils.py index c5f23524..272f1227 100644 --- a/src/kernels/kernel_card_utils.py +++ b/src/kernels/kernel_card_utils.py @@ -6,7 +6,7 @@ from huggingface_hub import ModelCard, ModelCardData from huggingface_hub.errors import EntryNotFoundError, RepositoryNotFoundError -MODEL_CARD_TEMPLATE_PATH = Path(__file__).parent / "card_template.md" +KERNEL_CARD_TEMPLATE_PATH = Path(__file__).parent / "card_template.md" DESCRIPTION = """ This is the repository card of {repo_id} that has been pushed on the Hub. It was built to be used with the [`kernels` library](https://github.com/huggingface/kernels). This card was automatically generated. """ @@ -19,6 +19,7 @@ {func_name}(...) ```""" +LIBRARY_NAME = "kernels" is_jinja_available = False try: @@ -29,7 +30,7 @@ pass -def _load_or_create_model_card( +def _load_or_create_kernel_card( repo_id_or_path: str = "REPO_ID", token: str | None = None, kernel_description: str | None = None, @@ -43,24 +44,24 @@ def _load_or_create_model_card( " To install it, please run `pip install Jinja2`." ) - model_card = None + kernel_card = None if not force_update_content: try: - model_card = ModelCard.load(repo_id_or_path, token=token) + kernel_card = ModelCard.load(repo_id_or_path, token=token) except (EntryNotFoundError, RepositoryNotFoundError): pass # Will create from template below - if model_card is None: + if kernel_card is None: kernel_description = kernel_description or DESCRIPTION - model_card = ModelCard.from_template( + kernel_card = ModelCard.from_template( # Card metadata object that will be converted to YAML block - card_data=ModelCardData(license=license, library_name="kernels"), - template_path=str(MODEL_CARD_TEMPLATE_PATH), + card_data=ModelCardData(license=license, library_name=LIBRARY_NAME), + template_path=str(KERNEL_CARD_TEMPLATE_PATH), model_description=kernel_description, ) - return model_card + return kernel_card def _parse_build_toml(local_path: str | Path) -> dict | None: @@ -131,42 +132,42 @@ def _extract_function_from_all(init_file_path: Path) -> str | None: return None -def _update_model_card_usage( - model_card: ModelCard, +def _update_kernel_card_usage( + kernel_card: ModelCard, local_path: str | Path, repo_id: str = "REPO_ID", ) -> ModelCard: init_file = _find_torch_ext_init(local_path) if not init_file: - return model_card + return kernel_card func_name = _extract_function_from_all(init_file) if not func_name: - return model_card + return kernel_card example_code = EXAMPLE_CODE.format(repo_id=repo_id, func_name=func_name) # Update the model card content - card_content = str(model_card.content) + card_content = str(kernel_card.content) pattern = r"(## How to use\s*\n\n)```python\n# TODO: add an example code snippet for running this kernel\n```" if re.search(pattern, card_content): updated_content = re.sub(pattern, r"\1" + example_code, card_content) - model_card.content = updated_content + kernel_card.content = updated_content - return model_card + return kernel_card -def _update_model_card_backends(model_card: ModelCard, local_path: str | Path) -> ModelCard: +def _update_kernel_card_backends(kernel_card: ModelCard, local_path: str | Path) -> ModelCard: config = _parse_build_toml(local_path) if not config: - return model_card + return kernel_card config = config.get("general", {}) - card_content = str(model_card.content) + card_content = str(kernel_card.content) backends = config.get("backends") if backends: @@ -184,5 +185,17 @@ def _update_model_card_backends(model_card: ModelCard, local_path: str | Path) - if re.search(pattern, card_content): card_content = re.sub(pattern, cuda_section + r"\1", card_content) - model_card.content = card_content - return model_card + kernel_card.content = card_content + return kernel_card + + +def _update_kernel_card_license(kernel_card: ModelCard, local_path: str | Path) -> ModelCard: + config = _parse_build_toml(local_path) + if not config: + return kernel_card + + existing_license = kernel_card.data.get("license", None) + license_from_config = config.get("license", None) + final_license = license_from_config or existing_license + kernel_card.data["license"] = final_license + return kernel_card From 8988ab6293d53241271a9f4cf1d5ee3e9d4082fc Mon Sep 17 00:00:00 2001 From: sayakpaul Date: Fri, 16 Jan 2026 11:26:12 +0530 Subject: [PATCH 11/23] implement. --- example_usage.py | 14 +++++++--- src/kernels/card_template.md | 4 +++ src/kernels/kernel_card_utils.py | 46 +++++++++++++++++++++++--------- 3 files changed, 48 insertions(+), 16 deletions(-) diff --git a/example_usage.py b/example_usage.py index c9357486..5cb41b42 100644 --- a/example_usage.py +++ b/example_usage.py @@ -1,16 +1,21 @@ from pathlib import Path -from kernels.kernel_card_utils import _update_kernel_card_license, _load_or_create_kernel_card, _update_kernel_card_usage, _update_kernel_card_backends +from kernels.kernel_card_utils import ( + _update_kernel_card_available_funcs, + _update_kernel_card_license, + _load_or_create_kernel_card, + _update_kernel_card_usage, + _update_kernel_card_backends, +) import argparse def main(args): kernel_dir = Path(args.kernels_dir) - kernel_card = _load_or_create_kernel_card( - kernel_description=args.description, license="apache-2.0" - ) + kernel_card = _load_or_create_kernel_card(kernel_description=args.description, license="apache-2.0") updated_card = _update_kernel_card_usage(kernel_card=kernel_card, local_path=kernel_dir) + updated_card = _update_kernel_card_available_funcs(kernel_card=kernel_card, local_path=kernel_dir) updated_card = _update_kernel_card_backends(kernel_card=updated_card, local_path=kernel_dir) updated_card = _update_kernel_card_license(kernel_card, kernel_dir) @@ -20,6 +25,7 @@ def main(args): print("\nUpdated content preview:") print(updated_card.content[:500] + "...") + if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("--kernels_dir", type=str, required=True, help="Path to the kernels source.") diff --git a/src/kernels/card_template.md b/src/kernels/card_template.md index e30b4895..975cab95 100644 --- a/src/kernels/card_template.md +++ b/src/kernels/card_template.md @@ -13,6 +13,10 @@ should probably proofread and complete it, then remove this comment. --> # TODO: add an example code snippet for running this kernel ``` +## Available functions + +[TODO: add the functions available through this kernel] + ## Supported backends [TODO: add the backends this kernel supports] diff --git a/src/kernels/kernel_card_utils.py b/src/kernels/kernel_card_utils.py index 272f1227..6efd8185 100644 --- a/src/kernels/kernel_card_utils.py +++ b/src/kernels/kernel_card_utils.py @@ -102,7 +102,7 @@ def _find_torch_ext_init(local_path: str | Path) -> Path | None: return None -def _extract_function_from_all(init_file_path: Path) -> str | None: +def _extract_functions_from_all(init_file_path: Path) -> list[str] | None: try: content = init_file_path.read_text() @@ -114,19 +114,14 @@ def _extract_function_from_all(init_file_path: Path) -> str | None: if isinstance(node, ast.Assign): for target in node.targets: if isinstance(target, ast.Name) and target.id == "__all__": - # Extract the list values + # Extract all list values if isinstance(node.value, ast.List): + functions = [] for elt in node.value.elts: if isinstance(elt, ast.Constant): func_name = str(elt.value) - # Skip module names, return the first function-like name - if not func_name.endswith("s") or "_" in func_name: - return func_name - # Fallback: return the first item if no function found - if node.value.elts: - first_elt = node.value.elts[0] - if isinstance(first_elt, ast.Constant): - return str(first_elt.value) + functions.append(func_name) + return functions if functions else None return None except Exception: return None @@ -142,11 +137,13 @@ def _update_kernel_card_usage( if not init_file: return kernel_card - func_name = _extract_function_from_all(init_file) + func_names = _extract_functions_from_all(init_file) - if not func_name: + if not func_names: return kernel_card + # Use the first function as an example + func_name = func_names[0] example_code = EXAMPLE_CODE.format(repo_id=repo_id, func_name=func_name) # Update the model card content @@ -160,6 +157,31 @@ def _update_kernel_card_usage( return kernel_card +def _update_kernel_card_available_funcs(kernel_card: ModelCard, local_path: str | Path) -> ModelCard: + init_file = _find_torch_ext_init(local_path) + + if not init_file: + return kernel_card + + func_names = _extract_functions_from_all(init_file) + + if not func_names: + return kernel_card + + # Format functions as a bulleted list + functions_list = "\n".join(f"- `{func}`" for func in func_names) + + # Update the model card content + card_content = str(kernel_card.content) + pattern = r"(## Available functions\s*\n\n)\[TODO: add the functions available through this kernel\]" + + if re.search(pattern, card_content): + updated_content = re.sub(pattern, r"\1" + functions_list, card_content) + kernel_card.content = updated_content + + return kernel_card + + def _update_kernel_card_backends(kernel_card: ModelCard, local_path: str | Path) -> ModelCard: config = _parse_build_toml(local_path) if not config: From da5a7ac314c2497025bc16779b78e0934599cbcd Mon Sep 17 00:00:00 2001 From: sayakpaul Date: Fri, 16 Jan 2026 11:29:47 +0530 Subject: [PATCH 12/23] format --- src/kernels/kernel_card_utils.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/kernels/kernel_card_utils.py b/src/kernels/kernel_card_utils.py index 6efd8185..c94684c1 100644 --- a/src/kernels/kernel_card_utils.py +++ b/src/kernels/kernel_card_utils.py @@ -157,7 +157,9 @@ def _update_kernel_card_usage( return kernel_card -def _update_kernel_card_available_funcs(kernel_card: ModelCard, local_path: str | Path) -> ModelCard: +def _update_kernel_card_available_funcs( + kernel_card: ModelCard, local_path: str | Path +) -> ModelCard: init_file = _find_torch_ext_init(local_path) if not init_file: @@ -182,7 +184,9 @@ def _update_kernel_card_available_funcs(kernel_card: ModelCard, local_path: str return kernel_card -def _update_kernel_card_backends(kernel_card: ModelCard, local_path: str | Path) -> ModelCard: +def _update_kernel_card_backends( + kernel_card: ModelCard, local_path: str | Path +) -> ModelCard: config = _parse_build_toml(local_path) if not config: return kernel_card @@ -211,7 +215,9 @@ def _update_kernel_card_backends(kernel_card: ModelCard, local_path: str | Path) return kernel_card -def _update_kernel_card_license(kernel_card: ModelCard, local_path: str | Path) -> ModelCard: +def _update_kernel_card_license( + kernel_card: ModelCard, local_path: str | Path +) -> ModelCard: config = _parse_build_toml(local_path) if not config: return kernel_card From 68638fb6a7d68bf601508f9b266b37e7fe0d891a Mon Sep 17 00:00:00 2001 From: sayakpaul Date: Mon, 19 Jan 2026 16:56:43 +0530 Subject: [PATCH 13/23] fix adding license. --- src/kernels/kernel_card_utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/kernels/kernel_card_utils.py b/src/kernels/kernel_card_utils.py index c94684c1..9c3dded0 100644 --- a/src/kernels/kernel_card_utils.py +++ b/src/kernels/kernel_card_utils.py @@ -191,11 +191,11 @@ def _update_kernel_card_backends( if not config: return kernel_card - config = config.get("general", {}) + general_config = config.get("general", {}) card_content = str(kernel_card.content) - backends = config.get("backends") + backends = general_config.get("backends") if backends: backends_list = "\n".join(f"- {backend}" for backend in backends) pattern = r"(## Supported backends\s*\n\n)\[TODO: add the backends this kernel supports\]" @@ -223,7 +223,7 @@ def _update_kernel_card_license( return kernel_card existing_license = kernel_card.data.get("license", None) - license_from_config = config.get("license", None) + license_from_config = config.get("general", {}).get("license", None) final_license = license_from_config or existing_license kernel_card.data["license"] = final_license return kernel_card From 081fdcf47a204c82f125c65b7464e3660c6c08cc Mon Sep 17 00:00:00 2001 From: sayakpaul Date: Mon, 19 Jan 2026 17:01:44 +0530 Subject: [PATCH 14/23] fix kernel card backend --- src/kernels/kernel_card_utils.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/kernels/kernel_card_utils.py b/src/kernels/kernel_card_utils.py index 9c3dded0..bb8d2a5d 100644 --- a/src/kernels/kernel_card_utils.py +++ b/src/kernels/kernel_card_utils.py @@ -203,7 +203,14 @@ def _update_kernel_card_backends( card_content = re.sub(pattern, r"\1" + backends_list, card_content) # TODO: should we consider making it a separate utility? - cuda_capabilities = config.get("cuda-capabilities") + kernel_configs = config.get("kernel", {}) + cuda_capabilities = [] + if kernel_configs: + for k in kernel_configs: + cuda_cap_for_config = kernel_configs[k].get("cuda-capabilities") + if cuda_cap_for_config: + cuda_capabilities.extend(cuda_cap_for_config) + cuda_capabilities = set(cuda_capabilities) if cuda_capabilities: cuda_list = "\n".join(f"- {cap}" for cap in cuda_capabilities) cuda_section = f"## CUDA Capabilities\n\n{cuda_list}\n\n" From 3ea7d88e34fef351b1101bdd313e2a14c52ca320 Mon Sep 17 00:00:00 2001 From: sayakpaul Date: Mon, 19 Jan 2026 18:49:21 +0530 Subject: [PATCH 15/23] black --- src/kernels/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/kernels/__init__.py b/src/kernels/__init__.py index 1df49193..997721bf 100644 --- a/src/kernels/__init__.py +++ b/src/kernels/__init__.py @@ -1,6 +1,5 @@ import importlib.metadata - __version__ = importlib.metadata.version("kernels") from kernels.layer import ( From ff155a50b2b06a85de6c1dd623e789e2a4e500a1 Mon Sep 17 00:00:00 2001 From: sayakpaul Date: Mon, 19 Jan 2026 18:52:27 +0530 Subject: [PATCH 16/23] up --- src/kernels/kernel_card_utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/kernels/kernel_card_utils.py b/src/kernels/kernel_card_utils.py index bb8d2a5d..f585131a 100644 --- a/src/kernels/kernel_card_utils.py +++ b/src/kernels/kernel_card_utils.py @@ -3,6 +3,7 @@ from pathlib import Path from .compat import tomllib +from typing import Any from huggingface_hub import ModelCard, ModelCardData from huggingface_hub.errors import EntryNotFoundError, RepositoryNotFoundError @@ -210,7 +211,7 @@ def _update_kernel_card_backends( cuda_cap_for_config = kernel_configs[k].get("cuda-capabilities") if cuda_cap_for_config: cuda_capabilities.extend(cuda_cap_for_config) - cuda_capabilities = set(cuda_capabilities) + cuda_capabilities: set[Any] = set(cuda_capabilities) if cuda_capabilities: cuda_list = "\n".join(f"- {cap}" for cap in cuda_capabilities) cuda_section = f"## CUDA Capabilities\n\n{cuda_list}\n\n" From 4e4c327dbfb7dd6af69ee22bbbf98ee393712b2b Mon Sep 17 00:00:00 2001 From: sayakpaul Date: Thu, 22 Jan 2026 20:09:50 +0530 Subject: [PATCH 17/23] make mypy ignore --- kernels/src/kernels/kernel_card_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernels/src/kernels/kernel_card_utils.py b/kernels/src/kernels/kernel_card_utils.py index f585131a..136670f7 100644 --- a/kernels/src/kernels/kernel_card_utils.py +++ b/kernels/src/kernels/kernel_card_utils.py @@ -211,7 +211,7 @@ def _update_kernel_card_backends( cuda_cap_for_config = kernel_configs[k].get("cuda-capabilities") if cuda_cap_for_config: cuda_capabilities.extend(cuda_cap_for_config) - cuda_capabilities: set[Any] = set(cuda_capabilities) + cuda_capabilities: set[Any] = set(cuda_capabilities) # type: ignore[no-redef] if cuda_capabilities: cuda_list = "\n".join(f"- {cap}" for cap in cuda_capabilities) cuda_section = f"## CUDA Capabilities\n\n{cuda_list}\n\n" From 5e91faa2c93fb2782a83ec728a745a1fb0ec189b Mon Sep 17 00:00:00 2001 From: sayakpaul Date: Tue, 27 Jan 2026 12:19:21 +0800 Subject: [PATCH 18/23] up. --- kernels/src/kernels/kernel_card_utils.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/kernels/src/kernels/kernel_card_utils.py b/kernels/src/kernels/kernel_card_utils.py index 136670f7..b567f373 100644 --- a/kernels/src/kernels/kernel_card_utils.py +++ b/kernels/src/kernels/kernel_card_utils.py @@ -211,7 +211,7 @@ def _update_kernel_card_backends( cuda_cap_for_config = kernel_configs[k].get("cuda-capabilities") if cuda_cap_for_config: cuda_capabilities.extend(cuda_cap_for_config) - cuda_capabilities: set[Any] = set(cuda_capabilities) # type: ignore[no-redef] + cuda_capabilities: set[Any] = set(cuda_capabilities) # type: ignore[no-redef] if cuda_capabilities: cuda_list = "\n".join(f"- {cap}" for cap in cuda_capabilities) cuda_section = f"## CUDA Capabilities\n\n{cuda_list}\n\n" @@ -235,3 +235,21 @@ def _update_kernel_card_license( final_license = license_from_config or existing_license kernel_card.data["license"] = final_license return kernel_card + + +def _update_benchmark(kernel_card: ModelCard, local_path: str | Path): + local_path = Path(local_path) + + benchmark_file = local_path / "benchmarks" / "benchmark.py" + if not benchmark_file.exists(): + return kernel_card + + card_content = str(kernel_card.content) + benchmark_text = '\n\nBenchmarking script is available for this kernel. Make sure to run `kernels benchmark org-id/repo-id` (replace "org-id" and "repo-id" with actual values).' + + pattern = r"(## Benchmarks)" + if re.search(pattern, card_content): + updated_content = re.sub(pattern, r"\1" + benchmark_text, card_content) + kernel_card.content = updated_content + + return kernel_card From 32ddf2993ee3b777233ce1061ef095a40c66beea Mon Sep 17 00:00:00 2001 From: sayakpaul Date: Tue, 27 Jan 2026 19:45:38 +0800 Subject: [PATCH 19/23] include template in the build. --- kernels/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernels/pyproject.toml b/kernels/pyproject.toml index d87133dc..3d7948c9 100644 --- a/kernels/pyproject.toml +++ b/kernels/pyproject.toml @@ -53,7 +53,7 @@ kernels = "kernels.cli:main" "kernels.lock" = "kernels.lockfile:write_egg_lockfile" [tool.setuptools.package-data] -kernels = ["python_depends.json"] +kernels = ["python_depends.json", "card_template.md"] [tool.isort] profile = "black" From 39a4f0902022a28f7dc0c8dc62f4e5f500b6efcc Mon Sep 17 00:00:00 2001 From: sayakpaul Date: Tue, 27 Jan 2026 20:20:05 +0800 Subject: [PATCH 20/23] empty From 81e0f9375c8e27e5eff1cba86586e20d131c71b8 Mon Sep 17 00:00:00 2001 From: sayakpaul Date: Mon, 9 Feb 2026 17:54:23 +0530 Subject: [PATCH 21/23] address review feedback. --- README.md | 65 ---------------------- docs/source/cli.md | 6 +++ kernels/src/kernels/card_template.md | 2 +- kernels/src/kernels/cli.py | 69 ++++++++++++++++++++++++ kernels/src/kernels/kernel_card_utils.py | 9 ---- 5 files changed, 76 insertions(+), 75 deletions(-) delete mode 100644 README.md diff --git a/README.md b/README.md deleted file mode 100644 index ecfeecd0..00000000 --- a/README.md +++ /dev/null @@ -1,65 +0,0 @@ -# kernels - -
-kernel-builder logo -

- PyPI - Version - GitHub tag - Test kernels - -

-
-
- -The Kernel Hub allows Python libraries and applications to load compute -kernels directly from the [Hub](https://hf.co/). To support this kind -of dynamic loading, Hub kernels differ from traditional Python kernel -packages in that they are made to be: - -- Portable: a kernel can be loaded from paths outside `PYTHONPATH`. -- Unique: multiple versions of the same kernel can be loaded in the - same Python process. -- Compatible: kernels must support all recent versions of Python and - the different PyTorch build configurations (various CUDA versions - and C++ ABIs). Furthermore, older C library versions must be supported. - -## Components - -- You can load kernels from the Hub using the [`kernels`](kernels/) Python package. -- If you are a kernel author, you can build your kernels with [kernel-builder](builder/). -- Hugging Face maintains a set of kernels in [kernels-community](https://huggingface.co/kernels-community). - -## 🚀 Quick Start - -Install the `kernels` Python package with `pip` (requires `torch>=2.5` and CUDA): - -```bash -pip install kernels -``` - -Here is how you would use the [activation](https://huggingface.co/kernels-community/activation) kernels from the Hugging Face Hub: - -```python -import torch - -from kernels import get_kernel - -# Download optimized kernels from the Hugging Face hub -activation = get_kernel("kernels-community/activation", version=1) - -# Random tensor -x = torch.randn((10, 10), dtype=torch.float16, device="cuda") - -# Run the kernel -y = torch.empty_like(x) -activation.gelu_fast(y, x) - -print(y) -``` - -You can [search for kernels](https://huggingface.co/models?other=kernels) on -the Hub. - -## 📚 Documentation - -Read the [documentation of kernels and kernel-builder](https://huggingface.co/docs/kernels/). diff --git a/docs/source/cli.md b/docs/source/cli.md index c7588c51..8b104e6b 100644 --- a/docs/source/cli.md +++ b/docs/source/cli.md @@ -31,3 +31,9 @@ your kernel builds to the Hub. To know the supported arguments run: `kernels upl - If a repo with the `repo_id` already exists and if it contains a `build` with the build variant being uploaded, it will attempt to delete the files existing under it. - Make sure to be authenticated (run `hf auth login` if not) to be able to perform uploads to the Hub. + +### kernels create-and-upload-card + +Use `kernels create-and-upload-card --card-path README.md` to generate a basic homepage +for the kernel. Find an example [here](https://hf.co/kernels-community/kernel-card-template). You can +optionally push it to the Hub by specifying a `--repo-id`. diff --git a/kernels/src/kernels/card_template.md b/kernels/src/kernels/card_template.md index 975cab95..ddffe02e 100644 --- a/kernels/src/kernels/card_template.md +++ b/kernels/src/kernels/card_template.md @@ -2,7 +2,7 @@ {{ card_data }} --- - {{ model_description }} diff --git a/kernels/src/kernels/cli.py b/kernels/src/kernels/cli.py index 3d7f54e5..25bd3424 100644 --- a/kernels/src/kernels/cli.py +++ b/kernels/src/kernels/cli.py @@ -14,6 +14,14 @@ ) from kernels.versions_cli import print_kernel_versions from kernels.init import run_init, parse_kernel_name +from kernels.kernel_card_utils import ( + _load_or_create_kernel_card, + _update_benchmark, + _update_kernel_card_available_funcs, + _update_kernel_card_license, + _update_kernel_card_backends, + _update_kernel_card_usage, +) from .doc import generate_readme_for_kernel @@ -181,6 +189,37 @@ def main(): ) init_parser.set_defaults(func=run_init) + repocard_parser = subparsers.add_parser( + "create-and-upload-card", + help="Create and optionally upload a kernel card.", + ) + repocard_parser.add_argument( + "kernel_dir", + type=str, + help="Path to the kernels source.", + ) + repocard_parser.add_argument( + "--card-path", type=str, required=True, help="Path to save the card to." + ) + repocard_parser.add_argument( + "--description", + type=str, + default=None, + help="Description to introduce the kernel.", + ) + repocard_parser.add_argument( + "--repo-id", + type=str, + default=None, + help="If specified it will be pushed to a repository on the Hub.", + ) + repocard_parser.add_argument( + "--create-pr", + action="store_true", + help="If specified it will create a PR on the `repo_id`.", + ) + repocard_parser.set_defaults(func=create_and_upload_card) + args = parser.parse_args() args.func(args) @@ -249,6 +288,36 @@ def upload_kernels(args): ) +def create_and_upload_card(args): + if not args.repo_id and args.create_pr: + raise ValueError("`create_pr` cannot be True when `repo_id` is None.") + + kernel_dir = Path(args.kernel_dir).resolve() + kernel_card = _load_or_create_kernel_card( + kernel_description=args.description, license="apache-2.0" + ) + + updated_card = _update_kernel_card_usage( + kernel_card=kernel_card, local_path=kernel_dir + ) + updated_card = _update_kernel_card_available_funcs( + kernel_card=kernel_card, local_path=kernel_dir + ) + updated_card = _update_kernel_card_backends( + kernel_card=kernel_card, local_path=kernel_dir + ) + updated_card = _update_benchmark(kernel_card=kernel_card, local_path=kernel_dir) + updated_card = _update_kernel_card_license( + kernel_card=kernel_card, local_path=kernel_dir + ) + + card_path = args.card_path + updated_card.save(card_path) + + if args.repo_id: + updated_card.push_to_hub(repo_id=args.repo_id, create_pr=args.create_pr) + + class _JSONEncoder(json.JSONEncoder): def default(self, o): if dataclasses.is_dataclass(o): diff --git a/kernels/src/kernels/kernel_card_utils.py b/kernels/src/kernels/kernel_card_utils.py index b567f373..b36973e5 100644 --- a/kernels/src/kernels/kernel_card_utils.py +++ b/kernels/src/kernels/kernel_card_utils.py @@ -56,7 +56,6 @@ def _load_or_create_kernel_card( if kernel_card is None: kernel_description = kernel_description or DESCRIPTION kernel_card = ModelCard.from_template( - # Card metadata object that will be converted to YAML block card_data=ModelCardData(license=license, library_name=LIBRARY_NAME), template_path=str(KERNEL_CARD_TEMPLATE_PATH), model_description=kernel_description, @@ -87,7 +86,6 @@ def _find_torch_ext_init(local_path: str | Path) -> Path | None: return None try: - # Get kernel name from general.name kernel_name = config.get("general", {}).get("name") if not kernel_name: return None @@ -107,15 +105,12 @@ def _extract_functions_from_all(init_file_path: Path) -> list[str] | None: try: content = init_file_path.read_text() - # Parse the file as an AST tree = ast.parse(content) - # Find the __all__ assignment for node in ast.walk(tree): if isinstance(node, ast.Assign): for target in node.targets: if isinstance(target, ast.Name) and target.id == "__all__": - # Extract all list values if isinstance(node.value, ast.List): functions = [] for elt in node.value.elts: @@ -143,11 +138,9 @@ def _update_kernel_card_usage( if not func_names: return kernel_card - # Use the first function as an example func_name = func_names[0] example_code = EXAMPLE_CODE.format(repo_id=repo_id, func_name=func_name) - # Update the model card content card_content = str(kernel_card.content) pattern = r"(## How to use\s*\n\n)```python\n# TODO: add an example code snippet for running this kernel\n```" @@ -171,10 +164,8 @@ def _update_kernel_card_available_funcs( if not func_names: return kernel_card - # Format functions as a bulleted list functions_list = "\n".join(f"- `{func}`" for func in func_names) - # Update the model card content card_content = str(kernel_card.content) pattern = r"(## Available functions\s*\n\n)\[TODO: add the functions available through this kernel\]" From c7f81475183eb16c7f4b8b419fcf32ad524747b6 Mon Sep 17 00:00:00 2001 From: sayakpaul Date: Mon, 9 Feb 2026 17:56:04 +0530 Subject: [PATCH 22/23] remove unneeded script. --- example_usage.py | 36 ------------------------------------ 1 file changed, 36 deletions(-) delete mode 100644 example_usage.py diff --git a/example_usage.py b/example_usage.py deleted file mode 100644 index 5cb41b42..00000000 --- a/example_usage.py +++ /dev/null @@ -1,36 +0,0 @@ -from pathlib import Path -from kernels.kernel_card_utils import ( - _update_kernel_card_available_funcs, - _update_kernel_card_license, - _load_or_create_kernel_card, - _update_kernel_card_usage, - _update_kernel_card_backends, -) -import argparse - - -def main(args): - kernel_dir = Path(args.kernels_dir) - - kernel_card = _load_or_create_kernel_card(kernel_description=args.description, license="apache-2.0") - - updated_card = _update_kernel_card_usage(kernel_card=kernel_card, local_path=kernel_dir) - updated_card = _update_kernel_card_available_funcs(kernel_card=kernel_card, local_path=kernel_dir) - updated_card = _update_kernel_card_backends(kernel_card=updated_card, local_path=kernel_dir) - updated_card = _update_kernel_card_license(kernel_card, kernel_dir) - - card_path = args.card_path or "README.md" - updated_card.save(card_path) - print("Kernel card updated successfully!") - print("\nUpdated content preview:") - print(updated_card.content[:500] + "...") - - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument("--kernels_dir", type=str, required=True, help="Path to the kernels source.") - parser.add_argument("--card_path", type=str, default=None, help="Path to save the card to.") - parser.add_argument("--description", type=str, default=None) - args = parser.parse_args() - - main(args) From f121e72adca431072021acce5a5c2070c46bb84c Mon Sep 17 00:00:00 2001 From: sayakpaul Date: Mon, 9 Feb 2026 18:18:04 +0530 Subject: [PATCH 23/23] add a simple test suote. --- kernels/tests/test_kernel_card.py | 275 ++++++++++++++++++++++++++++++ 1 file changed, 275 insertions(+) create mode 100644 kernels/tests/test_kernel_card.py diff --git a/kernels/tests/test_kernel_card.py b/kernels/tests/test_kernel_card.py new file mode 100644 index 00000000..397da44e --- /dev/null +++ b/kernels/tests/test_kernel_card.py @@ -0,0 +1,275 @@ +import tempfile +from pathlib import Path +from dataclasses import dataclass + +import pytest + +from kernels.cli import create_and_upload_card + + +@dataclass +class CardArgs: + kernel_dir: str + card_path: str + description: str | None = None + repo_id: str | None = None + create_pr: bool = False + + +@pytest.fixture +def mock_kernel_dir(): + with tempfile.TemporaryDirectory() as tmpdir: + kernel_dir = Path(tmpdir) + + build_toml = kernel_dir / "build.toml" + build_toml.write_text( + """[general] +name = "test_kernel" +backends = ["cuda", "metal"] +license = "apache-2.0" +version = 1 + +[general.hub] +repo-id = "test-org/test-kernel" + +[kernel._test] +backend = "cuda" +cuda-capabilities = ["8.0", "8.9"] +""" + ) + + torch_ext_dir = kernel_dir / "torch-ext" / "test_kernel" + torch_ext_dir.mkdir(parents=True) + + init_file = torch_ext_dir / "__init__.py" + init_file.write_text( + """from .core import func1, func2 + +__all__ = ["func1", "func2", "func3"] +""" + ) + + core_file = torch_ext_dir / "core.py" + core_file.write_text( + """def func1(): + pass + +def func2(): + pass + +def func3(): + pass +""" + ) + + yield kernel_dir + + +@pytest.fixture +def mock_kernel_dir_with_benchmark(mock_kernel_dir): + benchmarks_dir = mock_kernel_dir / "benchmarks" + benchmarks_dir.mkdir() + + benchmark_file = benchmarks_dir / "benchmark.py" + benchmark_file.write_text( + """import time + +def benchmark(): + # Simple benchmark + start = time.time() + # ... benchmark code ... + end = time.time() + return end - start +""" + ) + + return mock_kernel_dir + + +@pytest.fixture +def mock_kernel_dir_minimal(): + with tempfile.TemporaryDirectory() as tmpdir: + kernel_dir = Path(tmpdir) + + build_toml = kernel_dir / "build.toml" + build_toml.write_text( + """[general] +name = "minimal_kernel" +backends = ["cuda"] +""" + ) + + yield kernel_dir + + +def test_create_and_upload_card_basic(mock_kernel_dir): + with tempfile.TemporaryDirectory() as tmpdir: + card_path = Path(tmpdir) / "README.md" + + args = CardArgs( + kernel_dir=str(mock_kernel_dir), + card_path=str(card_path), + description="This is a test kernel for testing purposes.", + ) + + create_and_upload_card(args) + + assert card_path.exists() + + card_content = card_path.read_text() + + assert "---" in card_content + assert "This is a test kernel for testing purposes." in card_content + + +def test_create_and_upload_card_updates_usage(mock_kernel_dir): + """Test that usage code snippet is properly generated.""" + with tempfile.TemporaryDirectory() as tmpdir: + card_path = Path(tmpdir) / "README.md" + + args = CardArgs( + kernel_dir=str(mock_kernel_dir), + card_path=str(card_path), + ) + + create_and_upload_card(args) + + card_content = card_path.read_text() + + assert "## How to use" in card_content + assert "from kernels import get_kernel" in card_content + assert "func1" in card_content + assert "TODO: add an example code snippet" not in card_content + + +def test_create_and_upload_card_updates_available_functions(mock_kernel_dir): + with tempfile.TemporaryDirectory() as tmpdir: + card_path = Path(tmpdir) / "README.md" + + args = CardArgs( + kernel_dir=str(mock_kernel_dir), + card_path=str(card_path), + ) + + create_and_upload_card(args) + + card_content = card_path.read_text() + + assert "## Available functions" in card_content + assert "- `func1`" in card_content + assert "- `func2`" in card_content + assert "- `func3`" in card_content + assert ( + "[TODO: add the functions available through this kernel]" + not in card_content + ) + + +def test_create_and_upload_card_updates_backends(mock_kernel_dir): + with tempfile.TemporaryDirectory() as tmpdir: + card_path = Path(tmpdir) / "README.md" + + args = CardArgs( + kernel_dir=str(mock_kernel_dir), + card_path=str(card_path), + ) + + create_and_upload_card(args) + + card_content = card_path.read_text() + + assert "## Supported backends" in card_content + assert "- cuda" in card_content + assert "- metal" in card_content + assert "[TODO: add the backends this kernel supports]" not in card_content + + +def test_create_and_upload_card_updates_cuda_capabilities(mock_kernel_dir): + with tempfile.TemporaryDirectory() as tmpdir: + card_path = Path(tmpdir) / "README.md" + + args = CardArgs( + kernel_dir=str(mock_kernel_dir), + card_path=str(card_path), + ) + + create_and_upload_card(args) + + card_content = card_path.read_text() + + assert "## CUDA Capabilities" in card_content + assert "- 8.0" in card_content or "- 8.9" in card_content + + +def test_create_and_upload_card_updates_license(mock_kernel_dir): + with tempfile.TemporaryDirectory() as tmpdir: + card_path = Path(tmpdir) / "README.md" + + args = CardArgs( + kernel_dir=str(mock_kernel_dir), + card_path=str(card_path), + ) + + create_and_upload_card(args) + + card_content = card_path.read_text() + + assert "license: apache-2.0" in card_content + + +def test_create_and_upload_card_with_benchmark(mock_kernel_dir_with_benchmark): + with tempfile.TemporaryDirectory() as tmpdir: + card_path = Path(tmpdir) / "README.md" + + args = CardArgs( + kernel_dir=str(mock_kernel_dir_with_benchmark), + card_path=str(card_path), + ) + + create_and_upload_card(args) + + card_content = card_path.read_text() + + assert "## Benchmarks" in card_content + assert "Benchmarking script is available for this kernel" in card_content + assert "kernels benchmark" in card_content + + +def test_create_and_upload_card_minimal_structure(mock_kernel_dir_minimal): + with tempfile.TemporaryDirectory() as tmpdir: + card_path = Path(tmpdir) / "README.md" + + args = CardArgs( + kernel_dir=str(mock_kernel_dir_minimal), + card_path=str(card_path), + ) + + create_and_upload_card(args) + + assert card_path.exists() + + card_content = card_path.read_text() + + assert "---" in card_content + assert "## How to use" in card_content + assert "## Available functions" in card_content + assert "## Supported backends" in card_content + + +def test_create_and_upload_card_custom_description(mock_kernel_dir): + with tempfile.TemporaryDirectory() as tmpdir: + card_path = Path(tmpdir) / "README.md" + + custom_desc = "My custom kernel description with special features." + + args = CardArgs( + kernel_dir=str(mock_kernel_dir), + card_path=str(card_path), + description=custom_desc, + ) + + create_and_upload_card(args) + + card_content = card_path.read_text() + + assert custom_desc in card_content