From 02f09eb55ae34c51360b3e1728601dd31feb04b9 Mon Sep 17 00:00:00 2001 From: Jiahui Yang Date: Wed, 8 Jul 2026 03:40:34 +0000 Subject: [PATCH 1/6] Update matmul op args --- src/tilegym/ops/ops.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tilegym/ops/ops.py b/src/tilegym/ops/ops.py index f4cc430..fafea62 100644 --- a/src/tilegym/ops/ops.py +++ b/src/tilegym/ops/ops.py @@ -721,7 +721,7 @@ def matmul( trans_a: Optional[bool] = None, trans_b: Optional[bool] = None, static_persistent: Optional[bool] = True, - use_tma: Optional[bool] = True, + use_tma: Optional[bool] = None, **kwargs: Any, ): """ @@ -733,7 +733,7 @@ def matmul( trans_a: Whether to transpose matrix A (None uses backend default) trans_b: Whether to transpose matrix B (None uses backend default) static_persistent: Whether to use static persistent mode (default: True) - use_tma: Whether to use TMA (default: True) + use_tma: Whether to force TMA. None lets supported backends autotune TMA vs non-TMA. **kwargs: Additional arguments, including kernel_configs if needed Returns: torch.Tensor: Matrix multiplication result From b3768c8238ce20e73e230165a4637c884f5afab1 Mon Sep 17 00:00:00 2001 From: Zixin Huang Date: Wed, 8 Jul 2026 19:41:02 -0700 Subject: [PATCH 2/6] Update test_mla_decoding_split_kv parameter --- tests/ops/test_mla_decoding_split_kv.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/ops/test_mla_decoding_split_kv.py b/tests/ops/test_mla_decoding_split_kv.py index b523eae..70f166e 100644 --- a/tests/ops/test_mla_decoding_split_kv.py +++ b/tests/ops/test_mla_decoding_split_kv.py @@ -52,7 +52,11 @@ def _get_sm_scale(q, qpe): _backends = _backends + ["tilecpp"] _perf_frameworks = _backends + ["pytorch"] - @pytest.mark.parametrize("num_heads", [16, 32]) + # num_heads 8 and 24 are regression shapes: head counts that are not a + # multiple of the kernel's head-tile size (16) previously wrote LSE rows + # out of bounds (DeepSeek head counts under tensor parallelism, e.g. + # 128/TP16 = 8, hit this). + @pytest.mark.parametrize("num_heads", [8, 16, 24, 32]) @pytest.mark.parametrize("seq_len", [129, 1024, 8192, 11049]) @pytest.mark.parametrize("kv_len_per_split", [128, 512]) @pytest.mark.parametrize("dtype", [torch.float16]) From b8b61a6de402ad1b096dc69a087a488d2fa30bf4 Mon Sep 17 00:00:00 2001 From: Rundong Li Date: Wed, 8 Jul 2026 19:54:16 -0700 Subject: [PATCH 3/6] Generalize kernel inventory harness and add cuDNN schemas Signed-off-by: Rundong Li --- modeling/transformers/pyproject.toml | 1 + modeling/transformers/uv.lock | 402 ++++++----- .../references/kernel-inventory-schema.md | 4 +- .../__init__.py} | 134 +++- .../generation.py} | 105 +-- .../kernel_inventory/return_contract.py | 163 +++++ .../source_contract.py} | 2 +- .../olmo3_dual_rms_norm.json | 2 +- .../olmo3_rms_norm_residual_add.json | 2 +- .../kernel_solutions/olmo3_dual_rms_norm.json | 4 +- .../olmo3_rms_norm_residual_add.json | 6 +- .../olmoe_dual_rms_norm.json | 2 +- .../olmoe_residual_add_rms_norm.json | 2 +- .../kernel_solutions/olmoe_dual_rms_norm.json | 4 +- .../olmoe_residual_add_rms_norm.json | 6 +- .../qwen3_5_causal_conv1d_prefill_silu.json | 2 +- .../qwen3_5_causal_conv1d_update_silu.json | 2 +- .../qwen3_5_gdr_preprocess.json | 2 +- .../qwen3_5_residual_add_gemma_rms_norm.json | 2 +- .../qwen3_5_rms_norm_gated_silu.json | 2 +- .../qwen3_5_sigmoid_mul.json | 2 +- .../qwen3_5_causal_conv1d_prefill_silu.json | 6 +- .../qwen3_5_causal_conv1d_update_silu.json | 6 +- .../qwen3_5_gdr_preprocess.json | 6 +- .../qwen3_5_residual_add_gemma_rms_norm.json | 6 +- .../qwen3_5_rms_norm_gated_silu.json | 6 +- .../kernel_solutions/qwen3_5_sigmoid_mul.json | 6 +- .../qwen3_5_silu_and_mul_separate.json | 6 +- tests/kernel_inventory/__init__.py | 5 + .../conftest.py | 10 +- .../kernel_inventory/kernel_runtime_utils.py | 627 ++++++++++++++++++ ...test_kernel_definition_solution_runtime.py | 227 +++++++ .../test_kernel_inventory.py | 159 +++-- tests/transformers/kernel_runtime_utils.py | 261 -------- ...test_kernel_definition_solution_runtime.py | 42 -- 35 files changed, 1533 insertions(+), 691 deletions(-) rename src/tilegym/{transformers/kernel_inventory.py => kernel_inventory/__init__.py} (58%) rename src/tilegym/{transformers/inventory_generation.py => kernel_inventory/generation.py} (69%) create mode 100644 src/tilegym/kernel_inventory/return_contract.py rename src/tilegym/{transformers/inventory_common.py => kernel_inventory/source_contract.py} (96%) create mode 100644 tests/kernel_inventory/__init__.py rename tests/{transformers => kernel_inventory}/conftest.py (60%) create mode 100644 tests/kernel_inventory/kernel_runtime_utils.py create mode 100644 tests/kernel_inventory/test_kernel_definition_solution_runtime.py rename tests/{transformers => kernel_inventory}/test_kernel_inventory.py (75%) delete mode 100644 tests/transformers/kernel_runtime_utils.py delete mode 100644 tests/transformers/test_kernel_definition_solution_runtime.py diff --git a/modeling/transformers/pyproject.toml b/modeling/transformers/pyproject.toml index cb0ca50..82dd508 100644 --- a/modeling/transformers/pyproject.toml +++ b/modeling/transformers/pyproject.toml @@ -60,6 +60,7 @@ tilegym_hf_bench = ["kernel_filters/*.yaml"] managed = true override-dependencies = [ "transformers==5.10.2", + "triton==3.7.1", "cuda-toolkit[tileiras,nvcc,nvvm]==13.3.0", "nvidia-cuda-runtime>=13.3,<14", "nvidia-nvjitlink>=13.3,<14", diff --git a/modeling/transformers/uv.lock b/modeling/transformers/uv.lock index 6e2be11..13d1ae9 100644 --- a/modeling/transformers/uv.lock +++ b/modeling/transformers/uv.lock @@ -20,15 +20,16 @@ overrides = [ { name = "nvidia-cuda-runtime", specifier = ">=13.3,<14" }, { name = "nvidia-nvjitlink", specifier = ">=13.3,<14" }, { name = "transformers", specifier = "==5.10.2" }, + { name = "triton", specifier = "==3.7.1" }, ] [[package]] name = "absl-py" -version = "2.4.0" +version = "2.5.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/64/c7/8de93764ad66968d19329a7e0c147a2bb3c7054c554d4a119111b8f9440f/absl_py-2.4.0.tar.gz", hash = "sha256:8c6af82722b35cf71e0f4d1d47dcaebfff286e27110a99fc359349b247dfb5d4", size = 116543, upload-time = "2026-01-28T10:17:05.322Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/4f/d79676ab82f2e42fc3611618139f13a9c4c31d0cff4b486982047679a802/absl_py-2.5.0.tar.gz", hash = "sha256:0c996f25c0490700fadabe6351630f6111534fa0ae252cc6d2014ea3b141135f", size = 118119, upload-time = "2026-07-03T10:57:48.157Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/a6/907a406bb7d359e6a63f99c313846d9eec4f7e6f7437809e03aa00fa3074/absl_py-2.4.0-py3-none-any.whl", hash = "sha256:88476fd881ca8aab94ffa78b7b6c632a782ab3ba1cd19c9bd423abc4fb4cd28d", size = 135750, upload-time = "2026-01-28T10:17:04.19Z" }, + { url = "https://files.pythonhosted.org/packages/58/0a/a10b45aab35b175aded078a462dc8d0c698f5b13946e7cb0869097b78bb6/absl_py-2.5.0-py3-none-any.whl", hash = "sha256:0f17b89f2a4eaaedc4f28c622998aa690564b3012a396a4ffad0821007fe03ba", size = 137410, upload-time = "2026-07-03T10:57:46.735Z" }, ] [[package]] @@ -39,7 +40,7 @@ dependencies = [ { name = "huggingface-hub" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "packaging" }, { name = "psutil" }, { name = "pyyaml" }, @@ -53,11 +54,11 @@ wheels = [ [[package]] name = "aiohappyeyeballs" -version = "2.6.2" +version = "2.7.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/33/c6/61a2d7b7572279226bb2e7f61d7a19ca7c90da0329c93fa0d560cbf288d8/aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64", size = 22591, upload-time = "2026-05-20T15:12:24.631Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/f4/eec0465c2f67b2664688d0240b3212d5196fd89e741df67ddb81f8d35658/aiohappyeyeballs-2.7.1.tar.gz", hash = "sha256:065665c041c42a5938ed220bdcd7230f22527fbec085e1853d2402c8a3615d9d", size = 24757, upload-time = "2026-07-01T17:11:55.501Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/fc/a7bf5b6e4e617b45f90f2d9d2a68519c249c81dd4fc2658c7a2a61c4f4b7/aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4", size = 15062, upload-time = "2026-05-20T15:12:23.328Z" }, + { url = "https://files.pythonhosted.org/packages/71/43/1947f06babed6b3f1d7f38b0c767f52df66bfb2bc10b468c4a7de9eceff2/aiohappyeyeballs-2.7.1-py3-none-any.whl", hash = "sha256:9243213661e29250eb41368e5daa826fc017156c3b8a11440826b2e3ed376472", size = 15038, upload-time = "2026-07-01T17:11:54.055Z" }, ] [[package]] @@ -535,7 +536,7 @@ resolution-markers = [ ] dependencies = [ { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } wheels = [ @@ -649,7 +650,7 @@ dependencies = [ { name = "cuda-pathfinder" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/90/21/ef85f3e15d394c9ca41fe116d78cd9e28533b9d7ead842f9241b332acf01/cuda_core-1.0.1-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9632db74eceb1cd72a7c95b61a5e4cfb9cc2291de0503e170334d936cab3316", size = 4788165, upload-time = "2026-05-12T20:11:17.116Z" }, @@ -786,7 +787,7 @@ dependencies = [ { name = "multiprocess" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "packaging" }, { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "pandas", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, @@ -840,7 +841,7 @@ dependencies = [ { name = "multiprocess" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "packaging" }, { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "pandas", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, @@ -876,11 +877,11 @@ wheels = [ [[package]] name = "filelock" -version = "3.29.4" +version = "3.29.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e6/dc/be6cbe99670cd6e4ad387123647cb08e0c32975e223f82551e914c5568a6/filelock-3.29.4.tar.gz", hash = "sha256:10cdb3656fc44541cdf30652a93fb10ec6b05325620eb316bd26893e4201538a", size = 63028, upload-time = "2026-06-13T16:12:00.744Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/ee/29c668c50888588c432a702f7c2e8ee8a0c9e5286028d91f170308d6b2e9/filelock-3.29.5.tar.gz", hash = "sha256:6e6034c57a00a020e767f2614a5539863f056de7e7991d6d1473aef7ff73f156", size = 68927, upload-time = "2026-07-03T03:50:31.818Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/13/37/a065dc3bd6e49423a6532c642ca7378d3f467b1ef44c2800c937af7f9739/filelock-3.29.4-py3-none-any.whl", hash = "sha256:dac1648087d5115554850d113e7dd8c83ab2d38e3435dde2d4f163847e57b767", size = 42757, upload-time = "2026-06-13T16:11:59.582Z" }, + { url = "https://files.pythonhosted.org/packages/4a/e3/f1fae3647d170919c2cf2a898e77e7d1a4e5c7cae0aed7bb4bd3f5ebff6f/filelock-3.29.5-py3-none-any.whl", hash = "sha256:8af830889ba3a0ffcefbd6c7d2af8a54012058103771f2e10848222f476a1693", size = 45073, upload-time = "2026-07-03T03:50:30.445Z" }, ] [[package]] @@ -902,17 +903,17 @@ wheels = [ [[package]] name = "flashinfer-python" -version = "0.6.13" +version = "0.6.14" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "apache-tvm-ffi" }, { name = "click" }, - { name = "cuda-tile", extra = ["tileiras"] }, + { name = "cuda-tile" }, { name = "einops" }, { name = "ninja" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "nvidia-cudnn-frontend" }, { name = "nvidia-cutlass-dsl" }, { name = "nvidia-ml-py" }, @@ -922,9 +923,9 @@ dependencies = [ { name = "torch" }, { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1e/f7/7f6dd2b03f4277509dfd1e5c7a8ec1de2662fd245d2e663f44a3493882b1/flashinfer_python-0.6.13.tar.gz", hash = "sha256:8a6d7d3708c7c87952390ec4e3aabe6e1c356defa8c7211b26bccaa355a61c59", size = 9638085, upload-time = "2026-06-24T22:46:29.391Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8f/11/ce2271271bee6990d34ed2d01288e9e92a0ea8ee45fb28de8e746c7da761/flashinfer_python-0.6.14.tar.gz", hash = "sha256:f4da8b5e005601784e85e0dcaa3389f908ee2d32c2560142d67124ab10e4a070", size = 9944949, upload-time = "2026-07-02T00:22:50.879Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/50/e8920ed7f68e0116a385e3ab814ac2f0010579852fc483bfb48819d11976/flashinfer_python-0.6.13-py3-none-any.whl", hash = "sha256:239e6ddc3cbbaf0bee251861a8c7c69438b1171830d69ddfa133ddea4494850d", size = 14191198, upload-time = "2026-06-24T22:46:26.565Z" }, + { url = "https://files.pythonhosted.org/packages/f2/8f/b101913cb2b3687654f56681cfe9836d447526be663c149966470ef70531/flashinfer_python-0.6.14-py3-none-any.whl", hash = "sha256:d124369346a3d48eac67e31c42f7a3c813bcc0abc10e2e36db413b7b3dfd97df", size = 14574383, upload-time = "2026-07-02T00:22:48.413Z" }, ] [[package]] @@ -1190,7 +1191,7 @@ wheels = [ [[package]] name = "huggingface-hub" -version = "1.21.0" +version = "1.22.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, @@ -1201,12 +1202,11 @@ dependencies = [ { name = "packaging" }, { name = "pyyaml" }, { name = "tqdm" }, - { name = "typer" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8f/77/ce3331f40cb2d021fe9b24c46c41e72faf74493621138e5eddac12bf5e1c/huggingface_hub-1.21.0.tar.gz", hash = "sha256:a44f222cd8f2f7c2eade30b5e7a04cac984a3235fa61ea87a0a5a31db77d561f", size = 861572, upload-time = "2026-06-25T13:09:26.356Z" } +sdist = { url = "https://files.pythonhosted.org/packages/77/ea/dc54b4dda5841cb3a7812a178695be776e7c15c597887c2ed892f17d015a/huggingface_hub-1.22.0.tar.gz", hash = "sha256:e2dfe5fe1ec3b87ba2709aa34555b23e3f3f6ad4d7255238e13ddb8348e6bbfa", size = 914232, upload-time = "2026-07-03T09:46:44.685Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4f/85/b505a99a133d9f99d21af182af416e9baef70bdeef019983479651e494c2/huggingface_hub-1.21.0-py3-none-any.whl", hash = "sha256:eadaa3678c512c82aea69e8675d90a184861e68de32f1105668628b4dce0e7cd", size = 721078, upload-time = "2026-06-25T13:09:24.402Z" }, + { url = "https://files.pythonhosted.org/packages/64/9c/a1a377265abd8b823a2c661c665028ccb6b9fba1ca9d08e52ff679c20ecd/huggingface_hub-1.22.0-py3-none-any.whl", hash = "sha256:b09e19309ae09ee0a71892701c4fe70af39ab4e00817321dc62f2289a977249b", size = 765085, upload-time = "2026-07-03T09:46:42.832Z" }, ] [[package]] @@ -1397,7 +1397,7 @@ dependencies = [ { name = "more-itertools" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "pytablewriter" }, { name = "rouge-score" }, { name = "sacrebleu" }, @@ -1721,7 +1721,7 @@ dependencies = [ { name = "fonttools", marker = "python_full_version >= '3.11'" }, { name = "kiwisolver", marker = "python_full_version >= '3.11'" }, { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "packaging", marker = "python_full_version >= '3.11'" }, { name = "pillow", marker = "python_full_version >= '3.11'" }, { name = "pyparsing", marker = "python_full_version >= '3.11'" }, @@ -1978,11 +1978,11 @@ wheels = [ [[package]] name = "narwhals" -version = "2.22.1" +version = "2.23.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/62/3c/c4ef2164a71c1a63d7f1ae411c4082c5fa872405106db60a4b7114989ad7/narwhals-2.22.1.tar.gz", hash = "sha256:d62920805a0a43b7ff8b54b0c0d3142d796f8a9301836ada37e573d6a33cbcd9", size = 647493, upload-time = "2026-06-05T12:34:34.051Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/ac/66ed1fc6e38a0c0f330627ec5c5d597990d6159b6712b82af0ad2c65f06c/narwhals-2.23.0.tar.gz", hash = "sha256:13e7ff5b4bb4a2f77b907c2e4d8a76e273dfc1323a3c997440a2f9fd26aed408", size = 656209, upload-time = "2026-07-01T11:21:53.278Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/48/ca/36339329c4604adbcc99c899b7eb1ce1a555c499b6a6860757dc9bfed36d/narwhals-2.22.1-py3-none-any.whl", hash = "sha256:60567d774edf77db53906f89d9fbd164e66e56d66d388e1e6990f17ac33cfb53", size = 454815, upload-time = "2026-06-05T12:34:32.289Z" }, + { url = "https://files.pythonhosted.org/packages/f4/4e/afc8c31605cb8be1d3bb4438c4d979daa104dab6306cd2b87abe9c3a7299/narwhals-2.23.0-py3-none-any.whl", hash = "sha256:769e7b9ab102c93d8fa019f6b4cd1a657909b04a20bf6210e5a35aae06814ae9", size = 458938, upload-time = "2026-07-01T11:21:51.677Z" }, ] [[package]] @@ -2209,7 +2209,7 @@ wheels = [ [[package]] name = "numpy" -version = "2.5.0" +version = "2.5.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'win32'", @@ -2219,51 +2219,51 @@ resolution-markers = [ "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] -sdist = { url = "https://files.pythonhosted.org/packages/e7/05/3d27272d30698dc0ecb7fdfaa41ad70303b444f81722bb99bce1d818638a/numpy-2.5.0.tar.gz", hash = "sha256:5a129578019311b6e56bdd714250f19b518f7dceeeb8d1af5490f4942d3f891c", size = 20652461, upload-time = "2026-06-21T20:57:51.95Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/0a/11486d02add7b1384dff7374d124b1cfbb0ee864dcc9f6a2c0380638cf84/numpy-2.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:489780423903667933b4ed6197b6ec3b75ea5dd17d1d8f0f38d798feb6921561", size = 16789987, upload-time = "2026-06-21T20:56:16.657Z" }, - { url = "https://files.pythonhosted.org/packages/55/b2/285f48640a181947b4587a3766d21ec1eaa7fea833d4b49957e09da467a2/numpy-2.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ece55976ced6bca95a03ae2839e2e5ccffe8eb6a3e7022415645eb154a81e4e6", size = 11760322, upload-time = "2026-06-21T20:56:19.813Z" }, - { url = "https://files.pythonhosted.org/packages/dd/67/b032db1eb03ca30d16eda3b0c22aaa615338b9263c2fd559d0f29451aca4/numpy-2.5.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:c83b664b0e6eee9594fa920cf0639d8af796606d3fad6cc70180c87e4b97c7be", size = 5319605, upload-time = "2026-06-21T20:56:22.173Z" }, - { url = "https://files.pythonhosted.org/packages/b9/83/03fc7300c7c6b6c84c487b1dc80d322817b95fbd1f4dd57a85e23b7198de/numpy-2.5.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:bf80333980bf37f523341ddd72c783f39d6829ec7736b9eb99086388a2d52cc2", size = 6653628, upload-time = "2026-06-21T20:56:23.914Z" }, - { url = "https://files.pythonhosted.org/packages/82/49/2ec21730bc63ccfda829323f7040a8ed4715b3852ce658689cf74ee96a8c/numpy-2.5.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1a4874217b36d5ac8fc876f52e39df56f8182c88463e9e2dceabf7ca8b7efb8", size = 15153691, upload-time = "2026-06-21T20:56:25.631Z" }, - { url = "https://files.pythonhosted.org/packages/bb/6b/f4a3d0637692c49da8ef99d72d52526f92e0a8d6ac4f0ca9f31441b9d9ea/numpy-2.5.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aaa760137137e8d3c920d27927748215b56014f92667dc9b6c27dfc61249255a", size = 16660066, upload-time = "2026-06-21T20:56:28.009Z" }, - { url = "https://files.pythonhosted.org/packages/3a/2f/c354ec86d1f3f5c19649463b0d39652e160736e5b0a4cd18dff0576715c4/numpy-2.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7174ce8265fc7f7417d171c9ea8fe905220748893ea67a2a7abe726ec331c4b0", size = 16514638, upload-time = "2026-06-21T20:56:30.26Z" }, - { url = "https://files.pythonhosted.org/packages/06/34/43efdcb319988648580f93c11f1ae82cf7e2faa74925e98e454ae3aa95f8/numpy-2.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b8c3daaf99de52415d20b42f8e8155c78642cb04207d02f9d317a0dcf1b3fb54", size = 18419647, upload-time = "2026-06-21T20:56:32.41Z" }, - { url = "https://files.pythonhosted.org/packages/71/e2/f5d1676b1d7fb682eb5e9a1641e7ebd2414b3216c370661d1029778908b4/numpy-2.5.0-cp312-cp312-win32.whl", hash = "sha256:6206db0af545d73d068add6d992279145f158428d1da6cc49adc4b630c5d6ee5", size = 6056688, upload-time = "2026-06-21T20:56:34.657Z" }, - { url = "https://files.pythonhosted.org/packages/8f/7c/48f115d1c58a34032facebcd51fdf2d02df2c51d4a46a81dd1197bb2ea6b/numpy-2.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:6f2d6873e2940c860a309d21e25b1e69af6aaffdd80aa056b04c16380db1c4f2", size = 12419237, upload-time = "2026-06-21T20:56:36.24Z" }, - { url = "https://files.pythonhosted.org/packages/86/26/2e0882f4044d1b1a1b63e875151fb2393389032022a8b7f5657a7996d3b2/numpy-2.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:a55e1eb2bca2cfd17a16b213c99dfc8502d47b0d494224d2122277d0400935ca", size = 10339912, upload-time = "2026-06-21T20:56:38.733Z" }, - { url = "https://files.pythonhosted.org/packages/8a/33/07675aaad7f26ea013d5e884d9a0d784b79c6bd7566c333f5a52fa3c610b/numpy-2.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:520e6b8be0a4b65840ac8090d4f51cef4bed66e2b0894d5a520f099adc24a9b2", size = 16784890, upload-time = "2026-06-21T20:56:40.799Z" }, - { url = "https://files.pythonhosted.org/packages/85/4b/953118a730ee3b35e28645e0eb4cf9beec5bdbb954e1ac2f5fcefba6bbc3/numpy-2.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:146b81cdd3967fdb6beca8ba25f00c58741d8f3cbd797f55af0fbe0bfec3469c", size = 11754584, upload-time = "2026-06-21T20:56:43.094Z" }, - { url = "https://files.pythonhosted.org/packages/44/9b/56dd530c367c74ae17411027cea4135ca57e1e0583bf5594cee18bd83217/numpy-2.5.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:126b88d95e8ff9b00c9e717aa540469f21d6180162f84c0caec51b16215d49cd", size = 5313904, upload-time = "2026-06-21T20:56:45.503Z" }, - { url = "https://files.pythonhosted.org/packages/ce/b0/bcd672edad27ecca7da1f7bb0ce72cd1706a4f2d79ae94990afc97c13e1c/numpy-2.5.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d4313cef1594c5ce46c31b6e54e918338f63f16ee9322304e8c9114d6d81c8bd", size = 6648504, upload-time = "2026-06-21T20:56:47.567Z" }, - { url = "https://files.pythonhosted.org/packages/80/9e/15cdfcbd30a1544a46c9e487a00df331c4672450216538705a9e51fa6710/numpy-2.5.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:750fb097caf26fa878746d9d119f6f9da12dedcbff1eea966c3e3447647c4a9e", size = 15150086, upload-time = "2026-06-21T20:56:49.352Z" }, - { url = "https://files.pythonhosted.org/packages/32/4e/8d7656ccaab3e81e97258b8a9bc5f0c8502513a92fb4ceb0a2cbfebc17bf/numpy-2.5.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3893adc2dc7c0412ba76777db55a049215d99c9aa3113003be8f49f4f1290ab9", size = 16647250, upload-time = "2026-06-21T20:56:51.542Z" }, - { url = "https://files.pythonhosted.org/packages/3c/81/97060281b602ed07f21b12f4ec409eac1f75a2f91fbc829ed8b2becf3ad4/numpy-2.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:835e454dd99b238cdc5a3f63bce2371296f5ebc53ca1e0f8e6ddbb6d92a29aab", size = 16512864, upload-time = "2026-06-21T20:56:55.401Z" }, - { url = "https://files.pythonhosted.org/packages/33/ab/4496208146911f8d8ddb54f68a972aafa6c8d44babcb2ea03b0e5cc87c9d/numpy-2.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6f9836778081a0a3c02a6a21493f3e9f5b311f8d2541934f31f05583dc999ea4", size = 18408407, upload-time = "2026-06-21T20:56:57.75Z" }, - { url = "https://files.pythonhosted.org/packages/d4/9f/a4df67c181e4ee8b467aa3332dc2db10fd5c515136831302f3ca48bc0a01/numpy-2.5.0-cp313-cp313-win32.whl", hash = "sha256:0b525be4744b60bb0557ac872d53ef07d085b5f39622bc579c98d3809d05b988", size = 6054431, upload-time = "2026-06-21T20:57:00.016Z" }, - { url = "https://files.pythonhosted.org/packages/30/53/491e1c47c55b62ccc6a63c1c5b8635c73fc2258dddeb9bda27cae4a0ae96/numpy-2.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:44353e2878930039db472b99dc353d749826e4010bd4d2a7f835e94a97a5c748", size = 12414420, upload-time = "2026-06-21T20:57:01.815Z" }, - { url = "https://files.pythonhosted.org/packages/eb/4a/25c2906f541e9d9f4c5769764db732e6627be91a13f4724fa10634d77db4/numpy-2.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:48f54b00711f83a5f796b70c518e8c2b3c5848dda03a54911f23eb68519b9b60", size = 10339533, upload-time = "2026-06-21T20:57:03.961Z" }, - { url = "https://files.pythonhosted.org/packages/86/ad/abc44aaceaf7b17ee1edde2bbb4458da591bc79574cffff50c4bb35f00d1/numpy-2.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f27582c55ba4c750b7c58c8faf021d2cd9324a662b466229db8a417b41368af9", size = 16783807, upload-time = "2026-06-21T20:57:06.253Z" }, - { url = "https://files.pythonhosted.org/packages/5d/39/b72e168daf9c00fb20c9fc996d00437ccecdef3102387775d29d7a62576d/numpy-2.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:28e7137057d551e4a83c4ae414e3451f50568409db7569aacc7f9811ee06a446", size = 11765215, upload-time = "2026-06-21T20:57:08.547Z" }, - { url = "https://files.pythonhosted.org/packages/f7/a0/8400a9c0e3625182347593f5e1f57da9a617a534794805c8df5518154ddc/numpy-2.5.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:e1da54b53e75cd9fcfc23efcc7edab2c6aecf97b6037566d8a0fe804af8ec57c", size = 5324493, upload-time = "2026-06-21T20:57:11.012Z" }, - { url = "https://files.pythonhosted.org/packages/f6/8c/0d104deaa0401c93395a629ec902891618a2eff76d19229139cb5a887bfc/numpy-2.5.0-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:694d8f74e156f7fd01179f1aa8faa2f648ab6ae0f70b6c3fe57a03249aea2303", size = 6645211, upload-time = "2026-06-21T20:57:12.919Z" }, - { url = "https://files.pythonhosted.org/packages/6a/d9/4a4a628c812750363786afc3d33492709a5cd64b215469c16b0f6c7bb811/numpy-2.5.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1a7569a7b53c77716f036bb28cb1c91f166a26ec7d9502cd1e4bdfe502fdec22", size = 15166004, upload-time = "2026-06-21T20:57:14.717Z" }, - { url = "https://files.pythonhosted.org/packages/a0/5e/2a902317d7fc4aa93236e80c932662dadfc459b323d758329e01775125e1/numpy-2.5.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:39a0433bd4086ebd462960cf375e19195bb07b53dc1d87dd5fcf47ad78576f03", size = 16650797, upload-time = "2026-06-21T20:57:16.906Z" }, - { url = "https://files.pythonhosted.org/packages/e9/a0/a0090e6329f4ca5992c07847bb579c5259a19953dc57255bb08793142ffb/numpy-2.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:929f0c79ac38bcbd7154fe631dc907abfeddbcc5027a896bd1f7767323271e7a", size = 16524647, upload-time = "2026-06-21T20:57:19.165Z" }, - { url = "https://files.pythonhosted.org/packages/5e/7d/6caf27734c42b65837e7461ed0dbbd6b6fc835060c9714ec59d673bb383a/numpy-2.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cc4f247a47bbf070bfd70be53ccdcf47b800af563535e7bbe172322197c30e21", size = 18411841, upload-time = "2026-06-21T20:57:21.638Z" }, - { url = "https://files.pythonhosted.org/packages/13/dc/26edadbd812536769a82c2e9e002234e33feb5da43061d47a044f6d309b7/numpy-2.5.0-cp314-cp314-win32.whl", hash = "sha256:5dc71423499fab3f46f7a7201155ade1669ea101f2f429d332df9e72f8161731", size = 6106361, upload-time = "2026-06-21T20:57:23.844Z" }, - { url = "https://files.pythonhosted.org/packages/f2/9e/4dd1459282229a72d92dece2ae9138e5cac94a72263a7ceb48f37434c925/numpy-2.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:ebb81d9d5443e0309d6c54894c3fbed74ad7da0714352a67b6d773cd189eae73", size = 12551749, upload-time = "2026-06-21T20:57:25.945Z" }, - { url = "https://files.pythonhosted.org/packages/05/a7/6bc6384c080b86c7f6c85c5bc5b540b24f4f679cd144791d99574e90d462/numpy-2.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:3b94d0d0deceebfad3e67ae5c0e5eb87371e8f7a0581cd04a779928c2450cf1e", size = 10617072, upload-time = "2026-06-21T20:57:28.175Z" }, - { url = "https://files.pythonhosted.org/packages/86/6b/4a2b71d66ada5608ae02b63f150dfad520f6940721cb7f029ad270befc0e/numpy-2.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:22f3d43e362d650bc39db1f17851302874a148ca95ba6981c1dfb5fa6862f35b", size = 11881067, upload-time = "2026-06-21T20:57:30.104Z" }, - { url = "https://files.pythonhosted.org/packages/dc/b2/d365eb40a20efb49d67e9feb90494ed8511282ee1f5fa16006675c65397d/numpy-2.5.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:243563efb4cd7528a264567e9fd206c87826457322521d06206a00bfa316c927", size = 5440290, upload-time = "2026-06-21T20:57:32.193Z" }, - { url = "https://files.pythonhosted.org/packages/fa/5e/e9c03188de5f9b767e46a8fe988bcfd3efad066a4a3fda8b9cb11a93f895/numpy-2.5.0-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:84881d825ca75249b189bbee875fcfe3238aa5c479e6100893cda566e8e86826", size = 6748371, upload-time = "2026-06-21T20:57:33.933Z" }, - { url = "https://files.pythonhosted.org/packages/fd/1d/68c186a38a5027bae2c4ddd5ea681fdaf8b4d30fb7301def6d8ad270390f/numpy-2.5.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cda12aa4779d42b8771180aba759c96f527d43446d8f380ab59e2b35e8489efd", size = 15214643, upload-time = "2026-06-21T20:57:35.677Z" }, - { url = "https://files.pythonhosted.org/packages/8c/67/73f67b7c7e20635baae9c4c3ead4ae7326a005900297a6110971abd62eb5/numpy-2.5.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c0121101093d2bd74981b10f8837d78e794a8ff57834eb27179f49e1ba11ac6", size = 16690128, upload-time = "2026-06-21T20:57:38.159Z" }, - { url = "https://files.pythonhosted.org/packages/eb/05/d4c1fb0c46d02a27d6b2b8b319a78c90937acec8631c1641874670b31e6f/numpy-2.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d371c92cfa09da00022f501ab67fafaea813d752eb30ac44336d45b1e5b0268a", size = 16577902, upload-time = "2026-06-21T20:57:40.447Z" }, - { url = "https://files.pythonhosted.org/packages/9e/1d/771c797d50fa26e4888989cccf1d50ee51f530d4e455ad2692dcb64fa711/numpy-2.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9990713e9c38154c6861e7547f1e3fc7a87e75ff09bab24ef1cc81d81c2835e9", size = 18452814, upload-time = "2026-06-21T20:57:42.875Z" }, - { url = "https://files.pythonhosted.org/packages/e8/46/52fc0d2a68d7643f0f149eeea5a5d8ea2a3507056ac8afa83c9212606e8b/numpy-2.5.0-cp314-cp314t-win32.whl", hash = "sha256:edadfbd4794b1086c0d822f81863e8a68fc129d132fd0bb9e31e955d7fbbbdb7", size = 6253168, upload-time = "2026-06-21T20:57:45.101Z" }, - { url = "https://files.pythonhosted.org/packages/2a/be/6c8d1118b5f13b2881dc095d5b345de19c6638b8959c17409b6eff84c8aa/numpy-2.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f7e5fa4382967ae6548bd2f174219afb908e294b0d5f625af01166edd5f7d9aa", size = 12736286, upload-time = "2026-06-21T20:57:46.935Z" }, - { url = "https://files.pythonhosted.org/packages/fd/6a/d3a169aaf8536cf228d56a09e04bcb713a2fe4410d4e2105b9419b5a9c89/numpy-2.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:016623417bb330d719d579daf2d6b9a01ddc52e41a9ed61a47f39fde46dcd865", size = 10686451, upload-time = "2026-06-21T20:57:49.313Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/22/fd/89965aa4ac08c74998539fcbf24fa3540f3e15237fbeb6bcf9c908f4aade/numpy-2.5.1.tar.gz", hash = "sha256:a48a113e6afea91f5608793bafa7ef2ad481fefbda87ec5069f483de61cb9fa3", size = 20755553, upload-time = "2026-07-04T17:08:00.933Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/7b/14687aa674250e5e546f616f486b0d56d3631cd5b2415739141ce40bdcea/numpy-2.5.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2c889b56fe48b1018f764b0eec8df59ab654e9148aa91faa12596043500de277", size = 16801574, upload-time = "2026-07-04T17:06:12.423Z" }, + { url = "https://files.pythonhosted.org/packages/e1/19/cc5bb2a3f2913d27d6dbb2c78d25921fabaedc6741d4a5a615a11f3c5bf3/numpy-2.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ab451b59c5643c570974c43aef780703ef1d3b4965d2be07afd530615a9358d1", size = 11772250, upload-time = "2026-07-04T17:06:15.726Z" }, + { url = "https://files.pythonhosted.org/packages/42/77/fdf34a71dd30f54979b18603bee915e0aaf825b07afe79acd60b04b691e2/numpy-2.5.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:78798bd5b9ad744056af8efa90e3b9ddaa53272a0848a483084a1cc0a13b2dc0", size = 5331516, upload-time = "2026-07-04T17:06:17.913Z" }, + { url = "https://files.pythonhosted.org/packages/ce/e2/eb7efa015b4cce41e2517bf182a7fce0d7d5b9d9ed76a29bfa0f4fe4505c/numpy-2.5.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:2ae0ca40bcb22d6ba59c1dfd5446f49940b0f2d821fde133f10dda11f816b84e", size = 6664863, upload-time = "2026-07-04T17:06:20.02Z" }, + { url = "https://files.pythonhosted.org/packages/a9/4b/a2b32dd94ee9ffbeecb28152240042a3949db33b1c834d44090b80e1b3b8/numpy-2.5.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:61ac47e772e6b8ea489e1d2f441a34c5c3ac17327e7ce294cbdf535795ad4e75", size = 15167977, upload-time = "2026-07-04T17:06:21.621Z" }, + { url = "https://files.pythonhosted.org/packages/b8/a9/6e73d68500f80773f65f0654ea932019d6694329a0eb0ed0533de38df376/numpy-2.5.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:59fda5e192b570217ec2580c96f00e9a7e12ef6866a900eb089b62c1a32545ca", size = 16672469, upload-time = "2026-07-04T17:06:24.064Z" }, + { url = "https://files.pythonhosted.org/packages/24/7d/ad3e59015135f5261c95fd4cafeff159c955febd83a99a1d9250c4233815/numpy-2.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f7119ebff1a9829e9f431a4f9d28e703023bb6b9fe7c8f724467dbfc27c94ab3", size = 16527531, upload-time = "2026-07-04T17:06:26.69Z" }, + { url = "https://files.pythonhosted.org/packages/83/d0/a39b2fbcde9cb17a1dac678f254b33a6336298af9df338824c685425d5e8/numpy-2.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e824c2acf8862052246be5a44c15da1777940c60d010dd2aab897824d9c430f9", size = 18431940, upload-time = "2026-07-04T17:06:29.521Z" }, + { url = "https://files.pythonhosted.org/packages/04/12/cff070947791c1ed425ff76413189adbdc2fbe215eba7ce7fa454a03c7f8/numpy-2.5.1-cp312-cp312-win32.whl", hash = "sha256:08d60c810432eb83360958dea0999ac4cfb94531ea8efcbf0b7f277c2068aeb2", size = 6066764, upload-time = "2026-07-04T17:06:32.571Z" }, + { url = "https://files.pythonhosted.org/packages/65/66/53f31807a48a750f9d748da273bc3fcedd12b27ff1f3e373bfec55ef2dc0/numpy-2.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:f7d60026c0bdb1380e83bfa7a0419c4577ee4b9a08880afcb6dadeb74c649fa2", size = 12430966, upload-time = "2026-07-04T17:06:34.926Z" }, + { url = "https://files.pythonhosted.org/packages/2b/2a/d1a88066b1c14186f5d3c0d18c94f17b064511982bab0578d49ee9d43c29/numpy-2.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:17a25e09640602e10bc8de0e6fa2b3fd68eedd84ba6d7842dc8f32f9ab87bd0b", size = 10350488, upload-time = "2026-07-04T17:06:37.785Z" }, + { url = "https://files.pythonhosted.org/packages/eb/07/ec2a3f0c91761581d4b7104a740791800025983f9a4dc4e73f91a99aeac4/numpy-2.5.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0bfebd8695f9863592fe744be833a258120b14a9f39da255e8aa8fade2c0ddd1", size = 16796419, upload-time = "2026-07-04T17:06:40.37Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ab/ddb499fc4f8780354395face5b65c7fd107bcd6e1d667a5f07d046956f6f/numpy-2.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:30b44a6b53a7ae63c54c089a8726e5563ed302716c5b7ccc85afade40b0e7ff6", size = 11765832, upload-time = "2026-07-04T17:06:42.768Z" }, + { url = "https://files.pythonhosted.org/packages/88/b3/3c28c558a09fc72100c646dac6d2fce8e834c471b0edca01a29996706117/numpy-2.5.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:6165343f81b56ef8f514f396989e529b61d9dc709b99421b07e9f3e698e2287d", size = 5325143, upload-time = "2026-07-04T17:06:45.466Z" }, + { url = "https://files.pythonhosted.org/packages/5e/0e/ce19b985bb15c596f4f05954e76cccc77c845083b3b8f938a6c68e523128/numpy-2.5.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4939237038ada79308dda3204ac6462df056b5672b2e25db1149cf873668b3e1", size = 6659749, upload-time = "2026-07-04T17:06:47.288Z" }, + { url = "https://files.pythonhosted.org/packages/2e/20/1ee6614d64332a1bba6411f38e68cb79eec1b2459e20a623777c5c5492a2/numpy-2.5.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c6759f538fb912fc46de0a6b1758ccf7b57bc7c7ebebc23974fdac3de8db0cd", size = 15164716, upload-time = "2026-07-04T17:06:49.494Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a7/2bcd3fdbb87804755c35b729bf8709d62025c5f4cfd7d5b2415997097515/numpy-2.5.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9726558e8db4a5bf7929a70ae50f63abda4daf0efe810e3bfbab95976f75fc1a", size = 16661440, upload-time = "2026-07-04T17:06:52.061Z" }, + { url = "https://files.pythonhosted.org/packages/fc/d7/a41e3310c886fe457d36e670bbf24fae411aca8a7b6ad92a32afd924077c/numpy-2.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3935f3b419b244a02732676fa5317a9193cc596a4c0646db07e5b421229ac9f7", size = 16526305, upload-time = "2026-07-04T17:06:54.605Z" }, + { url = "https://files.pythonhosted.org/packages/53/75/4333a9a707c1edd3a4e1a0c58eca52c0f31e55089fa80db02b5565b24df7/numpy-2.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dc932a65ded7ce9013d120845a2514dcccb1a67bfc8deb8d37633762951904a6", size = 18423008, upload-time = "2026-07-04T17:06:57.54Z" }, + { url = "https://files.pythonhosted.org/packages/ee/90/e314a32b1c11a2ffe818ddad3a57b50b4b6e1b6c487192eb50cdef0415d0/numpy-2.5.1-cp313-cp313-win32.whl", hash = "sha256:4b4ff1608417eb7a59da7b967bbb798cacfe071d2caf526a24281cd562072ed9", size = 6063885, upload-time = "2026-07-04T17:07:00.14Z" }, + { url = "https://files.pythonhosted.org/packages/10/70/800b3fca480af32df9e8ea9f3d4a0c8feb4b32d7f195d174eabbda4829ad/numpy-2.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:6c3fe51bc6a16453d452997053454f309e8e0ed7b42d6b361ce4ac8c32913d74", size = 12425674, upload-time = "2026-07-04T17:07:02.387Z" }, + { url = "https://files.pythonhosted.org/packages/8b/0b/196350c122f50f6ca56846f2d71efd5e0d24b7b2e07355e019b2e2c7a11e/numpy-2.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:f7feb014281029e628ba2d5a007407443b06e418b6fe451d1e2adcbc8eba0107", size = 10350256, upload-time = "2026-07-04T17:07:04.878Z" }, + { url = "https://files.pythonhosted.org/packages/db/f4/731b6085a83faf6ca843394cbd5e217280c214399f7e8b21b9f552af0ae2/numpy-2.5.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7c786fe9a5bbe360022e584c5a34cf6b54265c71bd7ec8ac3d8fec38968071f8", size = 16795063, upload-time = "2026-07-04T17:07:07.374Z" }, + { url = "https://files.pythonhosted.org/packages/bf/64/0e215f2048dd11a55bb989ed41b3585ef57452404e638d703a211a3e4157/numpy-2.5.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32985c896d897419ef8da6917872d80b78ad0ea26d85b23245c7366ffde76d75", size = 11776652, upload-time = "2026-07-04T17:07:09.907Z" }, + { url = "https://files.pythonhosted.org/packages/b5/59/2b844c7a6e9deff69b404a66221e1542937734f65d5e6e39411876053862/numpy-2.5.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:efd736408cc97c79b9e6917338dfc8f06013b2274f992e96b1d9a81a71e2a2c2", size = 5335944, upload-time = "2026-07-04T17:07:12.227Z" }, + { url = "https://files.pythonhosted.org/packages/86/51/9bf7cb2cabcebc9e017e4ec7e6322b378317a542c08b4cb68479c1efc716/numpy-2.5.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:ab84dc6b074fa881cae55bea94cc4f68e285181ba7f32497bf7dee6b1496165b", size = 6656266, upload-time = "2026-07-04T17:07:14.368Z" }, + { url = "https://files.pythonhosted.org/packages/83/3e/fb7615b211b82a32f44d5180a6d421b61f84d4fadd578b48ba4ac34e189f/numpy-2.5.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:caf3e317d33d60c37986b452613f4ab51246d0691350c03d0cb4a898627f4a95", size = 15179720, upload-time = "2026-07-04T17:07:16.272Z" }, + { url = "https://files.pythonhosted.org/packages/41/5f/0f992cb24560673496c5d68de61913b57166ce530ffda07c1f280e0cc464/numpy-2.5.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:54ad769f17bc2d833b620851989f62054fb9ab93c969d9e1dc3c8e3d56beea21", size = 16664835, upload-time = "2026-07-04T17:07:19.021Z" }, + { url = "https://files.pythonhosted.org/packages/a2/2f/97d6475ee91afe2587797d09446f9d3e475ad4cb681662d824809327b75a/numpy-2.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c12afb53450fa976d4c681c50a7423729a4c51c0465ed9f32b8a9cabbc472373", size = 16539135, upload-time = "2026-07-04T17:07:22.015Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5b/4db81e4ba0be7e2776b1de68c82aa862c7f8ec27e1b4927d4ae075e20678/numpy-2.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e8c11c405efc5ff6816d5983c96cdfa215bab3428961243af3ff59b228490438", size = 18426684, upload-time = "2026-07-04T17:07:24.941Z" }, + { url = "https://files.pythonhosted.org/packages/1f/64/c0ba2d90724d450279a7df8f32057241070250a26a7e2b5337d77347f481/numpy-2.5.1-cp314-cp314-win32.whl", hash = "sha256:f2479a47f8d5932d1718168a681ad6e536a9df484c83cfcf9de365e164537ace", size = 6116103, upload-time = "2026-07-04T17:07:27.622Z" }, + { url = "https://files.pythonhosted.org/packages/c1/1a/837f9ed7405adcd7a40538792eb169eddd8fa5630c16a1ef49dae71a30f4/numpy-2.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:24d0eb82c0541d3415a33425db64ae439dffccd7b4dbcb30e7c35120205c506a", size = 12562177, upload-time = "2026-07-04T17:07:29.887Z" }, + { url = "https://files.pythonhosted.org/packages/22/ed/49707938b6dd0a78a9178dd93227dc89e4c11af47f5c798d70366e8d0483/numpy-2.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:5a4c988b38d261deeeaad9954e3deb091ad905c94e8bb6708654ef1d97f286b0", size = 10627739, upload-time = "2026-07-04T17:07:32.568Z" }, + { url = "https://files.pythonhosted.org/packages/a6/c7/bb4b882cfe7f299cbc8b66e42e7dd78cf9d14e40f9469fc5e3db7e15b3bd/numpy-2.5.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a33276be12fa045805f477f22482088b66bb758ffbe89a9d21457de863a32e22", size = 11894709, upload-time = "2026-07-04T17:07:34.941Z" }, + { url = "https://files.pythonhosted.org/packages/40/3f/5af7f4a7f6224aef48017aa82bb6174c7a659d724be0c75017b7e64a55b4/numpy-2.5.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:f089d7b00756190aacf1f5d34bdf38c3c430ac82b4f868f8cede73380460fce7", size = 5453810, upload-time = "2026-07-04T17:07:37.495Z" }, + { url = "https://files.pythonhosted.org/packages/20/c9/3474309bc94d634d3f9c3eddf03250ecb8c22cd948ef16fef69a77cc5d7b/numpy-2.5.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:09e9bfd8d2cf479c7d174804fb3811c53a8e9f20a37444008606b57d6b7a826d", size = 6761189, upload-time = "2026-07-04T17:07:39.563Z" }, + { url = "https://files.pythonhosted.org/packages/90/8a/558ae39fdd55d7e7f7fef9a84a6e964ac6b23edbd2a07e52bb084500507d/numpy-2.5.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e68d8dd1e7eba712948f2053a29ec86917bc70ba1358df869d9f06649ef9cf09", size = 15225039, upload-time = "2026-07-04T17:07:41.682Z" }, + { url = "https://files.pythonhosted.org/packages/63/27/ca7392b2d030277bdf0273e7d23255b3ee57d57a7c170a6f4fb3981e1e5d/numpy-2.5.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:99d5095fa265a0c4152e7bb12759e14381ef5496152f1ce58f44bdf55c44beb4", size = 16701306, upload-time = "2026-07-04T17:07:44.611Z" }, + { url = "https://files.pythonhosted.org/packages/02/42/03d53ae7996c44d4374a8262e9dc41671fd56cbb98f7d47ef85cf5da4c6b/numpy-2.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ab87a91b3cc3382b8956095bd8f95e00cf679bb81554339be1a2ba404a1473c1", size = 16589955, upload-time = "2026-07-04T17:07:47.694Z" }, + { url = "https://files.pythonhosted.org/packages/7b/15/6c1784ae469640e65db111e9a34b3d0f14d91e8a38b9ce34810ced370dbb/numpy-2.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:224ca51130ef7da85bea2191625181cb4f337f9cb64b471f10c1a12aa8b60077", size = 18464252, upload-time = "2026-07-04T17:07:50.684Z" }, + { url = "https://files.pythonhosted.org/packages/94/a8/f98e50356cf167df656c526c2dfeec2d7dde182f2a3da4b458a5938e2776/numpy-2.5.1-cp314-cp314t-win32.whl", hash = "sha256:6eab239876581b2b3c5a242281b6007bbdbcd1c7085d7709bb57c5929b11e6bf", size = 6263298, upload-time = "2026-07-04T17:07:53.445Z" }, + { url = "https://files.pythonhosted.org/packages/72/ac/96ae880cdecad0b3275d9359fcec72667b49a4863c9f12942e43679dda02/numpy-2.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:83ce9c80d5b521b0d77ddcbe5447c218d247929b6cc056ca5351342accfff0af", size = 12748623, upload-time = "2026-07-04T17:07:55.384Z" }, + { url = "https://files.pythonhosted.org/packages/a1/5a/4d2b1601df3602dba7a14f3348ba9bfe94a18adb428e693df6154c293831/numpy-2.5.1-cp314-cp314t-win_arm64.whl", hash = "sha256:5a6db61f9aaa57e369905c67d852045d3c4f7126405b29d09b19dec118e9c9cb", size = 10697674, upload-time = "2026-07-04T17:07:58.506Z" }, ] [[package]] @@ -2464,7 +2464,7 @@ dependencies = [ { name = "cuda-python" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "typing-extensions" }, ] wheels = [ @@ -2628,7 +2628,7 @@ resolution-markers = [ ] dependencies = [ { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "python-dateutil", marker = "python_full_version >= '3.11'" }, { name = "tzdata", marker = "(python_full_version >= '3.11' and sys_platform == 'emscripten') or (python_full_version >= '3.11' and sys_platform == 'win32')" }, ] @@ -2701,7 +2701,7 @@ dependencies = [ { name = "huggingface-hub" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "packaging" }, { name = "psutil" }, { name = "pyyaml" }, @@ -2717,100 +2717,96 @@ wheels = [ [[package]] name = "pillow" -version = "12.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8c/21/c2bcdd5906101a30244eaffc1b6e6ce71a31bd0742a01eb89e660ebfac2d/pillow-12.2.0.tar.gz", hash = "sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5", size = 46987819, upload-time = "2026-04-01T14:46:17.687Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/aa/d0b28e1c811cd4d5f5c2bfe2e022292bd255ae5744a3b9ac7d6c8f72dd75/pillow-12.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:a4e8f36e677d3336f35089648c8955c51c6d386a13cf6ee9c189c5f5bd713a9f", size = 5354355, upload-time = "2026-04-01T14:42:15.402Z" }, - { url = "https://files.pythonhosted.org/packages/27/8e/1d5b39b8ae2bd7650d0c7b6abb9602d16043ead9ebbfef4bc4047454da2a/pillow-12.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e589959f10d9824d39b350472b92f0ce3b443c0a3442ebf41c40cb8361c5b97", size = 4695871, upload-time = "2026-04-01T14:42:18.234Z" }, - { url = "https://files.pythonhosted.org/packages/f0/c5/dcb7a6ca6b7d3be41a76958e90018d56c8462166b3ef223150360850c8da/pillow-12.2.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a52edc8bfff4429aaabdf4d9ee0daadbbf8562364f940937b941f87a4290f5ff", size = 6269734, upload-time = "2026-04-01T14:42:20.608Z" }, - { url = "https://files.pythonhosted.org/packages/ea/f1/aa1bb13b2f4eba914e9637893c73f2af8e48d7d4023b9d3750d4c5eb2d0c/pillow-12.2.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:975385f4776fafde056abb318f612ef6285b10a1f12b8570f3647ad0d74b48ec", size = 8076080, upload-time = "2026-04-01T14:42:23.095Z" }, - { url = "https://files.pythonhosted.org/packages/a1/2a/8c79d6a53169937784604a8ae8d77e45888c41537f7f6f65ed1f407fe66d/pillow-12.2.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd9c0c7a0c681a347b3194c500cb1e6ca9cab053ea4d82a5cf45b6b754560136", size = 6382236, upload-time = "2026-04-01T14:42:25.82Z" }, - { url = "https://files.pythonhosted.org/packages/b5/42/bbcb6051030e1e421d103ce7a8ecadf837aa2f39b8f82ef1a8d37c3d4ebc/pillow-12.2.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:88d387ff40b3ff7c274947ed3125dedf5262ec6919d83946753b5f3d7c67ea4c", size = 7070220, upload-time = "2026-04-01T14:42:28.68Z" }, - { url = "https://files.pythonhosted.org/packages/3f/e1/c2a7d6dd8cfa6b231227da096fd2d58754bab3603b9d73bf609d3c18b64f/pillow-12.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:51c4167c34b0d8ba05b547a3bb23578d0ba17b80a5593f93bd8ecb123dd336a3", size = 6493124, upload-time = "2026-04-01T14:42:31.579Z" }, - { url = "https://files.pythonhosted.org/packages/5f/41/7c8617da5d32e1d2f026e509484fdb6f3ad7efaef1749a0c1928adbb099e/pillow-12.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:34c0d99ecccea270c04882cb3b86e7b57296079c9a4aff88cb3b33563d95afaa", size = 7194324, upload-time = "2026-04-01T14:42:34.615Z" }, - { url = "https://files.pythonhosted.org/packages/2d/de/a777627e19fd6d62f84070ee1521adde5eeda4855b5cf60fe0b149118bca/pillow-12.2.0-cp310-cp310-win32.whl", hash = "sha256:b85f66ae9eb53e860a873b858b789217ba505e5e405a24b85c0464822fe88032", size = 6376363, upload-time = "2026-04-01T14:42:37.19Z" }, - { url = "https://files.pythonhosted.org/packages/e7/34/fc4cb5204896465842767b96d250c08410f01f2f28afc43b257de842eed5/pillow-12.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:673aa32138f3e7531ccdbca7b3901dba9b70940a19ccecc6a37c77d5fdeb05b5", size = 7083523, upload-time = "2026-04-01T14:42:39.62Z" }, - { url = "https://files.pythonhosted.org/packages/2d/a0/32852d36bc7709f14dc3f64f929a275e958ad8c19a6deba9610d458e28b3/pillow-12.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:3e080565d8d7c671db5802eedfb438e5565ffa40115216eabb8cd52d0ecce024", size = 2463318, upload-time = "2026-04-01T14:42:42.063Z" }, - { url = "https://files.pythonhosted.org/packages/68/e1/748f5663efe6edcfc4e74b2b93edfb9b8b99b67f21a854c3ae416500a2d9/pillow-12.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:8be29e59487a79f173507c30ddf57e733a357f67881430449bb32614075a40ab", size = 5354347, upload-time = "2026-04-01T14:42:44.255Z" }, - { url = "https://files.pythonhosted.org/packages/47/a1/d5ff69e747374c33a3b53b9f98cca7889fce1fd03d79cdc4e1bccc6c5a87/pillow-12.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:71cde9a1e1551df7d34a25462fc60325e8a11a82cc2e2f54578e5e9a1e153d65", size = 4695873, upload-time = "2026-04-01T14:42:46.452Z" }, - { url = "https://files.pythonhosted.org/packages/df/21/e3fbdf54408a973c7f7f89a23b2cb97a7ef30c61ab4142af31eee6aebc88/pillow-12.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f490f9368b6fc026f021db16d7ec2fbf7d89e2edb42e8ec09d2c60505f5729c7", size = 6280168, upload-time = "2026-04-01T14:42:49.228Z" }, - { url = "https://files.pythonhosted.org/packages/d3/f1/00b7278c7dd52b17ad4329153748f87b6756ec195ff786c2bdf12518337d/pillow-12.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8bd7903a5f2a4545f6fd5935c90058b89d30045568985a71c79f5fd6edf9b91e", size = 8088188, upload-time = "2026-04-01T14:42:51.735Z" }, - { url = "https://files.pythonhosted.org/packages/ad/cf/220a5994ef1b10e70e85748b75649d77d506499352be135a4989c957b701/pillow-12.2.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3997232e10d2920a68d25191392e3a4487d8183039e1c74c2297f00ed1c50705", size = 6394401, upload-time = "2026-04-01T14:42:54.343Z" }, - { url = "https://files.pythonhosted.org/packages/e9/bd/e51a61b1054f09437acfbc2ff9106c30d1eb76bc1453d428399946781253/pillow-12.2.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e74473c875d78b8e9d5da2a70f7099549f9eb37ded4e2f6a463e60125bccd176", size = 7079655, upload-time = "2026-04-01T14:42:56.954Z" }, - { url = "https://files.pythonhosted.org/packages/6b/3d/45132c57d5fb4b5744567c3817026480ac7fc3ce5d4c47902bc0e7f6f853/pillow-12.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:56a3f9c60a13133a98ecff6197af34d7824de9b7b38c3654861a725c970c197b", size = 6503105, upload-time = "2026-04-01T14:42:59.847Z" }, - { url = "https://files.pythonhosted.org/packages/7d/2e/9df2fc1e82097b1df3dce58dc43286aa01068e918c07574711fcc53e6fb4/pillow-12.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:90e6f81de50ad6b534cab6e5aef77ff6e37722b2f5d908686f4a5c9eba17a909", size = 7203402, upload-time = "2026-04-01T14:43:02.664Z" }, - { url = "https://files.pythonhosted.org/packages/bd/2e/2941e42858ebb67e50ae741473de81c2984e6eff7b397017623c676e2e8d/pillow-12.2.0-cp311-cp311-win32.whl", hash = "sha256:8c984051042858021a54926eb597d6ee3012393ce9c181814115df4c60b9a808", size = 6378149, upload-time = "2026-04-01T14:43:05.274Z" }, - { url = "https://files.pythonhosted.org/packages/69/42/836b6f3cd7f3e5fa10a1f1a5420447c17966044c8fbf589cc0452d5502db/pillow-12.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e6b2a0c538fc200b38ff9eb6628228b77908c319a005815f2dde585a0664b60", size = 7082626, upload-time = "2026-04-01T14:43:08.557Z" }, - { url = "https://files.pythonhosted.org/packages/c2/88/549194b5d6f1f494b485e493edc6693c0a16f4ada488e5bd974ed1f42fad/pillow-12.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:9a8a34cc89c67a65ea7437ce257cea81a9dad65b29805f3ecee8c8fe8ff25ffe", size = 2463531, upload-time = "2026-04-01T14:43:10.743Z" }, - { url = "https://files.pythonhosted.org/packages/58/be/7482c8a5ebebbc6470b3eb791812fff7d5e0216c2be3827b30b8bb6603ed/pillow-12.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d192a155bbcec180f8564f693e6fd9bccff5a7af9b32e2e4bf8c9c69dbad6b5", size = 5308279, upload-time = "2026-04-01T14:43:13.246Z" }, - { url = "https://files.pythonhosted.org/packages/d8/95/0a351b9289c2b5cbde0bacd4a83ebc44023e835490a727b2a3bd60ddc0f4/pillow-12.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3f40b3c5a968281fd507d519e444c35f0ff171237f4fdde090dd60699458421", size = 4695490, upload-time = "2026-04-01T14:43:15.584Z" }, - { url = "https://files.pythonhosted.org/packages/de/af/4e8e6869cbed569d43c416fad3dc4ecb944cb5d9492defaed89ddd6fe871/pillow-12.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:03e7e372d5240cc23e9f07deca4d775c0817bffc641b01e9c3af208dbd300987", size = 6284462, upload-time = "2026-04-01T14:43:18.268Z" }, - { url = "https://files.pythonhosted.org/packages/e9/9e/c05e19657fd57841e476be1ab46c4d501bffbadbafdc31a6d665f8b737b6/pillow-12.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b86024e52a1b269467a802258c25521e6d742349d760728092e1bc2d135b4d76", size = 8094744, upload-time = "2026-04-01T14:43:20.716Z" }, - { url = "https://files.pythonhosted.org/packages/2b/54/1789c455ed10176066b6e7e6da1b01e50e36f94ba584dc68d9eebfe9156d/pillow-12.2.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7371b48c4fa448d20d2714c9a1f775a81155050d383333e0a6c15b1123dda005", size = 6398371, upload-time = "2026-04-01T14:43:23.443Z" }, - { url = "https://files.pythonhosted.org/packages/43/e3/fdc657359e919462369869f1c9f0e973f353f9a9ee295a39b1fea8ee1a77/pillow-12.2.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62f5409336adb0663b7caa0da5c7d9e7bdbaae9ce761d34669420c2a801b2780", size = 7087215, upload-time = "2026-04-01T14:43:26.758Z" }, - { url = "https://files.pythonhosted.org/packages/8b/f8/2f6825e441d5b1959d2ca5adec984210f1ec086435b0ed5f52c19b3b8a6e/pillow-12.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:01afa7cf67f74f09523699b4e88c73fb55c13346d212a59a2db1f86b0a63e8c5", size = 6509783, upload-time = "2026-04-01T14:43:29.56Z" }, - { url = "https://files.pythonhosted.org/packages/67/f9/029a27095ad20f854f9dba026b3ea6428548316e057e6fc3545409e86651/pillow-12.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc3d34d4a8fbec3e88a79b92e5465e0f9b842b628675850d860b8bd300b159f5", size = 7212112, upload-time = "2026-04-01T14:43:32.091Z" }, - { url = "https://files.pythonhosted.org/packages/be/42/025cfe05d1be22dbfdb4f264fe9de1ccda83f66e4fc3aac94748e784af04/pillow-12.2.0-cp312-cp312-win32.whl", hash = "sha256:58f62cc0f00fd29e64b29f4fd923ffdb3859c9f9e6105bfc37ba1d08994e8940", size = 6378489, upload-time = "2026-04-01T14:43:34.601Z" }, - { url = "https://files.pythonhosted.org/packages/5d/7b/25a221d2c761c6a8ae21bfa3874988ff2583e19cf8a27bf2fee358df7942/pillow-12.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f84204dee22a783350679a0333981df803dac21a0190d706a50475e361c93f5", size = 7084129, upload-time = "2026-04-01T14:43:37.213Z" }, - { url = "https://files.pythonhosted.org/packages/10/e1/542a474affab20fd4a0f1836cb234e8493519da6b76899e30bcc5d990b8b/pillow-12.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:af73337013e0b3b46f175e79492d96845b16126ddf79c438d7ea7ff27783a414", size = 2463612, upload-time = "2026-04-01T14:43:39.421Z" }, - { url = "https://files.pythonhosted.org/packages/4a/01/53d10cf0dbad820a8db274d259a37ba50b88b24768ddccec07355382d5ad/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8297651f5b5679c19968abefd6bb84d95fe30ef712eb1b2d9b2d31ca61267f4c", size = 4100837, upload-time = "2026-04-01T14:43:41.506Z" }, - { url = "https://files.pythonhosted.org/packages/0f/98/f3a6657ecb698c937f6c76ee564882945f29b79bad496abcba0e84659ec5/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:50d8520da2a6ce0af445fa6d648c4273c3eeefbc32d7ce049f22e8b5c3daecc2", size = 4176528, upload-time = "2026-04-01T14:43:43.773Z" }, - { url = "https://files.pythonhosted.org/packages/69/bc/8986948f05e3ea490b8442ea1c1d4d990b24a7e43d8a51b2c7d8b1dced36/pillow-12.2.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:766cef22385fa1091258ad7e6216792b156dc16d8d3fa607e7545b2b72061f1c", size = 3640401, upload-time = "2026-04-01T14:43:45.87Z" }, - { url = "https://files.pythonhosted.org/packages/34/46/6c717baadcd62bc8ed51d238d521ab651eaa74838291bda1f86fe1f864c9/pillow-12.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5d2fd0fa6b5d9d1de415060363433f28da8b1526c1c129020435e186794b3795", size = 5308094, upload-time = "2026-04-01T14:43:48.438Z" }, - { url = "https://files.pythonhosted.org/packages/71/43/905a14a8b17fdb1ccb58d282454490662d2cb89a6bfec26af6d3520da5ec/pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56b25336f502b6ed02e889f4ece894a72612fe885889a6e8c4c80239ff6e5f5f", size = 4695402, upload-time = "2026-04-01T14:43:51.292Z" }, - { url = "https://files.pythonhosted.org/packages/73/dd/42107efcb777b16fa0393317eac58f5b5cf30e8392e266e76e51cff28c3d/pillow-12.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f1c943e96e85df3d3478f7b691f229887e143f81fedab9b20205349ab04d73ed", size = 6280005, upload-time = "2026-04-01T14:43:54.242Z" }, - { url = "https://files.pythonhosted.org/packages/a8/68/b93e09e5e8549019e61acf49f65b1a8530765a7f812c77a7461bca7e4494/pillow-12.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:03f6fab9219220f041c74aeaa2939ff0062bd5c364ba9ce037197f4c6d498cd9", size = 8090669, upload-time = "2026-04-01T14:43:57.335Z" }, - { url = "https://files.pythonhosted.org/packages/4b/6e/3ccb54ce8ec4ddd1accd2d89004308b7b0b21c4ac3d20fa70af4760a4330/pillow-12.2.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cdfebd752ec52bf5bb4e35d9c64b40826bc5b40a13df7c3cda20a2c03a0f5ed", size = 6395194, upload-time = "2026-04-01T14:43:59.864Z" }, - { url = "https://files.pythonhosted.org/packages/67/ee/21d4e8536afd1a328f01b359b4d3997b291ffd35a237c877b331c1c3b71c/pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eedf4b74eda2b5a4b2b2fb4c006d6295df3bf29e459e198c90ea48e130dc75c3", size = 7082423, upload-time = "2026-04-01T14:44:02.74Z" }, - { url = "https://files.pythonhosted.org/packages/78/5f/e9f86ab0146464e8c133fe85df987ed9e77e08b29d8d35f9f9f4d6f917ba/pillow-12.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:00a2865911330191c0b818c59103b58a5e697cae67042366970a6b6f1b20b7f9", size = 6505667, upload-time = "2026-04-01T14:44:05.381Z" }, - { url = "https://files.pythonhosted.org/packages/ed/1e/409007f56a2fdce61584fd3acbc2bbc259857d555196cedcadc68c015c82/pillow-12.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e1757442ed87f4912397c6d35a0db6a7b52592156014706f17658ff58bbf795", size = 7208580, upload-time = "2026-04-01T14:44:08.39Z" }, - { url = "https://files.pythonhosted.org/packages/23/c4/7349421080b12fb35414607b8871e9534546c128a11965fd4a7002ccfbee/pillow-12.2.0-cp313-cp313-win32.whl", hash = "sha256:144748b3af2d1b358d41286056d0003f47cb339b8c43a9ea42f5fea4d8c66b6e", size = 6375896, upload-time = "2026-04-01T14:44:11.197Z" }, - { url = "https://files.pythonhosted.org/packages/3f/82/8a3739a5e470b3c6cbb1d21d315800d8e16bff503d1f16b03a4ec3212786/pillow-12.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:390ede346628ccc626e5730107cde16c42d3836b89662a115a921f28440e6a3b", size = 7081266, upload-time = "2026-04-01T14:44:13.947Z" }, - { url = "https://files.pythonhosted.org/packages/c3/25/f968f618a062574294592f668218f8af564830ccebdd1fa6200f598e65c5/pillow-12.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:8023abc91fba39036dbce14a7d6535632f99c0b857807cbbbf21ecc9f4717f06", size = 2463508, upload-time = "2026-04-01T14:44:16.312Z" }, - { url = "https://files.pythonhosted.org/packages/4d/a4/b342930964e3cb4dce5038ae34b0eab4653334995336cd486c5a8c25a00c/pillow-12.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:042db20a421b9bafecc4b84a8b6e444686bd9d836c7fd24542db3e7df7baad9b", size = 5309927, upload-time = "2026-04-01T14:44:18.89Z" }, - { url = "https://files.pythonhosted.org/packages/9f/de/23198e0a65a9cf06123f5435a5d95cea62a635697f8f03d134d3f3a96151/pillow-12.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd025009355c926a84a612fecf58bb315a3f6814b17ead51a8e48d3823d9087f", size = 4698624, upload-time = "2026-04-01T14:44:21.115Z" }, - { url = "https://files.pythonhosted.org/packages/01/a6/1265e977f17d93ea37aa28aa81bad4fa597933879fac2520d24e021c8da3/pillow-12.2.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88ddbc66737e277852913bd1e07c150cc7bb124539f94c4e2df5344494e0a612", size = 6321252, upload-time = "2026-04-01T14:44:23.663Z" }, - { url = "https://files.pythonhosted.org/packages/3c/83/5982eb4a285967baa70340320be9f88e57665a387e3a53a7f0db8231a0cd/pillow-12.2.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d362d1878f00c142b7e1a16e6e5e780f02be8195123f164edf7eddd911eefe7c", size = 8126550, upload-time = "2026-04-01T14:44:26.772Z" }, - { url = "https://files.pythonhosted.org/packages/4e/48/6ffc514adce69f6050d0753b1a18fd920fce8cac87620d5a31231b04bfc5/pillow-12.2.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c727a6d53cb0018aadd8018c2b938376af27914a68a492f59dfcaca650d5eea", size = 6433114, upload-time = "2026-04-01T14:44:29.615Z" }, - { url = "https://files.pythonhosted.org/packages/36/a3/f9a77144231fb8d40ee27107b4463e205fa4677e2ca2548e14da5cf18dce/pillow-12.2.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:efd8c21c98c5cc60653bcb311bef2ce0401642b7ce9d09e03a7da87c878289d4", size = 7115667, upload-time = "2026-04-01T14:44:32.773Z" }, - { url = "https://files.pythonhosted.org/packages/c1/fc/ac4ee3041e7d5a565e1c4fd72a113f03b6394cc72ab7089d27608f8aaccb/pillow-12.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f08483a632889536b8139663db60f6724bfcb443c96f1b18855860d7d5c0fd4", size = 6538966, upload-time = "2026-04-01T14:44:35.252Z" }, - { url = "https://files.pythonhosted.org/packages/c0/a8/27fb307055087f3668f6d0a8ccb636e7431d56ed0750e07a60547b1e083e/pillow-12.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dac8d77255a37e81a2efcbd1fc05f1c15ee82200e6c240d7e127e25e365c39ea", size = 7238241, upload-time = "2026-04-01T14:44:37.875Z" }, - { url = "https://files.pythonhosted.org/packages/ad/4b/926ab182c07fccae9fcb120043464e1ff1564775ec8864f21a0ebce6ac25/pillow-12.2.0-cp313-cp313t-win32.whl", hash = "sha256:ee3120ae9dff32f121610bb08e4313be87e03efeadfc6c0d18f89127e24d0c24", size = 6379592, upload-time = "2026-04-01T14:44:40.336Z" }, - { url = "https://files.pythonhosted.org/packages/c2/c4/f9e476451a098181b30050cc4c9a3556b64c02cf6497ea421ac047e89e4b/pillow-12.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:325ca0528c6788d2a6c3d40e3568639398137346c3d6e66bb61db96b96511c98", size = 7085542, upload-time = "2026-04-01T14:44:43.251Z" }, - { url = "https://files.pythonhosted.org/packages/00/a4/285f12aeacbe2d6dc36c407dfbbe9e96d4a80b0fb710a337f6d2ad978c75/pillow-12.2.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2e5a76d03a6c6dcef67edabda7a52494afa4035021a79c8558e14af25313d453", size = 2465765, upload-time = "2026-04-01T14:44:45.996Z" }, - { url = "https://files.pythonhosted.org/packages/bf/98/4595daa2365416a86cb0d495248a393dfc84e96d62ad080c8546256cb9c0/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:3adc9215e8be0448ed6e814966ecf3d9952f0ea40eb14e89a102b87f450660d8", size = 4100848, upload-time = "2026-04-01T14:44:48.48Z" }, - { url = "https://files.pythonhosted.org/packages/0b/79/40184d464cf89f6663e18dfcf7ca21aae2491fff1a16127681bf1fa9b8cf/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:6a9adfc6d24b10f89588096364cc726174118c62130c817c2837c60cf08a392b", size = 4176515, upload-time = "2026-04-01T14:44:51.353Z" }, - { url = "https://files.pythonhosted.org/packages/b0/63/703f86fd4c422a9cf722833670f4f71418fb116b2853ff7da722ea43f184/pillow-12.2.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:6a6e67ea2e6feda684ed370f9a1c52e7a243631c025ba42149a2cc5934dec295", size = 3640159, upload-time = "2026-04-01T14:44:53.588Z" }, - { url = "https://files.pythonhosted.org/packages/71/e0/fb22f797187d0be2270f83500aab851536101b254bfa1eae10795709d283/pillow-12.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2bb4a8d594eacdfc59d9e5ad972aa8afdd48d584ffd5f13a937a664c3e7db0ed", size = 5312185, upload-time = "2026-04-01T14:44:56.039Z" }, - { url = "https://files.pythonhosted.org/packages/ba/8c/1a9e46228571de18f8e28f16fabdfc20212a5d019f3e3303452b3f0a580d/pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:80b2da48193b2f33ed0c32c38140f9d3186583ce7d516526d462645fd98660ae", size = 4695386, upload-time = "2026-04-01T14:44:58.663Z" }, - { url = "https://files.pythonhosted.org/packages/70/62/98f6b7f0c88b9addd0e87c217ded307b36be024d4ff8869a812b241d1345/pillow-12.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22db17c68434de69d8ecfc2fe821569195c0c373b25cccb9cbdacf2c6e53c601", size = 6280384, upload-time = "2026-04-01T14:45:01.5Z" }, - { url = "https://files.pythonhosted.org/packages/5e/03/688747d2e91cfbe0e64f316cd2e8005698f76ada3130d0194664174fa5de/pillow-12.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7b14cc0106cd9aecda615dd6903840a058b4700fcb817687d0ee4fc8b6e389be", size = 8091599, upload-time = "2026-04-01T14:45:04.5Z" }, - { url = "https://files.pythonhosted.org/packages/f6/35/577e22b936fcdd66537329b33af0b4ccfefaeabd8aec04b266528cddb33c/pillow-12.2.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cbeb542b2ebc6fcdacabf8aca8c1a97c9b3ad3927d46b8723f9d4f033288a0f", size = 6396021, upload-time = "2026-04-01T14:45:07.117Z" }, - { url = "https://files.pythonhosted.org/packages/11/8d/d2532ad2a603ca2b93ad9f5135732124e57811d0168155852f37fbce2458/pillow-12.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bfd07bc812fbd20395212969e41931001fd59eb55a60658b0e5710872e95286", size = 7083360, upload-time = "2026-04-01T14:45:09.763Z" }, - { url = "https://files.pythonhosted.org/packages/5e/26/d325f9f56c7e039034897e7380e9cc202b1e368bfd04d4cbe6a441f02885/pillow-12.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9aba9a17b623ef750a4d11b742cbafffeb48a869821252b30ee21b5e91392c50", size = 6507628, upload-time = "2026-04-01T14:45:12.378Z" }, - { url = "https://files.pythonhosted.org/packages/5f/f7/769d5632ffb0988f1c5e7660b3e731e30f7f8ec4318e94d0a5d674eb65a4/pillow-12.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:deede7c263feb25dba4e82ea23058a235dcc2fe1f6021025dc71f2b618e26104", size = 7209321, upload-time = "2026-04-01T14:45:15.122Z" }, - { url = "https://files.pythonhosted.org/packages/6a/7a/c253e3c645cd47f1aceea6a8bacdba9991bf45bb7dfe927f7c893e89c93c/pillow-12.2.0-cp314-cp314-win32.whl", hash = "sha256:632ff19b2778e43162304d50da0181ce24ac5bb8180122cbe1bf4673428328c7", size = 6479723, upload-time = "2026-04-01T14:45:17.797Z" }, - { url = "https://files.pythonhosted.org/packages/cd/8b/601e6566b957ca50e28725cb6c355c59c2c8609751efbecd980db44e0349/pillow-12.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:4e6c62e9d237e9b65fac06857d511e90d8461a32adcc1b9065ea0c0fa3a28150", size = 7217400, upload-time = "2026-04-01T14:45:20.529Z" }, - { url = "https://files.pythonhosted.org/packages/d6/94/220e46c73065c3e2951bb91c11a1fb636c8c9ad427ac3ce7d7f3359b9b2f/pillow-12.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:b1c1fbd8a5a1af3412a0810d060a78b5136ec0836c8a4ef9aa11807f2a22f4e1", size = 2554835, upload-time = "2026-04-01T14:45:23.162Z" }, - { url = "https://files.pythonhosted.org/packages/b6/ab/1b426a3974cb0e7da5c29ccff4807871d48110933a57207b5a676cccc155/pillow-12.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:57850958fe9c751670e49b2cecf6294acc99e562531f4bd317fa5ddee2068463", size = 5314225, upload-time = "2026-04-01T14:45:25.637Z" }, - { url = "https://files.pythonhosted.org/packages/19/1e/dce46f371be2438eecfee2a1960ee2a243bbe5e961890146d2dee1ff0f12/pillow-12.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d5d38f1411c0ed9f97bcb49b7bd59b6b7c314e0e27420e34d99d844b9ce3b6f3", size = 4698541, upload-time = "2026-04-01T14:45:28.355Z" }, - { url = "https://files.pythonhosted.org/packages/55/c3/7fbecf70adb3a0c33b77a300dc52e424dc22ad8cdc06557a2e49523b703d/pillow-12.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c0a9f29ca8e79f09de89293f82fc9b0270bb4af1d58bc98f540cc4aedf03166", size = 6322251, upload-time = "2026-04-01T14:45:30.924Z" }, - { url = "https://files.pythonhosted.org/packages/1c/3c/7fbc17cfb7e4fe0ef1642e0abc17fc6c94c9f7a16be41498e12e2ba60408/pillow-12.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1610dd6c61621ae1cf811bef44d77e149ce3f7b95afe66a4512f8c59f25d9ebe", size = 8127807, upload-time = "2026-04-01T14:45:33.908Z" }, - { url = "https://files.pythonhosted.org/packages/ff/c3/a8ae14d6defd2e448493ff512fae903b1e9bd40b72efb6ec55ce0048c8ce/pillow-12.2.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a34329707af4f73cf1782a36cd2289c0368880654a2c11f027bcee9052d35dd", size = 6433935, upload-time = "2026-04-01T14:45:36.623Z" }, - { url = "https://files.pythonhosted.org/packages/6e/32/2880fb3a074847ac159d8f902cb43278a61e85f681661e7419e6596803ed/pillow-12.2.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e9c4f5b3c546fa3458a29ab22646c1c6c787ea8f5ef51300e5a60300736905e", size = 7116720, upload-time = "2026-04-01T14:45:39.258Z" }, - { url = "https://files.pythonhosted.org/packages/46/87/495cc9c30e0129501643f24d320076f4cc54f718341df18cc70ec94c44e1/pillow-12.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb043ee2f06b41473269765c2feae53fc2e2fbf96e5e22ca94fb5ad677856f06", size = 6540498, upload-time = "2026-04-01T14:45:41.879Z" }, - { url = "https://files.pythonhosted.org/packages/18/53/773f5edca692009d883a72211b60fdaf8871cbef075eaa9d577f0a2f989e/pillow-12.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f278f034eb75b4e8a13a54a876cc4a5ab39173d2cdd93a638e1b467fc545ac43", size = 7239413, upload-time = "2026-04-01T14:45:44.705Z" }, - { url = "https://files.pythonhosted.org/packages/c9/e4/4b64a97d71b2a83158134abbb2f5bd3f8a2ea691361282f010998f339ec7/pillow-12.2.0-cp314-cp314t-win32.whl", hash = "sha256:6bb77b2dcb06b20f9f4b4a8454caa581cd4dd0643a08bacf821216a16d9c8354", size = 6482084, upload-time = "2026-04-01T14:45:47.568Z" }, - { url = "https://files.pythonhosted.org/packages/ba/13/306d275efd3a3453f72114b7431c877d10b1154014c1ebbedd067770d629/pillow-12.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6562ace0d3fb5f20ed7290f1f929cae41b25ae29528f2af1722966a0a02e2aa1", size = 7225152, upload-time = "2026-04-01T14:45:50.032Z" }, - { url = "https://files.pythonhosted.org/packages/ff/6e/cf826fae916b8658848d7b9f38d88da6396895c676e8086fc0988073aaf8/pillow-12.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aa88ccfe4e32d362816319ed727a004423aab09c5cea43c01a4b435643fa34eb", size = 2556579, upload-time = "2026-04-01T14:45:52.529Z" }, - { url = "https://files.pythonhosted.org/packages/4e/b7/2437044fb910f499610356d1352e3423753c98e34f915252aafecc64889f/pillow-12.2.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0538bd5e05efec03ae613fd89c4ce0368ecd2ba239cc25b9f9be7ed426b0af1f", size = 5273969, upload-time = "2026-04-01T14:45:55.538Z" }, - { url = "https://files.pythonhosted.org/packages/f6/f4/8316e31de11b780f4ac08ef3654a75555e624a98db1056ecb2122d008d5a/pillow-12.2.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:394167b21da716608eac917c60aa9b969421b5dcbbe02ae7f013e7b85811c69d", size = 4659674, upload-time = "2026-04-01T14:45:58.093Z" }, - { url = "https://files.pythonhosted.org/packages/d4/37/664fca7201f8bb2aa1d20e2c3d5564a62e6ae5111741966c8319ca802361/pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5d04bfa02cc2d23b497d1e90a0f927070043f6cbf303e738300532379a4b4e0f", size = 5288479, upload-time = "2026-04-01T14:46:01.141Z" }, - { url = "https://files.pythonhosted.org/packages/49/62/5b0ed78fce87346be7a5cfcfaaad91f6a1f98c26f86bdbafa2066c647ef6/pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0c838a5125cee37e68edec915651521191cef1e6aa336b855f495766e77a366e", size = 7032230, upload-time = "2026-04-01T14:46:03.874Z" }, - { url = "https://files.pythonhosted.org/packages/c3/28/ec0fc38107fc32536908034e990c47914c57cd7c5a3ece4d8d8f7ffd7e27/pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a6c9fa44005fa37a91ebfc95d081e8079757d2e904b27103f4f5fa6f0bf78c0", size = 5355404, upload-time = "2026-04-01T14:46:06.33Z" }, - { url = "https://files.pythonhosted.org/packages/5e/8b/51b0eddcfa2180d60e41f06bd6d0a62202b20b59c68f5a132e615b75aecf/pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:25373b66e0dd5905ed63fa3cae13c82fbddf3079f2c8bf15c6fb6a35586324c1", size = 6002215, upload-time = "2026-04-01T14:46:08.83Z" }, - { url = "https://files.pythonhosted.org/packages/bc/60/5382c03e1970de634027cee8e1b7d39776b778b81812aaf45b694dfe9e28/pillow-12.2.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bfa9c230d2fe991bed5318a5f119bd6780cda2915cca595393649fc118ab895e", size = 7080946, upload-time = "2026-04-01T14:46:11.734Z" }, +version = "12.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/3d/bb7fca845737cf9d7dbde16ed1843984665ff2e0a518f5db43e77ec540b9/pillow-12.3.0.tar.gz", hash = "sha256:3b8182a766685eaa002637e28b4ec8d6b18819a0c71f579bf0dbaa5830297cce", size = 47025035, upload-time = "2026-07-01T11:56:38.965Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/c2/669d88644cddb1485bd9534e63e8cf476c8e51cb3c3a1297677023505c0e/pillow-12.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6c0016e7b354317c4e9e525b937ac8596c38d2d232b419529b9cd7a1cd46e39a", size = 5392418, upload-time = "2026-07-01T11:53:27.808Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ba/3762f376a2948e3036488d773a146e0ae6ecc2ca03ac20e2615bd0b2ba02/pillow-12.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bcc33feacfaefce60c12fd500a277533bdc02b10a19f7f6d348763d8140bbba7", size = 4785287, upload-time = "2026-07-01T11:53:29.761Z" }, + { url = "https://files.pythonhosted.org/packages/07/50/b5d688cc9c52d4482f3d5bcab6ce20bc2a74a85d2343841c907444a3be2c/pillow-12.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5594fc43d548a7ed94949d139aa1341b270f1863f11cfd37f5a6c8b778a6b67f", size = 6253754, upload-time = "2026-07-01T11:53:32.298Z" }, + { url = "https://files.pythonhosted.org/packages/4e/89/36f4cd76cf4baf05c50ababb976249153f18c959171c7f6ba09a6f217260/pillow-12.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0606c8bf2cdefea14a43530f7657cbbb7ecf1c4222512492ef4a4434a9501ec", size = 6925605, upload-time = "2026-07-01T11:53:34.487Z" }, + { url = "https://files.pythonhosted.org/packages/eb/c0/4de58cf6633b9e3a6061ef4be6fb91fc3c90b812ece886f531e3c523d777/pillow-12.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:85f998ea1848bc6757289e739cfbdda3a04adfd58b02fc018ce54d754a5ce468", size = 6327788, upload-time = "2026-07-01T11:53:36.433Z" }, + { url = "https://files.pythonhosted.org/packages/87/3c/14d53682a19550dbbaf3b598f807d5457646c510805a44c7d7891cd1cd1a/pillow-12.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:25b9b82bb22e6e2b3cd07b39c68b7b862001226cb3dff7130d1cb914121b39ed", size = 7036288, upload-time = "2026-07-01T11:53:38.712Z" }, + { url = "https://files.pythonhosted.org/packages/38/1d/36279e3c77efe034e4cc2b0393ee74ffdb5a62391dacbf9b916154f5f0b8/pillow-12.3.0-cp310-cp310-win32.whl", hash = "sha256:37dc8f7bbb66efe481bb60defacef820c950c24713fb44962ed6aa2a50966de1", size = 6472396, upload-time = "2026-07-01T11:53:40.781Z" }, + { url = "https://files.pythonhosted.org/packages/48/7c/8fa0039574c476d7c6fa57dd7c32a130436877c6ec1e5ce1cc8ec44878c1/pillow-12.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:300557495eb45ebb8aec96c2da9c4be642fbf7cd937278b4013ba894ea8eb0eb", size = 7226887, upload-time = "2026-07-01T11:53:42.764Z" }, + { url = "https://files.pythonhosted.org/packages/fa/17/e324be141d173c1c919428066c3259f21c1b8982e564e01a4a81e96dbdcf/pillow-12.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:514435a37670e3e5e08f3945b68718b6ed329bb84367777e16f9f4dfe1e61a0f", size = 2568039, upload-time = "2026-07-01T11:53:45.372Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c8/0a78b0e02d7ac54bc03e5321c9220da52f0c2ea83b21f7c40e7f3169c502/pillow-12.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:00808c5e14ef63ac5161091d242999076604ff74b883423a11e5d7bbb38bf756", size = 5392415, upload-time = "2026-07-01T11:53:47.162Z" }, + { url = "https://files.pythonhosted.org/packages/b2/5b/a02d30018abd97ced9f5a6c63d28597694a00d066516b9c1c6de45859fc9/pillow-12.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37d6d0a00072fd2948eb22bce7e1475f34569d90c87c59f7a2ec59541b77f7a6", size = 4785266, upload-time = "2026-07-01T11:53:49.079Z" }, + { url = "https://files.pythonhosted.org/packages/c8/98/766667a4be768150a202836acd9fad19c06824ca86c4286d3cf6b274964e/pillow-12.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bcb46e2f9feff8d06323983bd83ed00c201fdcab3d74973e7072a889b3979fcd", size = 6263814, upload-time = "2026-07-01T11:53:51.32Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2d/ede717bc1144f63886c21fd349bb95860b0d1a21149ff16f2bb362b612b6/pillow-12.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23d27a3e0307ec2244cc51e7287b919aa68d097504ebe19df4e76a98a3eea5bd", size = 6934408, upload-time = "2026-07-01T11:53:53.487Z" }, + { url = "https://files.pythonhosted.org/packages/a3/48/9c58b685e69d49c31af6c8eb9012055fab7e665785165c84796e2c73ce72/pillow-12.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4f883547d4b7f0495ebe7056b0cc2aea76094e7a4abc8e933540f3271df27d9c", size = 6337160, upload-time = "2026-07-01T11:53:55.457Z" }, + { url = "https://files.pythonhosted.org/packages/ff/fa/dc2a5c0ba6df93f67c31d34b808b7ce440b40cdbf96f0b81cde1d1e6fa93/pillow-12.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:236ff70b9312fb68943c703aa842ca6a758abfa45ac187a5e7c1452e96ef72b5", size = 7045172, upload-time = "2026-07-01T11:53:57.736Z" }, + { url = "https://files.pythonhosted.org/packages/86/a5/444817a4d4c4c2417df00513086ca196f388d8f9ef40c2e4ccd1ad1af54b/pillow-12.3.0-cp311-cp311-win32.whl", hash = "sha256:10e41f0fbf1eec8cfd234b8fe17a4caac7c9d0db4c204d3c173a8f9f6ef3232b", size = 6472232, upload-time = "2026-07-01T11:53:59.767Z" }, + { url = "https://files.pythonhosted.org/packages/63/c6/4bad1b18d132a50b27e1365e1ab163616f7a5bb56d330f66f9d1d9d4f9d4/pillow-12.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:8e95e1385e4998ae9694eeaa4730ba5457ff61185b3a55e2e7bea0880aef452a", size = 7233653, upload-time = "2026-07-01T11:54:02.066Z" }, + { url = "https://files.pythonhosted.org/packages/fd/16/00f91ab7760dc842f5aad55217e80fc4a7067a0604535249bc8a2d6d9870/pillow-12.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:ebaea975e03d3141d9d3a507df75c9b3ec90fa9d2ffd07567b3a978d9d790b26", size = 2568195, upload-time = "2026-07-01T11:54:04.622Z" }, + { url = "https://files.pythonhosted.org/packages/37/bf/fb3ebff8ddcb76aac5a01389251bbbb9519922a9b520d8247c1ca864a25d/pillow-12.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ba09209fbe443b4acccebe845d8a138b89a8f4fbaeedd44953490b5315d5e965", size = 5345969, upload-time = "2026-07-01T11:54:06.397Z" }, + { url = "https://files.pythonhosted.org/packages/d8/66/9a386a92561f402389a4fc70c18838bf6d35eb5eb5c6850b4b2dc64f5048/pillow-12.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffd0c5368496f41b0944be820fcb7a838aa6e623d250b01acf2643939c3f99d7", size = 4780323, upload-time = "2026-07-01T11:54:09.351Z" }, + { url = "https://files.pythonhosted.org/packages/25/27/ac8f99618ffd3dde21db0f4d4b1d2ab00c0880595bfd17df103f7f39fd0c/pillow-12.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9c7f76c0673154f044e9d78c8655fb4213f6ca31a836df48b40fe5d187717b9", size = 6266838, upload-time = "2026-07-01T11:54:11.71Z" }, + { url = "https://files.pythonhosted.org/packages/84/21/a35af28dcc61f37ed850a2d64c65c701321dfbf25085e469d5559360cbbf/pillow-12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:78cb2c6865a35ab8ff8b75fd122f6033b92a62c82801110e48ddd6c936a45d91", size = 6940830, upload-time = "2026-07-01T11:54:13.732Z" }, + { url = "https://files.pythonhosted.org/packages/eb/51/8b08617af3ad95e33ce6d7dd2c99ed6c8298f7fb131636303956be022e25/pillow-12.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e491916b378fba47242221bb9ead245211b70d504f495d105d17b14a24b4907c", size = 6344383, upload-time = "2026-07-01T11:54:15.756Z" }, + { url = "https://files.pythonhosted.org/packages/1d/72/cf78ac9780bb93c28328f408973845a309d4d145041665f734572ced1b52/pillow-12.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0dd2064cbc55aaec028ef5fbb60fa47bb6c3e7918e07ff17935284b227a9d2df", size = 7052934, upload-time = "2026-07-01T11:54:17.721Z" }, + { url = "https://files.pythonhosted.org/packages/20/20/25e0f4dc178a6bc0696793720055519a0de89e7661dae886992decbd2f81/pillow-12.3.0-cp312-cp312-win32.whl", hash = "sha256:dbce0b29841537a2fa4a214c2bbf14de3587c9680caa9b4e217568472490b28f", size = 6472684, upload-time = "2026-07-01T11:54:19.839Z" }, + { url = "https://files.pythonhosted.org/packages/45/89/da2f7971a317f83d807fdd4065c0af40208e59e692cc43d315a71a0e96d1/pillow-12.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a2b55dd6b2a4c4b7d87ffa56bdb33fdc5fdb9a462173861a7bc097f17d91cb09", size = 7227137, upload-time = "2026-07-01T11:54:22.025Z" }, + { url = "https://files.pythonhosted.org/packages/de/47/4845a0a6c0dbf1db8456bd9fc791f13c5ced7ced20606d08a0aacfd25b49/pillow-12.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:331b624368d4f1d069149002f25f44bc61c8919ce8ddb3c45bdad8f6e2d89510", size = 2568267, upload-time = "2026-07-01T11:54:24.051Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ac/31fb64e1e7efb5a4b50cd3d92049ba89ac6e4d8d3bb6a74e15048ca3353e/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:21900ce7ba264168cd50defae43cd75d25c833ad4ad6e73ffc5596d12e25ac89", size = 4161684, upload-time = "2026-07-01T11:54:25.934Z" }, + { url = "https://files.pythonhosted.org/packages/87/b4/9805e23d2b4d77842b468513841fda254ee42f0289d25088340e4ff46e2d/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:4e8c2a84d977f50b9daed6eeaf3baef67d00d5d74d932288f02cb94518ee3ace", size = 4255487, upload-time = "2026-07-01T11:54:27.935Z" }, + { url = "https://files.pythonhosted.org/packages/df/39/ecf519435a200c693fe053a6ee4d835b41cf963a4dfc2551c4e637cb2a71/pillow-12.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:ae26d61dfa7a47befdc7572b521024e8745f3d809bd95ca9505a7bba9ef849ec", size = 3696433, upload-time = "2026-07-01T11:54:29.813Z" }, + { url = "https://files.pythonhosted.org/packages/42/92/2fc3ffad878ae8dd5469ec1bc8eb83b71f48e13efdf68f02709003982a32/pillow-12.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7a743ff716f746fc19a9557f60dab1600d4613255f8a7aeb3cdde4db7eb15a66", size = 5345889, upload-time = "2026-07-01T11:54:31.97Z" }, + { url = "https://files.pythonhosted.org/packages/10/76/8803c13605b763d33d156c4678fc77f8443389c0c51c8aef707bb02015f4/pillow-12.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d69141514cc30b774ceea5e3ed3a6635c8d8a96edf664689b890f4089111fb35", size = 4780109, upload-time = "2026-07-01T11:54:34.026Z" }, + { url = "https://files.pythonhosted.org/packages/1f/01/e18aff37cb0b4aac47ac90f016d347a49aca667ef97f190b06ac2aabc928/pillow-12.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f7401aebd7f581d7f83a439d87d474999317ee099218e5ad25d125290990ba65", size = 6263736, upload-time = "2026-07-01T11:54:36.131Z" }, + { url = "https://files.pythonhosted.org/packages/f7/62/de5bdd77d935331f4f802edc11e4d82950f642caad6cb2f949837b8560e2/pillow-12.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0847a763afefb695bc912d7c131e7e0632d4edc1d8698f58ddabec8e46b8b6d3", size = 6937129, upload-time = "2026-07-01T11:54:38.216Z" }, + { url = "https://files.pythonhosted.org/packages/70/4d/105627a13300c5e0df1d174230b32fd1273062c96f7745fd552b945d1e1d/pillow-12.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:571b9fcb07b97ef3a492028fb3d2dc0993ca23a06138b0315286566d29ef718a", size = 6339562, upload-time = "2026-07-01T11:54:40.354Z" }, + { url = "https://files.pythonhosted.org/packages/6b/1d/f13de01a553988ab895ba1c722e06cf3144d4f57656fd5b81b6d881f1179/pillow-12.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:756c768d0c9c2955feb7a56c37ea24aea2e369f8d36a88da270b6a9f19e62b5e", size = 7049439, upload-time = "2026-07-01T11:54:42.489Z" }, + { url = "https://files.pythonhosted.org/packages/c9/f9/066794cca041b969964f779ee5fa66a9498bbf34248ac39c5d7954e4198f/pillow-12.3.0-cp313-cp313-win32.whl", hash = "sha256:a876864214e136f0eb367788dbd7df045f4806801518e2cfe9e13229cfe06d8f", size = 6473287, upload-time = "2026-07-01T11:54:44.9Z" }, + { url = "https://files.pythonhosted.org/packages/a6/9b/7a58e61d62be561da3a356fe2384d4059a6345fc130e23ef1c36a5b81d24/pillow-12.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:1cca606cd25738df4ed873d5ad46bbdb3d83b5cbca291f6b4ff13a4df6b0bbe8", size = 7239691, upload-time = "2026-07-01T11:54:47.141Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b0/c4ed4f0ef8f8fa5ee8351537db6650bb8189f7e118842978dd6589065692/pillow-12.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:b629de27fda84b42cde7edef0d85f13b958b47f6e9bbcbba9b673c562a89bd8b", size = 2568185, upload-time = "2026-07-01T11:54:49.137Z" }, + { url = "https://files.pythonhosted.org/packages/dc/01/001f65b68192f0228cc1dbbc8d2530ab5d58b61037ba0587f946fea607cd/pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:9cf95fe4d0f84c82d282745d9bb08ad9f926efa00be4697e767b814ce40d4330", size = 4161736, upload-time = "2026-07-01T11:54:51.156Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d2/0219746d0fd16fc8a84498e79452375be3797d3ce4044596ce565164b84f/pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:8728f216dcdb6e6d555cf971cb34076139ad74b31fc2c14da4fafc741c5f6217", size = 4255435, upload-time = "2026-07-01T11:54:53.414Z" }, + { url = "https://files.pythonhosted.org/packages/c8/02/8d0bc62ef0302318c46ff2a512822d2610e81c7aa46c9b3abe6cbaca5ad0/pillow-12.3.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:a45650e8ce7fafffd731db8550230db6b0d306d181a90b67d3e6bca2f1990930", size = 3696262, upload-time = "2026-07-01T11:54:55.739Z" }, + { url = "https://files.pythonhosted.org/packages/85/e2/73c77d218410b14f5f2d565e8a998d5317b7b9c75368d29985139f7a46f0/pillow-12.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ba54cfebe86920a559a7c4d6b9050791c20513650a1952ebe3368c7dc70306f8", size = 5350344, upload-time = "2026-07-01T11:54:57.657Z" }, + { url = "https://files.pythonhosted.org/packages/c7/da/32c752228ae345f489e3a42499d817b6c3996da7e8a3bc7a04fc806b243b/pillow-12.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e158cb00350dc278f3b91551101aa7d12415a66ebf2c91d8d5ac14e56ddd3ad0", size = 4780131, upload-time = "2026-07-01T11:54:59.713Z" }, + { url = "https://files.pythonhosted.org/packages/b1/9d/8b2c807dbef61a5197c047afe99823787eb66f63daf9fb2432f91d6f0462/pillow-12.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9aeb04d6aef139de265b29683e119b638208f88cf73cdd1658aa07221165321", size = 6263757, upload-time = "2026-07-01T11:55:01.778Z" }, + { url = "https://files.pythonhosted.org/packages/5c/44/c85361f65dbe00eea8576ee467c768d25129989efb76e94f205e9ca9bb46/pillow-12.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:251bf95b67017e27b13d82f5b326234ca62d70f9cf4c2b9032de2358a3b12c7b", size = 6936962, upload-time = "2026-07-01T11:55:03.93Z" }, + { url = "https://files.pythonhosted.org/packages/18/7e/e483414b35800b86b6f08dbbc7803fb5cd52c4d6f897f47d53ea2c7e6f65/pillow-12.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fe3cca2e4e8a592be0f269a1ca4835c25199d9f3ce815c8491048f785b0a0198", size = 6339171, upload-time = "2026-07-01T11:55:05.989Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f4/68c491844841ede6bed70189546b3ee9731cf9f2cbad396faff5e1ccba45/pillow-12.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:23aceaa007d6172b02c277f0cd359c79492bbb14f7072b4ede9fbcaf20648130", size = 7048116, upload-time = "2026-07-01T11:55:08.131Z" }, + { url = "https://files.pythonhosted.org/packages/a3/34/77f3f793fed8efc7d243f21b33c5a3f0d1c97ee70346d3db855587e155ff/pillow-12.3.0-cp314-cp314-win32.whl", hash = "sha256:af8d94b0db561cf68b88a267c5c44b49e134f525d0dc2cb7ed413a66bc23559a", size = 6467209, upload-time = "2026-07-01T11:55:10.408Z" }, + { url = "https://files.pythonhosted.org/packages/f1/e0/492879f69d94f91f60fc8cd05ba03650e9520afebb2fb7aa12777d7c7f38/pillow-12.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:fdafc9cce40277e0f7a0feabce0ee50dd2fa1800f3b38015e51296b5e814048d", size = 7237707, upload-time = "2026-07-01T11:55:12.745Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ac/6b11f2875f1c2ac040d84e1bbf9cf22a88038f901ca1037898b280b38365/pillow-12.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:e91206ee562682b51b98ef4b26a6ef48fd84e15fd4c4bc5ec768eb641d206838", size = 2565995, upload-time = "2026-07-01T11:55:14.736Z" }, + { url = "https://files.pythonhosted.org/packages/52/69/c2208e56af9bfc1913afb24020297a691eb1d4ef688474c8a04913f65e04/pillow-12.3.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:164b31cd1a0490ab6efae01aa5df49da7061be0af1b30e035b6e9a1bfe34ee6e", size = 5352503, upload-time = "2026-07-01T11:55:17.076Z" }, + { url = "https://files.pythonhosted.org/packages/07/70/e5686d753e898a45d778ff1718dba8516ead6ab6b95d85fc8c4b70650cf2/pillow-12.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5afb51d599ea772b8365ae807ae557f18bccfe46ab261fd1c2a9ed700fc6eb17", size = 4782956, upload-time = "2026-07-01T11:55:19.448Z" }, + { url = "https://files.pythonhosted.org/packages/d5/37/25c6692f06927ee973ff18c8d9ee98ad0b4d84ee67a09610c2dd1447958e/pillow-12.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3edce1d53195db527e0191f84b71d02022de0540bf43a16ed734ed7537b07385", size = 6322855, upload-time = "2026-07-01T11:55:21.613Z" }, + { url = "https://files.pythonhosted.org/packages/cc/91/420637fcb8f1bc11029e403b4538e6694744428d8246118e45719f944556/pillow-12.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf16ba1b4d0b6b7c8e534936632270cf70eb00dbe09005bc345b2677b726855c", size = 6989642, upload-time = "2026-07-01T11:55:24.006Z" }, + { url = "https://files.pythonhosted.org/packages/10/08/b94d7811281ccf0d143a1cf768d1c49e1e54af63e7b708ab2ee3eb87face/pillow-12.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:24870b09b224f7ae3c39ed07d10e819d06f8720bc551847b1d623832b5b0e28d", size = 6391281, upload-time = "2026-07-01T11:55:26.252Z" }, + { url = "https://files.pythonhosted.org/packages/d2/87/24233f785f55474dc02ce3e739c5528a77e3a862e9333d1dd7a25cc31f70/pillow-12.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:30f2aa603c41533cc25c05acd0da21636e84a315768feb631c937177db558931", size = 7096716, upload-time = "2026-07-01T11:55:28.318Z" }, + { url = "https://files.pythonhosted.org/packages/23/26/fcb2f6e37175b04f53570b59937867e2b80ee1685e744023153028fc14f9/pillow-12.3.0-cp314-cp314t-win32.whl", hash = "sha256:4b0a7fe987b14c31ebda6083f74f22b561fd3739bc0ac51e019622e3d72668c7", size = 6474125, upload-time = "2026-07-01T11:55:30.956Z" }, + { url = "https://files.pythonhosted.org/packages/90/de/3634abee5f1c9e13c56787b7d5517b0ba8d6de51700b95578cf338349c9f/pillow-12.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:962864dc93511324d51ddbb5b9f8731bf71675b93ca612a07441896f4688fb8c", size = 7242939, upload-time = "2026-07-01T11:55:34.044Z" }, + { url = "https://files.pythonhosted.org/packages/ce/2a/fd13f8eb24de5714a6eb444a3d67e2842c6c576e159a43793adf23051351/pillow-12.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0740a512dc522224c77d9aa5a8d70d8b7d73fb91f2c21125d8d025d3b8990e45", size = 2567506, upload-time = "2026-07-01T11:55:35.988Z" }, + { url = "https://files.pythonhosted.org/packages/5d/dc/8fdce34ec725a33c81c6ba122b904d6b9024e50ea9ac7bede62fab54506c/pillow-12.3.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:0feb2e9d6ad6c9e3c06effe9d00f3f1e618a6643273576b016f591e9315a7139", size = 4162063, upload-time = "2026-07-01T11:55:37.941Z" }, + { url = "https://files.pythonhosted.org/packages/76/66/2044b9a63d3b84ff048228dfcb7cd9bf0df983e8470971bf7d4c57b693de/pillow-12.3.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:9e881fca225083806662a5c43d627d215f258ff43c890f831966c7d7ba9c7402", size = 4255549, upload-time = "2026-07-01T11:55:40.022Z" }, + { url = "https://files.pythonhosted.org/packages/52/7e/1f67e6f4ece6b582ee4b539decbcc9f848dc245a93ed8cd7338bafef72f1/pillow-12.3.0-cp315-cp315-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:4998562bf62a445225f22e07c896bb04b35b1b1f2eb6d760584c9c51d7a5f78c", size = 3696331, upload-time = "2026-07-01T11:55:41.98Z" }, + { url = "https://files.pythonhosted.org/packages/12/40/d306fc2c8e4d45d7f175c77edca7063be7b86fe7fe6e68f4353bf71d808c/pillow-12.3.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:dc624f6bc473dacdf7ef7eb8678d0d08edf15cd94fad6ae5c7d6cc67a4e4902f", size = 5350370, upload-time = "2026-07-01T11:55:44.028Z" }, + { url = "https://files.pythonhosted.org/packages/dd/44/668fb1437e8ce420f62d6106eb66e44a5971602a4d794615bdf79315d82d/pillow-12.3.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:71d6097b330eea8fd15097780c8e89cb1a8ce7838669f48c5bacd6f663dd4701", size = 4780147, upload-time = "2026-07-01T11:55:46.073Z" }, + { url = "https://files.pythonhosted.org/packages/0c/08/93fa2e70e30a2d81547e481b6ee2bb9522117221fb1e0ce4b5df70967677/pillow-12.3.0-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:28ce87c5ab450a9dd970b52e5aca5fe63ed432d18a2eaddd1979a00a1ba24ace", size = 6273659, upload-time = "2026-07-01T11:55:48.264Z" }, + { url = "https://files.pythonhosted.org/packages/f8/6d/043e96ff814fc31a33077e4cba86082167db520c93632afdf2042febbb0c/pillow-12.3.0-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b02afb9b97f65fbca5f31db6a2a3ba21aa93030225f150fa3f249717e938fb4", size = 6947439, upload-time = "2026-07-01T11:55:50.503Z" }, + { url = "https://files.pythonhosted.org/packages/af/92/ba71d2ee2ac0edf3fa33bd9d5ee9ee080da70b1766f3ca3934f9938ddac9/pillow-12.3.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:1182d52bc2d5e5d7d0949503aa7e36d12f42205dc287e4883f407b1988820d39", size = 6353577, upload-time = "2026-07-01T11:55:52.697Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ce/e63064e2122923ff687c8ad792d0d736a7b3920a56a46982e81a7fdd25d6/pillow-12.3.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:e795b7eb908249c4e43c7c99fac7c2c75dab0c43566e37db472a355f63693d71", size = 7060394, upload-time = "2026-07-01T11:55:55.149Z" }, + { url = "https://files.pythonhosted.org/packages/54/76/a09cc3ccc8d773a7283d34c38bec1708f9e3cc932093cbc4c5e71ac4060b/pillow-12.3.0-cp315-cp315-win32.whl", hash = "sha256:57b3d78c95ba9059768b10e28b813002261d3f3dfc55cc48b0c988f625175827", size = 6467375, upload-time = "2026-07-01T11:55:57.769Z" }, + { url = "https://files.pythonhosted.org/packages/3e/03/1846c49ba3b1d5550392a4bbd06d6fb4578e1cd91a803198b5c90f5f7d53/pillow-12.3.0-cp315-cp315-win_amd64.whl", hash = "sha256:fa4ecea169a355be7a3ade2c783e2ed12f0e40d2c5621cda8b3297faf7fbb9f5", size = 7237048, upload-time = "2026-07-01T11:55:59.975Z" }, + { url = "https://files.pythonhosted.org/packages/fb/bb/89f35dcc79610423f9f195504d7def7f0d1416a711541b42867e25fe3412/pillow-12.3.0-cp315-cp315-win_arm64.whl", hash = "sha256:877c3f311ff35410f690861c4409e7ccbf0cd2f878e50628a28e5a0bb689e658", size = 2566006, upload-time = "2026-07-01T11:56:02.143Z" }, + { url = "https://files.pythonhosted.org/packages/30/88/707027ba09942dfa2c28759b5c222d769290a41c6d20ea60ec250801941f/pillow-12.3.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:e9871b1ffbfa9656b60aeee92ed5136a5742696006fa322b29ea3d8da0ecc9cf", size = 5352509, upload-time = "2026-07-01T11:56:04.2Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6d/00352fa25332c2569cd387851f568cc5a4b75a9adbfb37ac4fbce4c02eec/pillow-12.3.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:53aa02d20d10c3d814d536aa4e5ac9b84ca0ff5a88377963b085ad6822f93e64", size = 4783167, upload-time = "2026-07-01T11:56:06.631Z" }, + { url = "https://files.pythonhosted.org/packages/13/4f/9e049dfa21af7c22427275720e2490267ba8138120add5c4c574deb69782/pillow-12.3.0-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:446c34dcc4324b084a53b705127dc15717b22c5e140ae0a3c38349d4efec071e", size = 6329237, upload-time = "2026-07-01T11:56:08.868Z" }, + { url = "https://files.pythonhosted.org/packages/36/16/cf6eeaae8d0fce8dd390a33437cf68c5d5bd73834a2bc6e2f14efda0ab45/pillow-12.3.0-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf1845d02ad822a369a49f2bb9345b1614744267682e7a03527dc3bf6eea1777", size = 6997047, upload-time = "2026-07-01T11:56:11.379Z" }, + { url = "https://files.pythonhosted.org/packages/1e/69/dbf769bdd55f48bf5733cac28edc6364ffaa072ec9ba336266e4fe66be55/pillow-12.3.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:186941b6aef820ad110fb01fb06eb925374dc3a21b17e37ec9a53b250c6fe2d1", size = 6400440, upload-time = "2026-07-01T11:56:13.908Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e1/ffc9cfc2eea0d178da8018e18e959301ad9d6bc9f3edb7181e748a474b97/pillow-12.3.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:f13c32a3abd6079a66d9526e18dad9b6d280384d49d7c54040cd57b6424041d9", size = 7105895, upload-time = "2026-07-01T11:56:16.575Z" }, + { url = "https://files.pythonhosted.org/packages/18/f0/a5595c1e8c3ae44b9828cb2f0fa8155e5095ef04d6327b8f61cf44a3df85/pillow-12.3.0-cp315-cp315t-win32.whl", hash = "sha256:1657923d2d45afb66526e5b933e5b3052e6bdea196c90d3abb2424e18c77dae8", size = 6474384, upload-time = "2026-07-01T11:56:18.855Z" }, + { url = "https://files.pythonhosted.org/packages/e4/04/62bcd9f844984c5938d3b05264a61d797a29d3e0812341a8204af70bbdee/pillow-12.3.0-cp315-cp315t-win_amd64.whl", hash = "sha256:8cd2f7bdda092d99c9fc2fb7391354f306d01443d22785d0cbfafa2e2c8bb418", size = 7243537, upload-time = "2026-07-01T11:56:21.214Z" }, + { url = "https://files.pythonhosted.org/packages/3d/68/1f3066acedf37673694a7141381d8f811ae97f30d34413d236abe7d489f1/pillow-12.3.0-cp315-cp315t-win_arm64.whl", hash = "sha256:06ff022112bc9cbf83b60f8e028d94ad87b60621706487e65f673de61610ab59", size = 2567491, upload-time = "2026-07-01T11:56:23.506Z" }, + { url = "https://files.pythonhosted.org/packages/75/18/2e8b40223153ccbc60df07f9e8928dc0c76202aa4e55ae9f53962b6510d6/pillow-12.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b3c777e849237620b022f7f297dd67705f9f5cf1685f09f02e46f93e92725468", size = 5302510, upload-time = "2026-07-01T11:56:25.736Z" }, + { url = "https://files.pythonhosted.org/packages/46/3e/51fabf59d5ab801ceab709453d3ab6b180083496579549de4c45ced6528a/pillow-12.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:b343699e8308bdc51978310e1c959c584e7869cc8c40780058c87da7781a1e94", size = 4736058, upload-time = "2026-07-01T11:56:28.041Z" }, + { url = "https://files.pythonhosted.org/packages/bf/20/22fe9384b7949e25fb1293bcfc84fb82590ff4ea6b37c95b24d26d793d86/pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbd139c8447d25dd750ab79ee274cc5e1fe80fc56340ab10b18a195e1b6eca3e", size = 5237776, upload-time = "2026-07-01T11:56:30.263Z" }, + { url = "https://files.pythonhosted.org/packages/08/14/f6ba68107680ffa74b39985f3f30884e41318fbc4250caa423c79b4788bb/pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7e480451b9fa137494bccd3a7d69adbe8ac65a87d97be61e11f1b1050a5bac3", size = 5860358, upload-time = "2026-07-01T11:56:32.68Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0169bc772ec491108b62f644f8ecf1fe5d8ae5ebafde2ee2142210166903/pillow-12.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:04f01d28a6aaff387bf842a13be313df23ba0597a44f1a976c9feb3c6ff4711a", size = 7231786, upload-time = "2026-07-01T11:56:35.046Z" }, ] [[package]] @@ -3566,7 +3562,7 @@ dependencies = [ { name = "nltk" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "six" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e2/c5/9136736c37022a6ad27fea38f3111eb8f02fe75d067f9a985cc358653102/rouge_score-0.1.2.tar.gz", hash = "sha256:c7d4da2683e68c9abf0135ef915d63a46643666f848e558a1b9f7ead17ff0f04", size = 17400, upload-time = "2022-07-22T22:46:22.909Z" } @@ -3605,7 +3601,7 @@ dependencies = [ { name = "lxml" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "portalocker" }, { name = "regex" }, { name = "tabulate" }, @@ -3705,7 +3701,7 @@ dependencies = [ { name = "joblib", marker = "python_full_version >= '3.11'" }, { name = "narwhals", marker = "python_full_version >= '3.11'" }, { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "threadpoolctl", marker = "python_full_version >= '3.11'" }, @@ -3892,7 +3888,7 @@ resolution-markers = [ "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a7/25/c2700dfaf6442b4effaa91af24ebce5dc9d31bb4a69706313aae70d72cd0/scipy-1.18.0.tar.gz", hash = "sha256:67b2ad2ad54c72ca6d04975a9b2df8c3638c34ddd5b28738e94fc2b57929d378", size = 30774447, upload-time = "2026-06-19T15:01:43.456Z" } wheels = [ @@ -4004,11 +4000,11 @@ wheels = [ [[package]] name = "setuptools" -version = "82.0.1" +version = "83.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4f/db/cfac1baf10650ab4d1c111714410d2fbb77ac5a616db26775db562c8fab2/setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9", size = 1152316, upload-time = "2026-03-09T12:47:17.221Z" } +sdist = { url = "https://files.pythonhosted.org/packages/34/26/f5d29e25ffdb535afef2d35cdb55b325298f96debd670da4c325e08d70f4/setuptools-83.0.0.tar.gz", hash = "sha256:025bccbbf0fa05b6192bc64ae1e7b16e001fd6d6d4d5de03c97b1c1ade523bef", size = 1154254, upload-time = "2026-07-04T15:31:22.699Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb", size = 1006223, upload-time = "2026-03-09T12:47:15.026Z" }, + { url = "https://files.pythonhosted.org/packages/5d/40/e1e72872c6354b306daef1703549e8e83b4d43cfea356311bf722a043752/setuptools-83.0.0-py3-none-any.whl", hash = "sha256:29b23c360f22f414dc7336bb39178cc7bcbf6021ed2733cde173f09dba19abb3", size = 1008090, upload-time = "2026-07-04T15:31:20.885Z" }, ] [[package]] @@ -4101,7 +4097,7 @@ dependencies = [ { name = "matplotlib", version = "3.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "pandas", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "pillow" }, @@ -4144,7 +4140,7 @@ dependencies = [ { name = "matplotlib", version = "3.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "pandas", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "pillow" }, @@ -4314,7 +4310,7 @@ dependencies = [ { name = "nvidia-nvtx", marker = "sys_platform == 'linux'" }, { name = "setuptools", marker = "python_full_version >= '3.12'" }, { name = "sympy" }, - { name = "triton", marker = "sys_platform == 'linux'" }, + { name = "triton" }, { name = "typing-extensions" }, ] wheels = [ @@ -4361,7 +4357,7 @@ dependencies = [ { name = "huggingface-hub" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, - { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "packaging" }, { name = "pyyaml" }, { name = "regex" }, @@ -4377,23 +4373,21 @@ wheels = [ [[package]] name = "triton" -version = "3.5.1" +version = "3.7.1" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/2e/f95e673222afa2c7f0c687d8913e98fcf2589ef0b1405de76894e37fe18f/triton-3.5.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f63e34dcb32d7bd3a1d0195f60f30d2aee8b08a69a0424189b71017e23dfc3d2", size = 159821655, upload-time = "2025-11-11T17:51:44.09Z" }, - { url = "https://files.pythonhosted.org/packages/fd/6e/676ab5019b4dde8b9b7bab71245102fc02778ef3df48218b298686b9ffd6/triton-3.5.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5fc53d849f879911ea13f4a877243afc513187bc7ee92d1f2c0f1ba3169e3c94", size = 170320692, upload-time = "2025-11-11T17:40:46.074Z" }, - { url = "https://files.pythonhosted.org/packages/dc/dc/6ce44d055f2fc2403c4ec6b3cfd3a9b25f57b7d95efadccdea91497f8e81/triton-3.5.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:da47169e30a779bade679ce78df4810fca6d78a955843d2ddb11f226adc517dc", size = 159928005, upload-time = "2025-11-11T17:51:50.008Z" }, - { url = "https://files.pythonhosted.org/packages/b0/72/ec90c3519eaf168f22cb1757ad412f3a2add4782ad3a92861c9ad135d886/triton-3.5.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:61413522a48add32302353fdbaaf92daaaab06f6b5e3229940d21b5207f47579", size = 170425802, upload-time = "2025-11-11T17:40:53.209Z" }, - { url = "https://files.pythonhosted.org/packages/db/53/2bcc46879910991f09c063eea07627baef2bc62fe725302ba8f46a2c1ae5/triton-3.5.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:275a045b6ed670dd1bd005c3e6c2d61846c74c66f4512d6f33cc027b11de8fd4", size = 159940689, upload-time = "2025-11-11T17:51:55.938Z" }, - { url = "https://files.pythonhosted.org/packages/f2/50/9a8358d3ef58162c0a415d173cfb45b67de60176e1024f71fbc4d24c0b6d/triton-3.5.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d2c6b915a03888ab931a9fd3e55ba36785e1fe70cbea0b40c6ef93b20fc85232", size = 170470207, upload-time = "2025-11-11T17:41:00.253Z" }, - { url = "https://files.pythonhosted.org/packages/f1/ba/805684a992ee32d486b7948d36aed2f5e3c643fc63883bf8bdca1c3f3980/triton-3.5.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56765ffe12c554cd560698398b8a268db1f616c120007bfd8829d27139abd24a", size = 159955460, upload-time = "2025-11-11T17:52:01.861Z" }, - { url = "https://files.pythonhosted.org/packages/27/46/8c3bbb5b0a19313f50edcaa363b599e5a1a5ac9683ead82b9b80fe497c8d/triton-3.5.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3f4346b6ebbd4fad18773f5ba839114f4826037c9f2f34e0148894cd5dd3dba", size = 170470410, upload-time = "2025-11-11T17:41:06.319Z" }, - { url = "https://files.pythonhosted.org/packages/84/1e/7df59baef41931e21159371c481c31a517ff4c2517343b62503d0cd2be99/triton-3.5.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02c770856f5e407d24d28ddc66e33cf026e6f4d360dcb8b2fabe6ea1fc758621", size = 160072799, upload-time = "2025-11-11T17:52:07.293Z" }, - { url = "https://files.pythonhosted.org/packages/37/92/e97fcc6b2c27cdb87ce5ee063d77f8f26f19f06916aa680464c8104ef0f6/triton-3.5.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0b4d2c70127fca6a23e247f9348b8adde979d2e7a20391bfbabaac6aebc7e6a8", size = 170579924, upload-time = "2025-11-11T17:41:12.455Z" }, - { url = "https://files.pythonhosted.org/packages/14/f9/0430e879c1e63a1016cb843261528fd3187c872c3a9539132efc39514753/triton-3.5.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f617aa7925f9ea9968ec2e1adaf93e87864ff51549c8f04ce658f29bbdb71e2d", size = 159956163, upload-time = "2025-11-11T17:52:12.999Z" }, - { url = "https://files.pythonhosted.org/packages/a4/e6/c595c35e5c50c4bc56a7bac96493dad321e9e29b953b526bbbe20f9911d0/triton-3.5.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0637b1efb1db599a8e9dc960d53ab6e4637db7d4ab6630a0974705d77b14b60", size = 170480488, upload-time = "2025-11-11T17:41:18.222Z" }, - { url = "https://files.pythonhosted.org/packages/41/1e/63d367c576c75919e268e4fbc33c1cb33b6dc12bb85e8bfe531c2a8bd5d3/triton-3.5.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8932391d7f93698dfe5bc9bead77c47a24f97329e9f20c10786bb230a9083f56", size = 160073620, upload-time = "2025-11-11T17:52:18.403Z" }, - { url = "https://files.pythonhosted.org/packages/16/b5/b0d3d8b901b6a04ca38df5e24c27e53afb15b93624d7fd7d658c7cd9352a/triton-3.5.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bac7f7d959ad0f48c0e97d6643a1cc0fd5786fe61cb1f83b537c6b2d54776478", size = 170582192, upload-time = "2025-11-11T17:41:23.963Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ea/629cc37436ca5df93ce98956d09cd2ca1498bfee8ef4972d2fe48b9f958c/triton-3.7.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3daf64305d6cea88d3334c65ebc9bcd0c64c9564a977084366aa768d57cbcf64", size = 184551013, upload-time = "2026-06-17T20:03:37.551Z" }, + { url = "https://files.pythonhosted.org/packages/15/76/c79c34311625227a288df3e483fc5cdf3d596624cbd4b4758c4cbdc14af3/triton-3.7.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee89fbf782ec2ad50391dd1cf26cbea4f4467154c37f4773026da8fc31c0f58e", size = 197596267, upload-time = "2026-06-17T19:53:06.898Z" }, + { url = "https://files.pythonhosted.org/packages/7b/f9/19d842d06a08559534fa1eaab6ca551b1bcf40f06620bddec1babaa2772d/triton-3.7.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4a0e1cd4c4a76370ed74a8432a53cea28716827d19e40ffc732233e35ceb3f6", size = 184664887, upload-time = "2026-06-17T20:03:42.913Z" }, + { url = "https://files.pythonhosted.org/packages/cd/5e/fce69606f7f240297f163e25539906732b199530d486ce67ae319877e821/triton-3.7.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6744957e9fd610a29680ec2346057d0c86948ed3812468670719f391e94b44a5", size = 197701306, upload-time = "2026-06-17T19:53:13.673Z" }, + { url = "https://files.pythonhosted.org/packages/94/fa/f856e24deb462d5f18bd4b5a746957862ab9b6ee5834bda60605ec348366/triton-3.7.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9497f2e696ee368862a181a90b2dcc03ca978cc4f602abd67c7d81022a6988e1", size = 184692359, upload-time = "2026-06-17T20:03:48.288Z" }, + { url = "https://files.pythonhosted.org/packages/c4/6f/fb96d15db6f36d6eae4cafb998c2e0353bf59d7c4ea1662d7497f269134a/triton-3.7.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e40869937a68206ec70d7f25bb7ec6433cb083f9135e1f36dbd318dc449a728", size = 197719725, upload-time = "2026-06-17T19:53:20.419Z" }, + { url = "https://files.pythonhosted.org/packages/00/42/c5089d4d9327fcd1e862c599cc2927f39418f84dd11a84cb2ccff9d4787a/triton-3.7.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cdbfc09d9ec58bc5e68321525653220de7515c199e7a8097a97c85e62b52cd0a", size = 184694629, upload-time = "2026-06-17T20:03:53.444Z" }, + { url = "https://files.pythonhosted.org/packages/07/42/2c3ac59253ae8892b6f307875263dd23dc875cdf732d3aea40d6d41fb7cb/triton-3.7.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:58c0e131da05134a2a4788ccbcc0c1105cf0f54c8e98f19e34cd465396dc15eb", size = 197729241, upload-time = "2026-06-17T19:53:27.801Z" }, + { url = "https://files.pythonhosted.org/packages/40/71/e01aa7ad573883ed9456f130226babdec70b005e098c4d6226a6238e761b/triton-3.7.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe4ea396a06171f1f1f58cbd39c70b09294398f7dd7c620939bab54ad6f934fa", size = 184705764, upload-time = "2026-06-17T20:03:59.064Z" }, + { url = "https://files.pythonhosted.org/packages/a4/09/5683146fda6a2b569deb78ccfd8fbfea8bfe55f726b081c0a6bb18dd6f28/triton-3.7.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2020153b08280415ec0da6607834e79166442147e78e144df06b508c75b186d2", size = 197729537, upload-time = "2026-06-17T19:53:35.516Z" }, + { url = "https://files.pythonhosted.org/packages/e9/f8/448220c3092019f9fdfab39ec47985968181d67da34b44f6a7f6280a5cbb/triton-3.7.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c58e4c61f0c73b5dba3b5d19b4a7093c32f90dc18b2a7f121a7c16ccd31107b7", size = 184814760, upload-time = "2026-06-17T20:04:04.984Z" }, + { url = "https://files.pythonhosted.org/packages/f0/ac/229b7d4589d2e5937310e72c6d46e89599d16a4a12b479ffa1499fee8eb8/triton-3.7.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10ba85fa2cca4a2fbdeb36bf1cb082f2c252bda55bf9fccd74f65ec5bc647e68", size = 197824404, upload-time = "2026-06-17T19:53:42.772Z" }, ] [[package]] @@ -4417,26 +4411,26 @@ datetime = [ [[package]] name = "typer" -version = "0.25.1" +version = "0.26.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-doc" }, - { name = "click" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "rich" }, { name = "shellingham" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e4/51/9aed62104cea109b820bbd6c14245af756112017d309da813ef107d42e7e/typer-0.25.1.tar.gz", hash = "sha256:9616eb8853a09ffeabab1698952f33c6f29ffdbceb4eaeecf571880e8d7664cc", size = 122276, upload-time = "2026-04-30T19:32:16.964Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7c/f7/68adc395201b20b872d68e975386832e8005ffeacedd43a1d837a32815be/typer-0.26.8.tar.gz", hash = "sha256:c244a6bd558886fe3f8780efb6bdd28bb9aff005a94eedebaa5cb32926fe2f7e", size = 202097, upload-time = "2026-06-26T09:22:45.705Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/f9/2b3ff4e56e5fa7debfaf9eb135d0da96f3e9a1d5b27222223c7296336e5f/typer-0.25.1-py3-none-any.whl", hash = "sha256:75caa44ed46a03fb2dab8808753ffacdbfea88495e74c85a28c5eefcf5f39c89", size = 58409, upload-time = "2026-04-30T19:32:18.271Z" }, + { url = "https://files.pythonhosted.org/packages/80/87/b9fd69c92c6102a066e1b86a35243f53e70bd4c709f2a26d9f4fee4f4dc0/typer-0.26.8-py3-none-any.whl", hash = "sha256:3512ca79ac5c11113414b36e80281b872884477722440691c89d1112e321a49c", size = 122564, upload-time = "2026-06-26T09:22:44.72Z" }, ] [[package]] name = "typing-extensions" -version = "4.15.0" +version = "4.16.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, ] [[package]] diff --git a/skills/tilegym-monkey-patch-kernels-to-transformers/references/kernel-inventory-schema.md b/skills/tilegym-monkey-patch-kernels-to-transformers/references/kernel-inventory-schema.md index 257494d..e35d68b 100644 --- a/skills/tilegym-monkey-patch-kernels-to-transformers/references/kernel-inventory-schema.md +++ b/skills/tilegym-monkey-patch-kernels-to-transformers/references/kernel-inventory-schema.md @@ -52,10 +52,10 @@ Use FlashInfer Solution metadata with source paths: - `name`, `definition`, `author`, `spec`, and `sources` are required. - `spec.language` is `cuda-tile` for cuTile kernels. - `spec.entry_point` uses `{file_path}::{function_name}` and points at `kernels/.py`. -- `spec.target_hardware` lists supported GPUs, for example `NVIDIA_B200`. +- `spec.target_hardware` lists supported CUDA compute capabilities, for example `SM100`. - `sources.path` references one or more repo-relative files containing the implementation. -For Ocean in-repo inventory, `sources.content` is not required. If an external FlashInfer-Bench submission needs embedded file content, materialize it from `sources.path` at export time. +For TileGym in-repo inventory, `sources.content` is not required. If an external FlashInfer-Bench submission needs embedded file content, materialize it from `sources.path` at export time. ## Agent workflow rules diff --git a/src/tilegym/transformers/kernel_inventory.py b/src/tilegym/kernel_inventory/__init__.py similarity index 58% rename from src/tilegym/transformers/kernel_inventory.py rename to src/tilegym/kernel_inventory/__init__.py index bf8d65a..1b577ef 100644 --- a/src/tilegym/transformers/kernel_inventory.py +++ b/src/tilegym/kernel_inventory/__init__.py @@ -2,18 +2,18 @@ # # SPDX-License-Identifier: MIT -"""Helpers for FlashInfer-style transformer kernel inventory metadata. +"""Helpers for FlashInfer-Bench -shaped, TileGym-executed kernel inventory metadata. -The metadata files live under transformer model submodules: +The metadata files live under an inventory-owning package or suite: - ``kernel_definitions/*.json`` for FlashInfer Trace Definition objects. - ``kernel_solutions/*.json`` for Solution objects. - ``kernels/*.py`` for reusable kernel implementations referenced by Solution source paths. -This module intentionally avoids importing flashinfer-bench at module import -time. Schema validation uses FlashInfer-Bench lazily when called, and Ocean -keeps a few repo-local checks around source permalinks and path-only sources. +Shared schema fields use FlashInfer-Bench validation lazily, while +TileGym owns runtime invocation, source paths, and entry points. The checked-in +metadata is not a standalone FlashInfer-Bench runtime bundle. """ from __future__ import annotations @@ -25,9 +25,11 @@ from typing import Any from typing import Iterator -from tilegym.transformers.inventory_common import SourceContractError -from tilegym.transformers.inventory_common import resolve_repo_relative_path -from tilegym.transformers.inventory_common import validate_reference_source_contract +from tilegym.kernel_inventory.return_contract import ReturnContractError +from tilegym.kernel_inventory.return_contract import instrument_reference_returns +from tilegym.kernel_inventory.source_contract import SourceContractError +from tilegym.kernel_inventory.source_contract import resolve_repo_relative_path +from tilegym.kernel_inventory.source_contract import validate_reference_source_contract DEFINITION_SCHEMA_URL = ( "https://github.com/flashinfer-ai/flashinfer-bench/blob/main/docs/flashinfer-trace/definition.mdx" @@ -36,7 +38,7 @@ class KernelInventoryError(ValueError): - """Raised when transformer kernel inventory metadata is invalid.""" + """Raised when kernel inventory metadata is invalid.""" def load_json(path: str | Path) -> dict[str, Any]: @@ -49,30 +51,75 @@ def load_json(path: str | Path) -> dict[str, Any]: def iter_kernel_definition_paths(root: str | Path) -> Iterator[Path]: - """Yield all transformer kernel Definition JSON files under ``root``.""" - yield from sorted(Path(root).glob("src/tilegym/transformers/*/kernel_definitions/*.json")) + """Yield checked-in kernel Definition JSON files under ``root``. + + Transformer inventories keep Definitions and Solutions in sibling + directories. Suite inventories keep public Definitions at the root of + ``kernel_definitions`` and backend-specific Solutions in the sibling + ``kernel_solutions`` directory. + """ + root_path = Path(root) + transformer_paths = root_path.glob("src/tilegym/transformers/*/kernel_definitions/*.json") + suite_paths = root_path.glob("src/tilegym/suites/*/kernel_definitions/*.json") + paths = set(transformer_paths) + paths.update( + path + for path in suite_paths + if any((path.parent.parent / "kernel_solutions" / backend).is_dir() for backend in ("triton", "cutile")) + ) + yield from sorted(paths) def iter_kernel_solution_paths(root: str | Path) -> Iterator[Path]: - """Yield all transformer kernel Solution JSON files under ``root``.""" - yield from sorted(Path(root).glob("src/tilegym/transformers/*/kernel_solutions/*.json")) + """Yield checked-in backend-specific Solution JSON files under ``root``.""" + root_path = Path(root) + patterns = ( + "src/tilegym/transformers/*/kernel_solutions/*.json", + "src/tilegym/suites/*/kernel_solutions/triton/*.json", + "src/tilegym/suites/*/kernel_solutions/cutile/*.json", + ) + yield from _iter_paths(root_path, patterns) def iter_kernel_python_paths(root: str | Path) -> Iterator[Path]: - """Yield all dedicated transformer kernel Python modules under ``root``.""" + """Yield dedicated transformer kernel Python modules under ``root``. + + Suite solutions refer to their checked-in backend implementation modules, + whose importability is tested through their Solution entry points instead. + """ yield from sorted(Path(root).glob("src/tilegym/transformers/*/kernels/*.py")) +def iter_solution_paths_for_definition(definition_path: str | Path) -> Iterator[Path]: + """Yield checked-in Solutions that implement one Definition. + + The function supports both current inventory layouts without requiring + callers to infer a Solution path from a transformer-only convention. + """ + definition = Path(definition_path) + if definition.parent.name != "kernel_definitions": + raise KernelInventoryError(f"Definition must live in kernel_definitions: {definition}") + + transformer_solution = definition.parent.parent / "kernel_solutions" / definition.name + if transformer_solution.is_file(): + yield transformer_solution + + for backend in ("triton", "cutile"): + suite_solution = definition.parent.parent / "kernel_solutions" / backend / definition.name + if suite_solution.is_file(): + yield suite_solution + + def validate_definition(definition: dict[str, Any]) -> None: - """Validate a transformer kernel Definition with FIB and Ocean checks.""" + """Validate a kernel Definition with FIB and TileGym checks.""" _require_mapping(definition, "Definition") try: - from tilegym.transformers.inventory_generation import validate_ocean_definition_model + from tilegym.kernel_inventory.generation import validate_tilegym_definition_model - validate_ocean_definition_model(definition) + validate_tilegym_definition_model(definition) except ImportError as exc: raise KernelInventoryError( - "flashinfer-bench is required to validate transformer kernel Definitions. " + "flashinfer-bench is required to validate kernel Definitions. " "Install the tilegym-hf-bench dev environment with `uv sync --extra dev` " "from modeling/transformers." ) from exc @@ -80,10 +127,18 @@ def validate_definition(definition: dict[str, Any]) -> None: raise KernelInventoryError(f"Definition schema invalid: {exc}") from exc _validate_reference_run(definition["reference"], list(definition["inputs"])) + try: + instrument_reference_returns( + definition["reference"], + list(definition["outputs"]), + allow_no_return="runtime:unsupported" in definition.get("tags", []), + ) + except ReturnContractError as exc: + raise KernelInventoryError(str(exc)) from exc def validate_solution(solution: dict[str, Any], repo_root: str | Path | None = None) -> None: - """Validate a transformer kernel Solution with FIB and Ocean checks.""" + """Validate a kernel Solution with FIB and TileGym checks.""" _require_mapping(solution, "Solution") source_paths = normalize_solution_source_paths(solution) if repo_root is not None: @@ -98,12 +153,12 @@ def validate_solution(solution: dict[str, Any], repo_root: str | Path | None = N _resolve_inventory_path(root, entry_file, "Solution entry point path") try: - from tilegym.transformers.inventory_generation import validate_ocean_solution_model + from tilegym.kernel_inventory.generation import validate_tilegym_solution_model - validate_ocean_solution_model(solution, repo_root) + validate_tilegym_solution_model(solution, repo_root) except ImportError as exc: raise KernelInventoryError( - "flashinfer-bench is required to validate transformer kernel Solutions. " + "flashinfer-bench is required to validate kernel Solutions. " "Install the tilegym-hf-bench dev environment with `uv sync --extra dev` " "from modeling/transformers." ) from exc @@ -133,7 +188,7 @@ def validate_solution_entry_point(solution: dict[str, Any], repo_root: str | Pat def normalize_solution_source_paths(solution: dict[str, Any]) -> list[str]: """Return source paths from either path-list or file-object Solution sources. - Ocean transformer inventory stores source references by path and does not + TileGym inventory stores source references by path and does not require embedded ``content``. This accepts both ``{"path": [...]}`` and the file-object array shape used by current flashinfer-bench examples. """ @@ -195,12 +250,30 @@ def _validate_reference_run(reference: str, input_names: list[str]) -> None: if len(run_nodes) != 1: raise KernelInventoryError("Definition.reference must contain exactly one global run function") run_args = run_nodes[0].args - if run_args.vararg is not None or run_args.kwarg is not None: - raise KernelInventoryError("Definition.reference run function must not use *args or **kwargs") - arg_names = [arg.arg for arg in (*run_args.posonlyargs, *run_args.args, *run_args.kwonlyargs)] - if arg_names != input_names: + if run_args.vararg is not None: + raise KernelInventoryError("Definition.reference run function must not use *args") + + parameters = [*run_args.posonlyargs, *run_args.args, *run_args.kwonlyargs] + parameter_names = [parameter.arg for parameter in parameters] + unknown_inputs = [name for name in input_names if name not in parameter_names] + if run_args.kwarg is not None: + unknown_inputs = [] + if unknown_inputs: raise KernelInventoryError( - f"Definition.reference run arguments {arg_names} must match Definition.inputs {input_names}" + f"Definition.inputs contains names not accepted by Definition.reference run: {unknown_inputs}" + ) + + positional_parameters = [*run_args.posonlyargs, *run_args.args] + positional_required_count = len(positional_parameters) - len(run_args.defaults) + required_names = [parameter.arg for parameter in positional_parameters[:positional_required_count]] + required_names.extend( + parameter.arg for parameter, default in zip(run_args.kwonlyargs, run_args.kw_defaults) if default is None + ) + missing_required_inputs = [name for name in required_names if name not in input_names] + if missing_required_inputs: + raise KernelInventoryError( + "Definition.inputs must include every required Definition.reference run parameter: " + f"{missing_required_inputs}" ) @@ -214,3 +287,8 @@ def _resolve_inventory_path(root: Path, path: str, label: str) -> Path: def _require_mapping(value: Any, label: str) -> None: if not isinstance(value, dict): raise KernelInventoryError(f"{label} must be an object") + + +def _iter_paths(root: Path, patterns: tuple[str, ...]) -> Iterator[Path]: + paths = {path for pattern in patterns for path in root.glob(pattern)} + yield from sorted(paths) diff --git a/src/tilegym/transformers/inventory_generation.py b/src/tilegym/kernel_inventory/generation.py similarity index 69% rename from src/tilegym/transformers/inventory_generation.py rename to src/tilegym/kernel_inventory/generation.py index 4a8ce5c..a3fbde3 100644 --- a/src/tilegym/transformers/inventory_generation.py +++ b/src/tilegym/kernel_inventory/generation.py @@ -2,15 +2,17 @@ # # SPDX-License-Identifier: MIT -"""FIB-backed helpers for transformer kernel inventory generation. +"""FIB-shaped helpers for TileGym kernel inventory generation. This module is intentionally imported lazily by ``kernel_inventory`` so normal -TileGym runtime imports do not require FlashInfer-Bench. +TileGym runtime imports do not require FlashInfer-Bench. FIB validates the data +schema; TileGym owns execution of the checked-in metadata. """ from __future__ import annotations import json +import re from pathlib import Path from typing import Any from typing import Sequence @@ -25,31 +27,31 @@ from pydantic import Field from pydantic import model_validator -from tilegym.transformers.inventory_common import resolve_repo_relative_path as _resolve_repo_relative_path -from tilegym.transformers.inventory_common import validate_reference_source_contract +from tilegym.kernel_inventory.source_contract import resolve_repo_relative_path as _resolve_repo_relative_path +from tilegym.kernel_inventory.source_contract import validate_reference_source_contract -OCEAN_CUTILE_LANGUAGE = "cuda-tile" -_FIB_LANGUAGE_BY_OCEAN_LANGUAGE = { - OCEAN_CUTILE_LANGUAGE: SupportedLanguages.PYTHON.value, +TILEGYM_CUTILE_LANGUAGE = "cuda-tile" +_FIB_LANGUAGE_BY_TILEGYM_LANGUAGE = { + TILEGYM_CUTILE_LANGUAGE: SupportedLanguages.PYTHON.value, } -class OceanSourceFile(BaseModel): - """Ocean source reference that may omit content in checked-in JSON.""" +class TileGymSourceFile(BaseModel): + """TileGym source reference that may omit content in checked-in JSON.""" path: str content: str | None = None @model_validator(mode="after") - def _validate_source_path(self) -> "OceanSourceFile": + def _validate_source_path(self) -> "TileGymSourceFile": raw_path = Path(self.path) if not self.path or raw_path.is_absolute() or ".." in raw_path.parts: raise ValueError(f"Invalid source path: {self.path}") return self -class OceanBuildSpec(BaseModel): - """Build spec compatible with Ocean's cuTile language label.""" +class TileGymBuildSpec(BaseModel): + """Build spec compatible with TileGym's cuTile language label.""" language: str target_hardware: list[str] = Field(min_length=1) @@ -59,30 +61,35 @@ class OceanBuildSpec(BaseModel): binding: str | None = None @model_validator(mode="after") - def _validate_spec(self) -> "OceanBuildSpec": + def _validate_spec(self) -> "TileGymBuildSpec": supported_languages = {language.value for language in SupportedLanguages} - supported_languages.add(OCEAN_CUTILE_LANGUAGE) + supported_languages.add(TILEGYM_CUTILE_LANGUAGE) if self.language not in supported_languages: raise ValueError(f"Unsupported Solution.spec.language: {self.language}") if self.entry_point.count("::") != 1: raise ValueError( f'Invalid entry point format: {self.entry_point}. Expected "::".' ) + invalid_targets = [label for label in self.target_hardware if re.fullmatch(r"SM\d{2,}", label) is None] + if invalid_targets: + raise ValueError( + f"Solution.spec.target_hardware entries must use the SM format: {invalid_targets}" + ) return self def fib_language(self) -> str: """Return the closest FIB language for schema validation.""" - return _FIB_LANGUAGE_BY_OCEAN_LANGUAGE.get(self.language, self.language) + return _FIB_LANGUAGE_BY_TILEGYM_LANGUAGE.get(self.language, self.language) -class OceanSolution(BaseModel): - """Ocean Solution schema with path-only source support.""" +class TileGymSolution(BaseModel): + """TileGym Solution schema with path-only source support.""" name: str definition: str author: str - spec: OceanBuildSpec - sources: list[OceanSourceFile] = Field(min_length=1) + spec: TileGymBuildSpec + sources: list[TileGymSourceFile] = Field(min_length=1) description: str | None = None @model_validator(mode="before") @@ -105,7 +112,7 @@ def _normalize_sources(cls, data: Any) -> Any: return normalized @model_validator(mode="after") - def _validate_source_path_entry_point(self) -> "OceanSolution": + def _validate_source_path_entry_point(self) -> "TileGymSolution": seen_paths: set[str] = set() for source in self.sources: if source.path in seen_paths: @@ -145,8 +152,8 @@ def to_fib_solution( fib_data["sources"] = sources return Solution.model_validate(fib_data) - def to_ocean_dict(self, *, path_only_sources: bool = True) -> dict[str, Any]: - """Return stable Ocean JSON data.""" + def to_tilegym_dict(self, *, path_only_sources: bool = True) -> dict[str, Any]: + """Return stable TileGym JSON data.""" data = self.model_dump(mode="json", exclude_none=True) if path_only_sources: data["sources"] = {"path": [source.path for source in self.sources]} @@ -180,7 +187,7 @@ def make_definition( description: str | None = None, constraints: Sequence[str] | None = None, ) -> Definition: - """Create a Definition after enforcing Ocean's source permalink contract.""" + """Create a Definition after enforcing TileGym's source permalink contract.""" validate_reference_source_contract(reference) return Definition( name=name, @@ -200,12 +207,12 @@ def make_solution( name: str, definition: str, author: str, - spec: dict[str, Any] | OceanBuildSpec, - sources: dict[str, Any] | Sequence[str | dict[str, Any] | OceanSourceFile], + spec: dict[str, Any] | TileGymBuildSpec, + sources: dict[str, Any] | Sequence[str | dict[str, Any] | TileGymSourceFile], repo_root: str | Path | None = None, description: str | None = None, -) -> OceanSolution: - """Create an Ocean Solution and validate it through FIB Solution.""" +) -> TileGymSolution: + """Create a TileGym Solution and validate it through FIB Solution.""" normalized_sources: Any if isinstance(sources, dict): normalized_sources = sources @@ -214,12 +221,12 @@ def make_solution( else: normalized_sources = [{"path": source} if isinstance(source, str) else source for source in sources] - solution = OceanSolution.model_validate( + solution = TileGymSolution.model_validate( { "name": name, "definition": definition, "author": author, - "spec": spec.model_dump(mode="json") if isinstance(spec, OceanBuildSpec) else spec, + "spec": spec.model_dump(mode="json") if isinstance(spec, TileGymBuildSpec) else spec, "sources": normalized_sources, "description": description, } @@ -228,48 +235,48 @@ def make_solution( return solution -def source_path(path: str) -> OceanSourceFile: - """Create a path-only Ocean source reference.""" - return OceanSourceFile(path=path) +def source_path(path: str) -> TileGymSourceFile: + """Create a path-only TileGym source reference.""" + return TileGymSourceFile(path=path) -def validate_ocean_definition_model(definition: dict[str, Any]) -> Definition: - """Validate an Ocean Definition with FIB Definition.""" +def validate_tilegym_definition_model(definition: dict[str, Any]) -> Definition: + """Validate a TileGym Definition with FIB Definition.""" model = Definition.model_validate(definition) validate_reference_source_contract(model.reference) return model -def validate_ocean_solution_model(solution: dict[str, Any], repo_root: str | Path | None = None) -> OceanSolution: - """Validate an Ocean Solution with Ocean and FIB schema checks.""" - model = OceanSolution.model_validate(solution) +def validate_tilegym_solution_model(solution: dict[str, Any], repo_root: str | Path | None = None) -> TileGymSolution: + """Validate a TileGym Solution with TileGym and FIB schema checks.""" + model = TileGymSolution.model_validate(solution) model.to_fib_solution(repo_root, allow_placeholder_content=repo_root is None) return model def materialize_solution_for_fib(solution: dict[str, Any], repo_root: str | Path) -> Solution: - """Return a FIB Solution with source contents materialized from Ocean JSON.""" - return OceanSolution.model_validate(solution).to_fib_solution(repo_root) + """Return a FIB Solution with source contents materialized from TileGym JSON.""" + return TileGymSolution.model_validate(solution).to_fib_solution(repo_root) -def definition_to_ocean_json(definition: Definition) -> dict[str, Any]: - """Return stable Ocean JSON data for a Definition.""" +def definition_to_tilegym_json(definition: Definition) -> dict[str, Any]: + """Return stable TileGym JSON data for a Definition.""" return _drop_none_except_shape(definition.model_dump(mode="json")) -def solution_to_ocean_json(solution: OceanSolution, *, path_only_sources: bool = True) -> dict[str, Any]: - """Return stable Ocean JSON data for a Solution.""" - return solution.to_ocean_dict(path_only_sources=path_only_sources) +def solution_to_tilegym_json(solution: TileGymSolution, *, path_only_sources: bool = True) -> dict[str, Any]: + """Return stable TileGym JSON data for a Solution.""" + return solution.to_tilegym_dict(path_only_sources=path_only_sources) def write_definition_json(definition: Definition, path: str | Path) -> None: - """Write a Definition JSON file using Ocean's stable formatting.""" - _write_stable_json(definition_to_ocean_json(definition), path) + """Write a Definition JSON file using TileGym's stable formatting.""" + _write_stable_json(definition_to_tilegym_json(definition), path) -def write_solution_json(solution: OceanSolution, path: str | Path, *, path_only_sources: bool = True) -> None: - """Write a Solution JSON file using Ocean's stable formatting.""" - _write_stable_json(solution_to_ocean_json(solution, path_only_sources=path_only_sources), path) +def write_solution_json(solution: TileGymSolution, path: str | Path, *, path_only_sources: bool = True) -> None: + """Write a Solution JSON file using TileGym's stable formatting.""" + _write_stable_json(solution_to_tilegym_json(solution, path_only_sources=path_only_sources), path) def _drop_none_except_shape(value: Any, *, key: str | None = None) -> Any: diff --git a/src/tilegym/kernel_inventory/return_contract.py b/src/tilegym/kernel_inventory/return_contract.py new file mode 100644 index 0000000..8e872b9 --- /dev/null +++ b/src/tilegym/kernel_inventory/return_contract.py @@ -0,0 +1,163 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# SPDX-License-Identifier: MIT + +"""AST-derived mapping from reference return values to Definition outputs.""" + +from __future__ import annotations + +import ast +import copy +from collections.abc import Sequence + +CAPTURE_RETURN_NAME = "__tilegym_capture_reference_return__" + + +class ReturnContractError(ValueError): + """Raised when reference return expressions cannot map to Definition outputs.""" + + +def instrument_reference_returns( + reference: str, + output_names: Sequence[str], + *, + allow_no_return: bool = False, +) -> ast.Module: + """Wrap each executed ``run`` return with its parallel output-name tree.""" + module = ast.parse(reference, mode="exec") + run_nodes = [node for node in module.body if isinstance(node, ast.FunctionDef) and node.name == "run"] + if len(run_nodes) != 1: + raise ReturnContractError("Definition.reference must define exactly one global run function") + + transformer = _ReturnTransformer(tuple(output_names)) + run = run_nodes[0] + run.body = [transformer.visit(statement) for statement in run.body] + if not transformer.descriptors: + if allow_no_return: + return ast.fix_missing_locations(module) + raise ReturnContractError("Definition.reference run must return its declared outputs") + + referenced_names = { + node.value + for descriptor in transformer.descriptors + for node in ast.walk(descriptor) + if isinstance(node, ast.Constant) and isinstance(node.value, str) + } + missing_names = sorted(set(output_names) - referenced_names) + if missing_names: + raise ReturnContractError(f"Definition.outputs are not referenced by run return expressions: {missing_names}") + return ast.fix_missing_locations(module) + + +class _ReturnTransformer(ast.NodeTransformer): + def __init__(self, output_names: tuple[str, ...]): + self.output_names = output_names + self.descriptors: list[ast.expr] = [] + + def visit_FunctionDef(self, node: ast.FunctionDef): + """Do not treat returns from helpers nested inside ``run`` as public outputs.""" + return node + + def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef): + return node + + def visit_Lambda(self, node: ast.Lambda): + return node + + def visit_Return(self, node: ast.Return): + value = node.value if node.value is not None else ast.Constant(value=None) + descriptor = _top_level_descriptor(value, self.output_names) + self.descriptors.append(copy.deepcopy(descriptor)) + node.value = ast.Call( + func=ast.Name(id=CAPTURE_RETURN_NAME, ctx=ast.Load()), + args=[value, descriptor], + keywords=[], + ) + return node + + +def _top_level_descriptor(value: ast.expr, output_names: tuple[str, ...]) -> ast.expr: + if isinstance(value, ast.Tuple): + if len(value.elts) == len(output_names): + try: + explicit = [_value_descriptor(element, output_names, None) for element in value.elts] + except ReturnContractError: + explicit = [] + explicit_names = { + node.value + for descriptor in explicit + for node in ast.walk(descriptor) + if isinstance(node, ast.Constant) and isinstance(node.value, str) + } + if explicit_names == set(output_names): + return ast.Tuple(elts=explicit, ctx=ast.Load()) + return ast.Tuple( + elts=[ + _positional_descriptor(element, output_name) + for element, output_name in zip(value.elts, output_names, strict=True) + ], + ctx=ast.Load(), + ) + return ast.Tuple( + elts=[_value_descriptor(element, output_names, None) for element in value.elts], + ctx=ast.Load(), + ) + + fallback_name = output_names[0] if len(output_names) == 1 else None + return _value_descriptor(value, output_names, fallback_name) + + +def _positional_descriptor(value: ast.expr, output_name: str) -> ast.expr: + if isinstance(value, ast.Constant) and value.value is None: + return ast.Constant(value=None) + if isinstance(value, ast.IfExp): + return ast.IfExp( + test=copy.deepcopy(value.test), + body=_positional_descriptor(value.body, output_name), + orelse=_positional_descriptor(value.orelse, output_name), + ) + if isinstance(value, ast.Tuple): + raise ReturnContractError("Nested reference return tuples require SSA variables named after Definition outputs") + return ast.Constant(value=output_name) + + +def _value_descriptor( + value: ast.expr, + output_names: tuple[str, ...], + fallback_name: str | None, +) -> ast.expr: + if isinstance(value, ast.Constant) and value.value is None: + return ast.Constant(value=None) + if isinstance(value, ast.IfExp): + return ast.IfExp( + test=copy.deepcopy(value.test), + body=_value_descriptor(value.body, output_names, fallback_name), + orelse=_value_descriptor(value.orelse, output_names, fallback_name), + ) + if isinstance(value, ast.Tuple): + return ast.Tuple( + elts=[_value_descriptor(element, output_names, None) for element in value.elts], + ctx=ast.Load(), + ) + + root_name = _root_name(value) + if root_name in output_names: + return ast.Constant(value=root_name) + if fallback_name is not None: + return ast.Constant(value=fallback_name) + raise ReturnContractError( + "Reference return tensor must use an SSA variable named after a Definition output; " + f"could not map expression {ast.unparse(value)!r}" + ) + + +def _root_name(value: ast.expr) -> str | None: + if isinstance(value, ast.Name): + return value.id + if isinstance(value, ast.Attribute): + return _root_name(value.value) + if isinstance(value, ast.Call): + return _root_name(value.func) + if isinstance(value, ast.Subscript): + return _root_name(value.value) + return None diff --git a/src/tilegym/transformers/inventory_common.py b/src/tilegym/kernel_inventory/source_contract.py similarity index 96% rename from src/tilegym/transformers/inventory_common.py rename to src/tilegym/kernel_inventory/source_contract.py index a0919d9..3834c57 100644 --- a/src/tilegym/transformers/inventory_common.py +++ b/src/tilegym/kernel_inventory/source_contract.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: MIT -"""Shared dependency-light checks for transformer kernel inventory metadata.""" +"""Shared dependency-light checks for kernel inventory metadata.""" from __future__ import annotations diff --git a/src/tilegym/transformers/olmo3/kernel_definitions/olmo3_dual_rms_norm.json b/src/tilegym/transformers/olmo3/kernel_definitions/olmo3_dual_rms_norm.json index d075752..d3c3b3b 100644 --- a/src/tilegym/transformers/olmo3/kernel_definitions/olmo3_dual_rms_norm.json +++ b/src/tilegym/transformers/olmo3/kernel_definitions/olmo3_dual_rms_norm.json @@ -60,7 +60,7 @@ ] } }, - "reference": "# Source: https://github.com/huggingface/transformers/blob/aad13b87ed59f2afcfaebc985f403301887a35fc/src/transformers/models/olmo3/modeling_olmo3.py#L179-L180\n# Source: https://github.com/huggingface/transformers/blob/aad13b87ed59f2afcfaebc985f403301887a35fc/src/transformers/models/olmo3/modeling_olmo3.py#L55-L60\nimport torch\n\ndef run(eps, k, k_weight, q, q_weight):\n qf = q.to(torch.float32)\n kf = k.to(torch.float32)\n q_out = qf * torch.rsqrt((qf * qf).mean(-1, keepdim=True) + eps) * q_weight\n k_out = kf * torch.rsqrt((kf * kf).mean(-1, keepdim=True) + eps) * k_weight\n q.copy_(q_out.to(q.dtype))\n k.copy_(k_out.to(k.dtype))\n return q, k", + "reference": "# Source: https://github.com/huggingface/transformers/blob/aad13b87ed59f2afcfaebc985f403301887a35fc/src/transformers/models/olmo3/modeling_olmo3.py#L179-L180\n# Source: https://github.com/huggingface/transformers/blob/aad13b87ed59f2afcfaebc985f403301887a35fc/src/transformers/models/olmo3/modeling_olmo3.py#L55-L60\nimport torch\n\ndef run(q, k, q_weight, k_weight, eps):\n qf = q.to(torch.float32)\n kf = k.to(torch.float32)\n q_out = qf * torch.rsqrt((qf * qf).mean(-1, keepdim=True) + eps) * q_weight\n k_out = kf * torch.rsqrt((kf * kf).mean(-1, keepdim=True) + eps) * k_weight\n q.copy_(q_out.to(q.dtype))\n k.copy_(k_out.to(k.dtype))\n return q, k", "tags": [ "model:olmo3", "stage:prefill", diff --git a/src/tilegym/transformers/olmo3/kernel_definitions/olmo3_rms_norm_residual_add.json b/src/tilegym/transformers/olmo3/kernel_definitions/olmo3_rms_norm_residual_add.json index 2780504..98cdfca 100644 --- a/src/tilegym/transformers/olmo3/kernel_definitions/olmo3_rms_norm_residual_add.json +++ b/src/tilegym/transformers/olmo3/kernel_definitions/olmo3_rms_norm_residual_add.json @@ -47,7 +47,7 @@ ] } }, - "reference": "# Source: https://github.com/huggingface/transformers/blob/aad13b87ed59f2afcfaebc985f403301887a35fc/src/transformers/models/olmo3/modeling_olmo3.py#L253-L265\n# Source: https://github.com/huggingface/transformers/blob/aad13b87ed59f2afcfaebc985f403301887a35fc/src/transformers/models/olmo3/modeling_olmo3.py#L55-L60\nimport torch\n\ndef run(eps, residual, weight, x):\n xf = x.to(torch.float32)\n normed = xf * torch.rsqrt((xf * xf).mean(-1, keepdim=True) + eps) * weight\n return (residual + normed).to(residual.dtype)", + "reference": "# Source: https://github.com/huggingface/transformers/blob/aad13b87ed59f2afcfaebc985f403301887a35fc/src/transformers/models/olmo3/modeling_olmo3.py#L253-L265\n# Source: https://github.com/huggingface/transformers/blob/aad13b87ed59f2afcfaebc985f403301887a35fc/src/transformers/models/olmo3/modeling_olmo3.py#L55-L60\nimport torch\n\ndef run(x, residual, weight, eps):\n xf = x.to(torch.float32)\n normed = xf * torch.rsqrt((xf * xf).mean(-1, keepdim=True) + eps) * weight\n return (residual + normed).to(residual.dtype)", "tags": [ "model:olmo3", "stage:prefill", diff --git a/src/tilegym/transformers/olmo3/kernel_solutions/olmo3_dual_rms_norm.json b/src/tilegym/transformers/olmo3/kernel_solutions/olmo3_dual_rms_norm.json index aac7937..6173487 100644 --- a/src/tilegym/transformers/olmo3/kernel_solutions/olmo3_dual_rms_norm.json +++ b/src/tilegym/transformers/olmo3/kernel_solutions/olmo3_dual_rms_norm.json @@ -17,8 +17,8 @@ "entry_point": "src/tilegym/transformers/olmo3/kernels/dual_rms_norm.py::dual_rms_norm_cutile", "language": "cuda-tile", "target_hardware": [ - "NVIDIA_B200", - "NVIDIA_RTX_PRO_6000_BLACKWELL_SERVER_EDITION" + "SM100", + "SM120" ] } } diff --git a/src/tilegym/transformers/olmo3/kernel_solutions/olmo3_rms_norm_residual_add.json b/src/tilegym/transformers/olmo3/kernel_solutions/olmo3_rms_norm_residual_add.json index bb6f237..a039d2b 100644 --- a/src/tilegym/transformers/olmo3/kernel_solutions/olmo3_rms_norm_residual_add.json +++ b/src/tilegym/transformers/olmo3/kernel_solutions/olmo3_rms_norm_residual_add.json @@ -17,9 +17,9 @@ "entry_point": "src/tilegym/transformers/olmo3/kernels/rms_norm_residual_add.py::rms_norm_residual_add_cutile", "language": "cuda-tile", "target_hardware": [ - "NVIDIA_B200", - "NVIDIA_GB300", - "NVIDIA_RTX_PRO_6000_BLACKWELL_SERVER_EDITION" + "SM100", + "SM103", + "SM120" ] } } diff --git a/src/tilegym/transformers/olmoe/kernel_definitions/olmoe_dual_rms_norm.json b/src/tilegym/transformers/olmoe/kernel_definitions/olmoe_dual_rms_norm.json index ab61b7d..1a87784 100644 --- a/src/tilegym/transformers/olmoe/kernel_definitions/olmoe_dual_rms_norm.json +++ b/src/tilegym/transformers/olmoe/kernel_definitions/olmoe_dual_rms_norm.json @@ -60,7 +60,7 @@ ] } }, - "reference": "# Source: https://github.com/huggingface/transformers/blob/aad13b87ed59f2afcfaebc985f403301887a35fc/src/transformers/models/olmoe/modeling_olmoe.py#L263-L264\n# Source: https://github.com/huggingface/transformers/blob/aad13b87ed59f2afcfaebc985f403301887a35fc/src/transformers/models/olmoe/modeling_olmoe.py#L58-L63\nimport torch\n\ndef run(eps, k, k_weight, q, q_weight):\n qf = q.to(torch.float32)\n kf = k.to(torch.float32)\n q_out = qf * torch.rsqrt((qf * qf).mean(-1, keepdim=True) + eps) * q_weight\n k_out = kf * torch.rsqrt((kf * kf).mean(-1, keepdim=True) + eps) * k_weight\n q.copy_(q_out.to(q.dtype))\n k.copy_(k_out.to(k.dtype))\n return q, k", + "reference": "# Source: https://github.com/huggingface/transformers/blob/aad13b87ed59f2afcfaebc985f403301887a35fc/src/transformers/models/olmoe/modeling_olmoe.py#L263-L264\n# Source: https://github.com/huggingface/transformers/blob/aad13b87ed59f2afcfaebc985f403301887a35fc/src/transformers/models/olmoe/modeling_olmoe.py#L58-L63\nimport torch\n\ndef run(q, k, q_weight, k_weight, eps):\n qf = q.to(torch.float32)\n kf = k.to(torch.float32)\n q_out = qf * torch.rsqrt((qf * qf).mean(-1, keepdim=True) + eps) * q_weight\n k_out = kf * torch.rsqrt((kf * kf).mean(-1, keepdim=True) + eps) * k_weight\n q.copy_(q_out.to(q.dtype))\n k.copy_(k_out.to(k.dtype))\n return q, k", "tags": [ "model:olmoe", "stage:prefill", diff --git a/src/tilegym/transformers/olmoe/kernel_definitions/olmoe_residual_add_rms_norm.json b/src/tilegym/transformers/olmoe/kernel_definitions/olmoe_residual_add_rms_norm.json index eb0c269..010f614 100644 --- a/src/tilegym/transformers/olmoe/kernel_definitions/olmoe_residual_add_rms_norm.json +++ b/src/tilegym/transformers/olmoe/kernel_definitions/olmoe_residual_add_rms_norm.json @@ -54,7 +54,7 @@ ] } }, - "reference": "# Source: https://github.com/huggingface/transformers/blob/aad13b87ed59f2afcfaebc985f403301887a35fc/src/transformers/models/olmoe/modeling_olmoe.py#L414-L419\n# Source: https://github.com/huggingface/transformers/blob/aad13b87ed59f2afcfaebc985f403301887a35fc/src/transformers/models/olmoe/modeling_olmoe.py#L58-L63\nimport torch\n\ndef run(eps, residual, weight, x):\n summed = residual + x\n s = summed.to(torch.float32)\n normed = s * torch.rsqrt((s * s).mean(-1, keepdim=True) + eps) * weight\n return summed, normed.to(summed.dtype)", + "reference": "# Source: https://github.com/huggingface/transformers/blob/aad13b87ed59f2afcfaebc985f403301887a35fc/src/transformers/models/olmoe/modeling_olmoe.py#L414-L419\n# Source: https://github.com/huggingface/transformers/blob/aad13b87ed59f2afcfaebc985f403301887a35fc/src/transformers/models/olmoe/modeling_olmoe.py#L58-L63\nimport torch\n\ndef run(residual, x, weight, eps):\n summed = residual + x\n s = summed.to(torch.float32)\n normed = s * torch.rsqrt((s * s).mean(-1, keepdim=True) + eps) * weight\n return summed, normed.to(summed.dtype)", "tags": [ "model:olmoe", "stage:prefill", diff --git a/src/tilegym/transformers/olmoe/kernel_solutions/olmoe_dual_rms_norm.json b/src/tilegym/transformers/olmoe/kernel_solutions/olmoe_dual_rms_norm.json index bccd36e..218e30e 100644 --- a/src/tilegym/transformers/olmoe/kernel_solutions/olmoe_dual_rms_norm.json +++ b/src/tilegym/transformers/olmoe/kernel_solutions/olmoe_dual_rms_norm.json @@ -17,8 +17,8 @@ "entry_point": "src/tilegym/transformers/olmoe/kernels/dual_rms_norm.py::dual_rms_norm_olmoe_cutile", "language": "cuda-tile", "target_hardware": [ - "NVIDIA_B200", - "NVIDIA_RTX_PRO_6000_BLACKWELL_SERVER_EDITION" + "SM100", + "SM120" ] } } diff --git a/src/tilegym/transformers/olmoe/kernel_solutions/olmoe_residual_add_rms_norm.json b/src/tilegym/transformers/olmoe/kernel_solutions/olmoe_residual_add_rms_norm.json index f33707c..141bab4 100644 --- a/src/tilegym/transformers/olmoe/kernel_solutions/olmoe_residual_add_rms_norm.json +++ b/src/tilegym/transformers/olmoe/kernel_solutions/olmoe_residual_add_rms_norm.json @@ -17,9 +17,9 @@ "entry_point": "src/tilegym/transformers/olmoe/kernels/residual_add_rms_norm.py::residual_add_rms_norm_olmoe_cutile", "language": "cuda-tile", "target_hardware": [ - "NVIDIA_B200", - "NVIDIA_GB300", - "NVIDIA_RTX_PRO_6000_BLACKWELL_SERVER_EDITION" + "SM100", + "SM103", + "SM120" ] } } diff --git a/src/tilegym/transformers/qwen3_5/kernel_definitions/qwen3_5_causal_conv1d_prefill_silu.json b/src/tilegym/transformers/qwen3_5/kernel_definitions/qwen3_5_causal_conv1d_prefill_silu.json index 44f171f..37751c3 100644 --- a/src/tilegym/transformers/qwen3_5/kernel_definitions/qwen3_5_causal_conv1d_prefill_silu.json +++ b/src/tilegym/transformers/qwen3_5/kernel_definitions/qwen3_5_causal_conv1d_prefill_silu.json @@ -61,7 +61,7 @@ ] } }, - "reference": "# Source: https://github.com/huggingface/transformers/blob/aad13b87ed59f2afcfaebc985f403301887a35fc/src/transformers/models/qwen3_5/modeling_qwen3_5.py#L556-L568\nimport torch\n\ndef run(seq_len, weight, x):\n y = []\n x2 = x[0]\n for t in range(seq_len):\n window = x2[:, t:t + weight.shape[1]]\n dot = (window * weight).sum(-1)\n y.append(dot * torch.sigmoid(dot))\n return torch.stack(y, dim=-1).unsqueeze(0)", + "reference": "# Source: https://github.com/huggingface/transformers/blob/aad13b87ed59f2afcfaebc985f403301887a35fc/src/transformers/models/qwen3_5/modeling_qwen3_5.py#L556-L568\nimport torch\n\ndef run(x, weight, seq_len):\n y = []\n x2 = x[0]\n for t in range(seq_len):\n window = x2[:, t:t + weight.shape[1]]\n dot = (window * weight).sum(-1)\n y.append(dot * torch.sigmoid(dot))\n return torch.stack(y, dim=-1).unsqueeze(0)", "tags": [ "model:qwen3.5", "stage:prefill", diff --git a/src/tilegym/transformers/qwen3_5/kernel_definitions/qwen3_5_causal_conv1d_update_silu.json b/src/tilegym/transformers/qwen3_5/kernel_definitions/qwen3_5_causal_conv1d_update_silu.json index b567e17..b4a0916 100644 --- a/src/tilegym/transformers/qwen3_5/kernel_definitions/qwen3_5_causal_conv1d_update_silu.json +++ b/src/tilegym/transformers/qwen3_5/kernel_definitions/qwen3_5_causal_conv1d_update_silu.json @@ -66,7 +66,7 @@ ] } }, - "reference": "# Source: https://github.com/huggingface/transformers/blob/aad13b87ed59f2afcfaebc985f403301887a35fc/src/transformers/models/qwen3_5/modeling_qwen3_5.py#L545-L554\nimport torch\n\ndef run(conv_state, hidden_states, weight):\n x = hidden_states[:, :, 0]\n full = torch.cat([conv_state, x.unsqueeze(-1)], dim=-1)\n dot = (full * weight.unsqueeze(0)).sum(-1)\n output = dot * torch.sigmoid(dot)\n conv_state[:, :, 0] = conv_state[:, :, 1]\n conv_state[:, :, 1] = conv_state[:, :, 2]\n conv_state[:, :, 2] = x\n return output.unsqueeze(-1)", + "reference": "# Source: https://github.com/huggingface/transformers/blob/aad13b87ed59f2afcfaebc985f403301887a35fc/src/transformers/models/qwen3_5/modeling_qwen3_5.py#L545-L554\nimport torch\n\ndef run(hidden_states, conv_state, weight, bias=None, activation=None):\n x = hidden_states[:, :, 0]\n full = torch.cat([conv_state, x.unsqueeze(-1)], dim=-1)\n dot = (full * weight.unsqueeze(0)).sum(-1)\n output = dot * torch.sigmoid(dot)\n conv_state[:, :, 0] = conv_state[:, :, 1]\n conv_state[:, :, 1] = conv_state[:, :, 2]\n conv_state[:, :, 2] = x\n return output.unsqueeze(-1)", "tags": [ "model:qwen3.5", "stage:decode", diff --git a/src/tilegym/transformers/qwen3_5/kernel_definitions/qwen3_5_gdr_preprocess.json b/src/tilegym/transformers/qwen3_5/kernel_definitions/qwen3_5_gdr_preprocess.json index 1686aff..57259ee 100644 --- a/src/tilegym/transformers/qwen3_5/kernel_definitions/qwen3_5_gdr_preprocess.json +++ b/src/tilegym/transformers/qwen3_5/kernel_definitions/qwen3_5_gdr_preprocess.json @@ -56,7 +56,7 @@ ] } }, - "reference": "# Source: https://github.com/huggingface/transformers/blob/aad13b87ed59f2afcfaebc985f403301887a35fc/src/transformers/models/qwen3_5/modeling_qwen3_5.py#L585-L587\nimport torch\nimport torch.nn.functional as F\n\ndef run(A_log, a, b, dt_bias):\n beta = b.sigmoid()\n g = -A_log.float().exp() * F.softplus(a.float() + dt_bias)\n return beta, g", + "reference": "# Source: https://github.com/huggingface/transformers/blob/aad13b87ed59f2afcfaebc985f403301887a35fc/src/transformers/models/qwen3_5/modeling_qwen3_5.py#L585-L587\nimport torch\nimport torch.nn.functional as F\n\ndef run(b, a, A_log, dt_bias):\n beta = b.sigmoid()\n g = -A_log.float().exp() * F.softplus(a.float() + dt_bias)\n return beta, g", "tags": [ "model:qwen3.5", "stage:prefill", diff --git a/src/tilegym/transformers/qwen3_5/kernel_definitions/qwen3_5_residual_add_gemma_rms_norm.json b/src/tilegym/transformers/qwen3_5/kernel_definitions/qwen3_5_residual_add_gemma_rms_norm.json index 7460bd7..d36ad73 100644 --- a/src/tilegym/transformers/qwen3_5/kernel_definitions/qwen3_5_residual_add_gemma_rms_norm.json +++ b/src/tilegym/transformers/qwen3_5/kernel_definitions/qwen3_5_residual_add_gemma_rms_norm.json @@ -58,7 +58,7 @@ ] } }, - "reference": "# Source: https://github.com/huggingface/transformers/blob/aad13b87ed59f2afcfaebc985f403301887a35fc/src/transformers/models/qwen3_5/modeling_qwen3_5.py#L875-L881\n# Source: https://github.com/huggingface/transformers/blob/aad13b87ed59f2afcfaebc985f403301887a35fc/src/transformers/models/qwen3_5/modeling_qwen3_5.py#L814-L822\nimport torch\n\ndef run(eps, offset, residual, weight, x):\n summed = residual + x\n s = summed.float()\n output = s * torch.rsqrt(s.pow(2).mean(-1, keepdim=True) + eps)\n output = output * (offset + weight.float())\n return summed, output.type_as(summed)", + "reference": "# Source: https://github.com/huggingface/transformers/blob/aad13b87ed59f2afcfaebc985f403301887a35fc/src/transformers/models/qwen3_5/modeling_qwen3_5.py#L875-L881\n# Source: https://github.com/huggingface/transformers/blob/aad13b87ed59f2afcfaebc985f403301887a35fc/src/transformers/models/qwen3_5/modeling_qwen3_5.py#L814-L822\nimport torch\n\ndef run(residual, x, weight, eps, offset=1.0):\n summed = residual + x\n s = summed.float()\n output = s * torch.rsqrt(s.pow(2).mean(-1, keepdim=True) + eps)\n output = output * (offset + weight.float())\n return summed, output.type_as(summed)", "tags": [ "model:qwen3.5", "stage:prefill", diff --git a/src/tilegym/transformers/qwen3_5/kernel_definitions/qwen3_5_rms_norm_gated_silu.json b/src/tilegym/transformers/qwen3_5/kernel_definitions/qwen3_5_rms_norm_gated_silu.json index 2344d83..dddd587 100644 --- a/src/tilegym/transformers/qwen3_5/kernel_definitions/qwen3_5_rms_norm_gated_silu.json +++ b/src/tilegym/transformers/qwen3_5/kernel_definitions/qwen3_5_rms_norm_gated_silu.json @@ -47,7 +47,7 @@ ] } }, - "reference": "# Source: https://github.com/huggingface/transformers/blob/aad13b87ed59f2afcfaebc985f403301887a35fc/src/transformers/models/qwen3_5/modeling_qwen3_5.py#L271-L280\nimport torch\nimport torch.nn.functional as F\n\ndef run(eps, gate, hidden_states, weight):\n input_dtype = hidden_states.dtype\n hidden_states = hidden_states.to(torch.float32)\n variance = hidden_states.pow(2).mean(-1, keepdim=True)\n hidden_states = hidden_states * torch.rsqrt(variance + eps)\n hidden_states = weight * hidden_states.to(input_dtype)\n hidden_states = hidden_states * F.silu(gate.to(torch.float32))\n return hidden_states.to(input_dtype)", + "reference": "# Source: https://github.com/huggingface/transformers/blob/aad13b87ed59f2afcfaebc985f403301887a35fc/src/transformers/models/qwen3_5/modeling_qwen3_5.py#L271-L280\nimport torch\nimport torch.nn.functional as F\n\ndef run(hidden_states, gate, weight, eps):\n input_dtype = hidden_states.dtype\n hidden_states = hidden_states.to(torch.float32)\n variance = hidden_states.pow(2).mean(-1, keepdim=True)\n hidden_states = hidden_states * torch.rsqrt(variance + eps)\n hidden_states = weight * hidden_states.to(input_dtype)\n hidden_states = hidden_states * F.silu(gate.to(torch.float32))\n return hidden_states.to(input_dtype)", "tags": [ "model:qwen3.5", "stage:prefill", diff --git a/src/tilegym/transformers/qwen3_5/kernel_definitions/qwen3_5_sigmoid_mul.json b/src/tilegym/transformers/qwen3_5/kernel_definitions/qwen3_5_sigmoid_mul.json index 581ea3e..d95a4fb 100644 --- a/src/tilegym/transformers/qwen3_5/kernel_definitions/qwen3_5_sigmoid_mul.json +++ b/src/tilegym/transformers/qwen3_5/kernel_definitions/qwen3_5_sigmoid_mul.json @@ -37,7 +37,7 @@ ] } }, - "reference": "# Source: https://github.com/huggingface/transformers/blob/aad13b87ed59f2afcfaebc985f403301887a35fc/src/transformers/models/qwen3_5/modeling_qwen3_5.py#L785-L786\nimport torch\n\ndef run(gate, x):\n return x * torch.sigmoid(gate)", + "reference": "# Source: https://github.com/huggingface/transformers/blob/aad13b87ed59f2afcfaebc985f403301887a35fc/src/transformers/models/qwen3_5/modeling_qwen3_5.py#L785-L786\nimport torch\n\ndef run(x, gate):\n return x * torch.sigmoid(gate)", "tags": [ "model:qwen3.5", "stage:prefill", diff --git a/src/tilegym/transformers/qwen3_5/kernel_solutions/qwen3_5_causal_conv1d_prefill_silu.json b/src/tilegym/transformers/qwen3_5/kernel_solutions/qwen3_5_causal_conv1d_prefill_silu.json index d1d2a6f..b20e3b2 100644 --- a/src/tilegym/transformers/qwen3_5/kernel_solutions/qwen3_5_causal_conv1d_prefill_silu.json +++ b/src/tilegym/transformers/qwen3_5/kernel_solutions/qwen3_5_causal_conv1d_prefill_silu.json @@ -17,9 +17,9 @@ "entry_point": "src/tilegym/transformers/qwen3_5/kernels/causal_conv1d_prefill_silu.py::causal_conv1d_prefill_silu_cutile", "language": "cuda-tile", "target_hardware": [ - "NVIDIA_B200", - "NVIDIA_GB300", - "NVIDIA_RTX_PRO_6000_BLACKWELL_SERVER_EDITION" + "SM100", + "SM103", + "SM120" ] } } diff --git a/src/tilegym/transformers/qwen3_5/kernel_solutions/qwen3_5_causal_conv1d_update_silu.json b/src/tilegym/transformers/qwen3_5/kernel_solutions/qwen3_5_causal_conv1d_update_silu.json index c7ec7c3..9c99695 100644 --- a/src/tilegym/transformers/qwen3_5/kernel_solutions/qwen3_5_causal_conv1d_update_silu.json +++ b/src/tilegym/transformers/qwen3_5/kernel_solutions/qwen3_5_causal_conv1d_update_silu.json @@ -17,9 +17,9 @@ "entry_point": "src/tilegym/transformers/qwen3_5/kernels/causal_conv1d_update_silu.py::causal_conv1d_update_silu_cutile", "language": "cuda-tile", "target_hardware": [ - "NVIDIA_B200", - "NVIDIA_GB300", - "NVIDIA_RTX_PRO_6000_BLACKWELL_SERVER_EDITION" + "SM100", + "SM103", + "SM120" ] } } diff --git a/src/tilegym/transformers/qwen3_5/kernel_solutions/qwen3_5_gdr_preprocess.json b/src/tilegym/transformers/qwen3_5/kernel_solutions/qwen3_5_gdr_preprocess.json index 3a59c62..5c3cdc2 100644 --- a/src/tilegym/transformers/qwen3_5/kernel_solutions/qwen3_5_gdr_preprocess.json +++ b/src/tilegym/transformers/qwen3_5/kernel_solutions/qwen3_5_gdr_preprocess.json @@ -17,9 +17,9 @@ "entry_point": "src/tilegym/transformers/qwen3_5/kernels/gdr_preprocess.py::gdr_preprocess_cutile", "language": "cuda-tile", "target_hardware": [ - "NVIDIA_B200", - "NVIDIA_GB300", - "NVIDIA_RTX_PRO_6000_BLACKWELL_SERVER_EDITION" + "SM100", + "SM103", + "SM120" ] } } diff --git a/src/tilegym/transformers/qwen3_5/kernel_solutions/qwen3_5_residual_add_gemma_rms_norm.json b/src/tilegym/transformers/qwen3_5/kernel_solutions/qwen3_5_residual_add_gemma_rms_norm.json index 0cd1c4c..378da05 100644 --- a/src/tilegym/transformers/qwen3_5/kernel_solutions/qwen3_5_residual_add_gemma_rms_norm.json +++ b/src/tilegym/transformers/qwen3_5/kernel_solutions/qwen3_5_residual_add_gemma_rms_norm.json @@ -17,9 +17,9 @@ "entry_point": "src/tilegym/transformers/qwen3_5/kernels/residual_add_rms_norm.py::residual_add_rms_norm_cutile", "language": "cuda-tile", "target_hardware": [ - "NVIDIA_B200", - "NVIDIA_GB300", - "NVIDIA_RTX_PRO_6000_BLACKWELL_SERVER_EDITION" + "SM100", + "SM103", + "SM120" ] } } diff --git a/src/tilegym/transformers/qwen3_5/kernel_solutions/qwen3_5_rms_norm_gated_silu.json b/src/tilegym/transformers/qwen3_5/kernel_solutions/qwen3_5_rms_norm_gated_silu.json index 4bdda5a..27e6ca6 100644 --- a/src/tilegym/transformers/qwen3_5/kernel_solutions/qwen3_5_rms_norm_gated_silu.json +++ b/src/tilegym/transformers/qwen3_5/kernel_solutions/qwen3_5_rms_norm_gated_silu.json @@ -17,9 +17,9 @@ "entry_point": "src/tilegym/transformers/qwen3_5/kernels/rms_norm_gated.py::rms_norm_gated_cutile", "language": "cuda-tile", "target_hardware": [ - "NVIDIA_B200", - "NVIDIA_GB300", - "NVIDIA_RTX_PRO_6000_BLACKWELL_SERVER_EDITION" + "SM100", + "SM103", + "SM120" ] } } diff --git a/src/tilegym/transformers/qwen3_5/kernel_solutions/qwen3_5_sigmoid_mul.json b/src/tilegym/transformers/qwen3_5/kernel_solutions/qwen3_5_sigmoid_mul.json index eb8440d..a4d5c5e 100644 --- a/src/tilegym/transformers/qwen3_5/kernel_solutions/qwen3_5_sigmoid_mul.json +++ b/src/tilegym/transformers/qwen3_5/kernel_solutions/qwen3_5_sigmoid_mul.json @@ -17,9 +17,9 @@ "entry_point": "src/tilegym/transformers/qwen3_5/kernels/sigmoid_mul.py::sigmoid_mul_cutile", "language": "cuda-tile", "target_hardware": [ - "NVIDIA_B200", - "NVIDIA_GB300", - "NVIDIA_RTX_PRO_6000_BLACKWELL_SERVER_EDITION" + "SM100", + "SM103", + "SM120" ] } } diff --git a/src/tilegym/transformers/qwen3_5/kernel_solutions/qwen3_5_silu_and_mul_separate.json b/src/tilegym/transformers/qwen3_5/kernel_solutions/qwen3_5_silu_and_mul_separate.json index 147f555..9e2321c 100644 --- a/src/tilegym/transformers/qwen3_5/kernel_solutions/qwen3_5_silu_and_mul_separate.json +++ b/src/tilegym/transformers/qwen3_5/kernel_solutions/qwen3_5_silu_and_mul_separate.json @@ -17,9 +17,9 @@ "entry_point": "src/tilegym/transformers/qwen3_5/kernels/silu_and_mul_separate.py::silu_and_mul_separate_cutile", "language": "cuda-tile", "target_hardware": [ - "NVIDIA_B200", - "NVIDIA_GB300", - "NVIDIA_RTX_PRO_6000_BLACKWELL_SERVER_EDITION" + "SM100", + "SM103", + "SM120" ] } } diff --git a/tests/kernel_inventory/__init__.py b/tests/kernel_inventory/__init__.py new file mode 100644 index 0000000..6c3e3d1 --- /dev/null +++ b/tests/kernel_inventory/__init__.py @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# SPDX-License-Identifier: MIT + +"""Kernel inventory test support.""" diff --git a/tests/transformers/conftest.py b/tests/kernel_inventory/conftest.py similarity index 60% rename from tests/transformers/conftest.py rename to tests/kernel_inventory/conftest.py index 674f945..6fb30c9 100644 --- a/tests/transformers/conftest.py +++ b/tests/kernel_inventory/conftest.py @@ -7,19 +7,19 @@ def pytest_addoption(parser): parser.addoption( - "--transformer-submodule", + "--kernel-submodule", action="store", default=None, help=( - "Limit transformer kernel Definition/Solution runtime checks to one " - "src/tilegym/transformers/ directory or module name." + "Limit kernel Definition/Solution runtime checks to one directory " + "under src/tilegym that contains kernel_definitions and kernel_solutions." ), ) def pytest_generate_tests(metafunc): if {"definition_path", "solution_path"}.issubset(metafunc.fixturenames): - from tests.transformers.kernel_runtime_utils import definition_solution_cases_for_submodule + from tests.kernel_inventory.kernel_runtime_utils import definition_solution_cases_for_submodule - cases = definition_solution_cases_for_submodule(metafunc.config.getoption("--transformer-submodule")) + cases = definition_solution_cases_for_submodule(metafunc.config.getoption("--kernel-submodule")) metafunc.parametrize(("definition_path", "solution_path"), cases) diff --git a/tests/kernel_inventory/kernel_runtime_utils.py b/tests/kernel_inventory/kernel_runtime_utils.py new file mode 100644 index 0000000..eea6031 --- /dev/null +++ b/tests/kernel_inventory/kernel_runtime_utils.py @@ -0,0 +1,627 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# SPDX-License-Identifier: MIT + +from __future__ import annotations + +import ast +import importlib.util +import inspect +import itertools +import os +import sys +import types +from contextlib import contextmanager +from pathlib import Path +from typing import Any + +import pytest + +# Correctness coverage should select a stable config before importing cuda.tile. +os.environ.setdefault("DISABLE_TUNE", "1") +os.environ.setdefault("TILEGYM_DISABLE_AUTOTUNE", "1") +os.environ.setdefault("TRITON_CACHE_DIR", "/tmp/tilegym-triton-cache") + +REPO_ROOT = Path(__file__).resolve().parents[2] +# Import inventory modules without running tilegym/__init__.py. Runtime tests +# import the individual solution modules directly from Solution.spec.entry_point. +_original_tilegym = sys.modules.get("tilegym") +if _original_tilegym is None: + tilegym_pkg = types.ModuleType("tilegym") + tilegym_pkg.__path__ = [str(REPO_ROOT / "src/tilegym")] + sys.modules["tilegym"] = tilegym_pkg +try: + from tilegym.kernel_inventory import iter_kernel_definition_paths + from tilegym.kernel_inventory import iter_solution_paths_for_definition + from tilegym.kernel_inventory import load_json + from tilegym.kernel_inventory import validate_definition + from tilegym.kernel_inventory import validate_solution + from tilegym.kernel_inventory import validate_solution_entry_point + from tilegym.kernel_inventory.return_contract import CAPTURE_RETURN_NAME + from tilegym.kernel_inventory.return_contract import instrument_reference_returns +finally: + if _original_tilegym is None: + sys.modules.pop("tilegym", None) + +DEFAULT_AXIS_VALUES = { + "N": 3, + "D": 64, + "H": 64, + "T": 5, +} +MUTATED_INPUTS_BY_DEFINITION = { + "olmo3_dual_rms_norm": ("q", "k"), + "olmoe_dual_rms_norm": ("q", "k"), + "qwen3_5_causal_conv1d_update_silu": ("conv_state",), +} + + +class _CapturedReferenceReturn: + """Reference value paired with the executed AST-derived output-name tree.""" + + def __init__(self, value: Any, contract: Any): + self.value = value + self.contract = contract + + +def all_definition_solution_cases() -> list[Any]: + """Return runtime parameter pairs for every discoverable inventory Definition.""" + return definition_solution_cases_for_submodule(None) + + +def definition_solution_cases_for_submodule(submodule: str | Path | None) -> list[Any]: + """Return runtime parameter pairs for one inventory submodule or the full catalog.""" + search_root = _kernel_submodule_root(submodule) + cases = [] + definition_paths = ( + iter_kernel_definition_paths(REPO_ROOT) + if search_root == REPO_ROOT + else sorted(search_root.glob("kernel_definitions/*.json")) + ) + for definition_path in definition_paths: + solution_paths = list(iter_solution_paths_for_definition(definition_path)) + if not solution_paths: + raise ValueError(f"Definition has no checked-in Solutions: {definition_path}") + for solution_path in solution_paths: + solution = load_json(solution_path) + cases.append(pytest.param(definition_path, solution_path, id=solution["name"])) + if search_root != REPO_ROOT and not cases: + raise ValueError(f"Kernel submodule has no Definition/Solution pairs: {search_root}") + return cases + + +def _kernel_submodule_root(submodule: str | Path | None) -> Path: + """Resolve an inventory-bearing submodule below ``src/tilegym``.""" + if submodule is None or str(submodule) == "": + return REPO_ROOT + + raw_path = Path(submodule) + if raw_path.is_absolute(): + candidates = (raw_path,) + else: + candidates = ( + Path.cwd() / raw_path, + REPO_ROOT / raw_path, + REPO_ROOT / "src/tilegym" / raw_path, + REPO_ROOT / "src/tilegym/transformers" / raw_path, + REPO_ROOT / "src/tilegym/suites" / raw_path, + ) + submodule_root = next((candidate for candidate in candidates if candidate.exists()), candidates[0]) + + submodule_root = submodule_root.resolve() + tilegym_root = (REPO_ROOT / "src/tilegym").resolve() + try: + submodule_root.relative_to(tilegym_root) + except ValueError as exc: + raise ValueError(f"Kernel submodule must live under {tilegym_root}: {submodule}") from exc + + if not submodule_root.is_dir(): + raise ValueError(f"Kernel submodule does not exist: {submodule}") + missing_directories = [ + directory + for directory in ("kernel_definitions", "kernel_solutions") + if not (submodule_root / directory).is_dir() + ] + if missing_directories: + raise ValueError( + f"Kernel submodule must contain kernel_definitions and kernel_solutions: {submodule_root} " + f"(missing {', '.join(missing_directories)})" + ) + return submodule_root + + +@contextmanager +def _isolated_solution_modules(): + """Temporarily isolate TileGym and FlashInfer imports used by Solution modules.""" + prefixes = ("tilegym", "flashinfer") + saved_modules = { + name: module + for name, module in sys.modules.items() + if any(name == prefix or name.startswith(f"{prefix}.") for prefix in prefixes) + } + for name in saved_modules: + sys.modules.pop(name, None) + + tilegym_pkg = types.ModuleType("tilegym") + tilegym_pkg.__path__ = [str(REPO_ROOT / "src/tilegym")] + sys.modules["tilegym"] = tilegym_pkg + + # Importing tilegym.backend probes the optional FlashInfer backend. The + # inventory harness does not validate it, so avoid its JIT initialization. + flashinfer_pkg = types.ModuleType("flashinfer") + flashinfer_pkg.single_prefill_with_kv_cache = object() + sys.modules["flashinfer"] = flashinfer_pkg + try: + yield + finally: + for name in tuple(sys.modules): + if any(name == prefix or name.startswith(f"{prefix}.") for prefix in prefixes): + sys.modules.pop(name, None) + sys.modules.update(saved_modules) + + +def run_definition_solution_runtime(definition_path: Path, solution_path: Path) -> None: + """Validate one Solution against its Definition reference or error contract.""" + with _isolated_solution_modules(): + _run_definition_solution_runtime(definition_path, solution_path) + + +def _run_definition_solution_runtime(definition_path: Path, solution_path: Path) -> None: + """Run one Definition/Solution check with package imports isolated.""" + torch = pytest.importorskip("torch") + pytest.importorskip("flashinfer_bench") + pytest.importorskip("cuda.tile") + + definition = load_json(definition_path) + solution = load_json(solution_path) + validate_definition(definition) + validate_solution(solution, repo_root=REPO_ROOT) + validate_solution_entry_point(solution, repo_root=REPO_ROOT) + reference_fn = _load_reference(definition, definition_path) + solution_fn = _load_solution_entry(solution) + _assert_matching_entry_signatures(definition, reference_fn, solution, solution_fn) + + if not torch.cuda.is_available(): + pytest.skip("CUDA is required for kernel inventory runtime checks") + _skip_if_solution_does_not_target_current_compute_capability(solution, torch) + + device = torch.device("cuda") + torch.manual_seed(2026) + axes = _axis_values(definition) + base_inputs = _make_inputs(definition, torch, device, axes) + for boolean_assignment in _boolean_branch_assignments(definition, axes, base_inputs): + inputs = dict(base_inputs) + inputs.update(boolean_assignment) + _run_runtime_branch(definition, reference_fn, solution_fn, inputs, axes, torch) + + +def _run_runtime_branch( + definition: dict[str, Any], + reference_fn: Any, + solution_fn: Any, + inputs: dict[str, Any], + axes: dict[str, int], + torch: Any, +) -> None: + """Compare reference and Solution for one concrete Boolean branch.""" + reference_inputs = {name: _clone_value(value) for name, value in inputs.items()} + solution_inputs = {name: _clone_value(value) for name, value in inputs.items()} + branch = _boolean_branch_label(inputs) + + if "runtime:unsupported" in definition.get("tags", []): + _assert_unsupported_solution_matches_reference( + definition, + reference_fn, + reference_inputs, + solution_fn, + solution_inputs, + ) + return + + reference_result = _call_entry_strictly(reference_fn, reference_inputs, "Definition.reference") + solution_result = _call_entry_strictly(solution_fn, solution_inputs, "Solution entry point") + torch.cuda.synchronize() + + assert isinstance(reference_result, _CapturedReferenceReturn), ( + f"{definition['name']} {branch}: Definition.reference return was not instrumented" + ) + _assert_return_contract( + solution_result, + reference_result.value, + reference_result.contract, + definition["outputs"], + axes, + torch, + f"{definition['name']} {branch}", + ) + + for name in MUTATED_INPUTS_BY_DEFINITION.get(definition["name"], ()): + torch.testing.assert_close( + solution_inputs[name], + reference_inputs[name], + rtol=2e-2, + atol=2e-2, + msg=lambda msg: f"{definition['name']} {branch} mutated input {name} mismatch\n{msg}", + ) + + +def _assert_unsupported_solution_matches_reference( + definition: dict[str, Any], + reference_fn: Any, + reference_inputs: dict[str, Any], + solution_fn: Any, + solution_inputs: dict[str, Any], +) -> None: + """Verify an intentionally unsupported Solution raises the reference error type.""" + reference_error = _capture_runtime_error( + lambda: _call_entry_strictly(reference_fn, reference_inputs, "Definition.reference"), + f"{definition['name']} reference", + ) + solution_error = _capture_runtime_error( + lambda: _call_entry_strictly(solution_fn, solution_inputs, "Solution entry point"), + f"{definition['name']} Solution", + ) + assert type(solution_error) is type(reference_error), ( + f"{definition['name']}: deprecated Solution raised {type(solution_error).__name__}; " + f"reference raised {type(reference_error).__name__}" + ) + + +def _capture_runtime_error(call: Any, label: str) -> Exception: + """Run ``call`` and return its required exception.""" + try: + call() + except Exception as exc: + return exc + pytest.fail(f"{label} must raise because the Definition is tagged runtime:unsupported") + + +def _axis_values(definition: dict[str, Any]) -> dict[str, int]: + """Build deterministic concrete axis values for a Definition runtime case.""" + values = {name: axis["value"] for name, axis in definition["axes"].items() if axis.get("type") == "const"} + for name, axis in definition["axes"].items(): + if axis.get("type") == "var": + defaults = DEFAULT_AXIS_VALUES + values[name] = defaults.get(name, 4) + if "T_padded" in values and "T" in values and "K" in values: + values["T_padded"] = values["T"] + values["K"] - 1 + return values + + +def _make_inputs(definition: dict[str, Any], torch: Any, device: Any, axes: dict[str, int]) -> dict[str, Any]: + """Create seeded representative inputs that conform to a Definition.""" + inputs = {} + for name, spec in definition["inputs"].items(): + inputs[name] = _make_input(name, spec, axes, torch, device) + _satisfy_boolean_constraints(definition, inputs, axes) + return inputs + + +def _boolean_branch_assignments( + definition: dict[str, Any], + axes: dict[str, int] | None = None, + inputs: dict[str, Any] | None = None, +) -> list[dict[str, bool]]: + """Enumerate scalar Boolean assignments satisfying applicable constraints.""" + boolean_inputs = [ + name for name, spec in definition["inputs"].items() if spec["shape"] is None and spec["dtype"] == "bool" + ] + if not boolean_inputs: + return [{}] + constraints = [ + constraint + for constraint in definition.get("constraints", ()) + if set(boolean_inputs) & _constraint_names(constraint) + ] + assignments = [] + for values in itertools.product((False, True), repeat=len(boolean_inputs)): + assignment = dict(zip(boolean_inputs, values, strict=True)) + candidate = dict(inputs or {}) + candidate.update(assignment) + if _constraints_hold(constraints, candidate, axes or {}): + assignments.append(assignment) + if not assignments: + pytest.fail(f"{definition['name']}: no Boolean input assignment satisfies Definition.constraints") + return assignments + + +def _boolean_branch_label(inputs: dict[str, Any]) -> str: + """Describe the concrete scalar Boolean branch in assertion messages.""" + values = [f"{name}={value}" for name, value in inputs.items() if isinstance(value, bool)] + return f"[{', '.join(values)}]" if values else "[no Boolean inputs]" + + +def _satisfy_boolean_constraints(definition: dict[str, Any], inputs: dict[str, Any], axes: dict[str, int]) -> None: + """Choose scalar Boolean inputs that satisfy evaluable Definition constraints.""" + inputs.update(_boolean_branch_assignments(definition, axes, inputs)[0]) + + +def _constraint_names(constraint: str) -> set[str]: + """Return variable names in one valid Python constraint expression.""" + try: + tree = ast.parse(constraint, mode="eval") + except SyntaxError: + return set() + return {node.id for node in ast.walk(tree) if isinstance(node, ast.Name)} + + +def _constraints_hold(constraints: Any, inputs: dict[str, Any], axes: dict[str, int]) -> bool: + """Evaluate constraints that depend only on axes and Python scalar inputs.""" + context = dict(axes) + context.update({name: value for name, value in inputs.items() if isinstance(value, bool | float | int)}) + for constraint in constraints: + try: + result = eval(compile(constraint, "", "eval"), {"__builtins__": {}}, context) + except (NameError, SyntaxError): + continue + if not result: + return False + return True + + +def _make_input(name: str, spec: dict[str, Any], axes: dict[str, int], torch: Any, device: Any) -> Any: + """Create one scalar or tensor input from its schema specification.""" + dtype = spec["dtype"] + shape_spec = spec["shape"] + if shape_spec is None: + if name == "scale": + return axes.get("K", axes.get("D", 64)) ** -0.5 + if dtype == "bool": + return False + if name == "eps": + return 1e-6 + if name == "offset": + return 1.0 + if name == "seq_len": + return axes["T"] + if dtype.startswith("int"): + return 1 + return 1.0 + + shape = tuple(axes[axis] for axis in shape_spec) + torch_dtype = _torch_dtype(dtype, torch) + if dtype == "bool": + return torch.randint(0, 2, shape, device=device, dtype=torch.bool) + if dtype.startswith("int"): + return torch.randint(-3, 4, shape, device=device, dtype=torch_dtype) + + base = 0.1 * torch.randn(shape, device=device, dtype=torch.float32) + if "weight" in name: + base = 1.0 + base + if name == "A_log": + base = -base.abs() + return base.to(torch_dtype).contiguous() + + +def _torch_dtype(dtype: str, torch: Any) -> Any: + """Resolve an inventory dtype string to its torch dtype.""" + return { + "float32": torch.float32, + "float16": torch.float16, + "bfloat16": torch.bfloat16, + "int64": torch.int64, + "int32": torch.int32, + "int16": torch.int16, + "int8": torch.int8, + "bool": torch.bool, + }[dtype] + + +def _assert_output_matches_spec(value: Any, spec: dict[str, Any], axes: dict[str, int], torch: Any, label: str) -> None: + """Check one concrete tensor output against its Definition TensorSpec.""" + expected_shape = () if spec["shape"] is None else tuple(axes[axis] for axis in spec["shape"]) + assert tuple(value.shape) == expected_shape, ( + f"{label} has shape {tuple(value.shape)}; Definition declares {expected_shape}" + ) + expected_dtype = _torch_dtype(spec["dtype"], torch) + assert value.dtype == expected_dtype, f"{label} has dtype {value.dtype}; Definition declares {expected_dtype}" + + +def _assert_return_contract( + actual: Any, + expected: Any, + contract: Any, + output_specs: dict[str, dict[str, Any]], + axes: dict[str, int], + torch: Any, + label: str, +) -> None: + """Recursively compare values using the executed reference return-name tree.""" + if contract is None: + assert actual is expected is None, f"{label}: expected matching None return values" + return + if isinstance(contract, tuple): + assert isinstance(actual, tuple) and isinstance(expected, tuple), ( + f"{label}: reference and Solution must both return a tuple" + ) + assert len(actual) == len(expected) == len(contract), ( + f"{label}: nested return arity does not match reference contract" + ) + for index, (actual_item, expected_item, item_contract) in enumerate( + zip(actual, expected, contract, strict=True) + ): + _assert_return_contract( + actual_item, + expected_item, + item_contract, + output_specs, + axes, + torch, + f"{label}[{index}]", + ) + return + + assert isinstance(contract, str) and contract in output_specs, f"{label}: unknown output contract {contract!r}" + if actual is None or expected is None: + assert actual is expected is None, f"{label} output {contract}: tensor/None mismatch" + return + spec = output_specs[contract] + _assert_output_matches_spec(actual, spec, axes, torch, f"Solution entry point output {contract}") + _assert_output_matches_spec(expected, spec, axes, torch, f"Definition.reference output {contract}") + torch.testing.assert_close( + actual, + expected, + rtol=2e-2, + atol=2e-2, + msg=lambda msg: f"{label} output {contract} mismatch\n{msg}", + ) + + +def _skip_if_solution_does_not_target_current_compute_capability(solution: dict[str, Any], torch: Any) -> None: + """Skip only when a Solution explicitly excludes the active GPU capability.""" + target_hardware = solution["spec"].get("target_hardware", []) + current_hardware = _current_compute_capability_label(torch) + if target_hardware and current_hardware not in target_hardware: + pytest.skip( + f"Solution targets {target_hardware}; current compute capability is {current_hardware} " + f"({torch.cuda.get_device_name(0)})" + ) + + +def _current_compute_capability_label(torch: Any) -> str: + """Return the active CUDA compute capability as an ``SM`` label.""" + major, minor = torch.cuda.get_device_capability(0) + return f"SM{major}{minor}" + + +def _load_reference(definition: dict[str, Any], definition_path: Path) -> Any: + """Compile an AST-instrumented Definition reference ``run`` callable.""" + module = instrument_reference_returns( + definition["reference"], + list(definition["outputs"]), + allow_no_return="runtime:unsupported" in definition.get("tags", []), + ) + namespace: dict[str, Any] = { + CAPTURE_RETURN_NAME: lambda value, contract: _CapturedReferenceReturn(value, contract), + } + exec(compile(module, str(definition_path), "exec"), namespace) + return namespace["run"] + + +def _load_solution_entry(solution: dict[str, Any]) -> Any: + """Load the callable named by a Solution entry point without package side effects.""" + file_path, symbol = solution["spec"]["entry_point"].split("::", 1) + source_path = REPO_ROOT / file_path + module_name = _solution_module_name(source_path) + _ensure_solution_parent_packages(source_path, module_name) + spec = importlib.util.spec_from_file_location(module_name, source_path) + assert spec is not None + assert spec.loader is not None + module = importlib.util.module_from_spec(spec) + sys.modules[module_name] = module + try: + spec.loader.exec_module(module) + except Exception: + sys.modules.pop(module_name, None) + raise + return getattr(module, symbol) + + +def _solution_module_name(source_path: Path) -> str: + """Return the package-qualified name for a checked-in TileGym module.""" + relative_path = source_path.relative_to(REPO_ROOT / "src").with_suffix("") + return ".".join(relative_path.parts) + + +def _ensure_solution_parent_packages(source_path: Path, module_name: str) -> None: + """Install lightweight package parents so Solution modules can use relative imports. + + Importing a backend package can execute its ``__init__.py`` and import every + implementation. Runtime validation deliberately loads one entry point at a + time, so it constructs only the package parents needed for Python's + relative-import resolver. + """ + source_root = REPO_ROOT / "src" + relative_path = source_path.relative_to(source_root) + package_parts = module_name.split(".")[:-1] + for depth in range(1, len(package_parts) + 1): + package_name = ".".join(package_parts[:depth]) + if package_name in sys.modules: + continue + package = types.ModuleType(package_name) + package.__path__ = [str(source_root.joinpath(*relative_path.parts[:depth]))] + package.__package__ = package_name + sys.modules[package_name] = package + + +def _assert_matching_entry_signatures( + definition: dict[str, Any], reference_fn: Any, solution: dict[str, Any], solution_fn: Any +) -> None: + """Require Definition reference and Solution entry points to expose one call contract.""" + reference_signature = _call_signature(reference_fn) + solution_signature = _call_signature(solution_fn) + assert reference_signature == solution_signature, ( + f"{definition['name']}: Definition.reference run signature {reference_signature} does not match " + f"Solution entry point {solution['spec']['entry_point']} signature {solution_signature}" + ) + + +def _call_signature(callable_: Any) -> inspect.Signature: + """Return a callable's public invocation signature without type-only annotations.""" + signature = inspect.signature(callable_) + return signature.replace( + parameters=[ + parameter.replace(annotation=inspect.Parameter.empty) for parameter in signature.parameters.values() + ], + return_annotation=inspect.Signature.empty, + ) + + +def _call_entry_strictly(entry_fn: Any, inputs: dict[str, Any], label: str) -> Any: + """Call an entry with required parameters positional and defaulted parameters by keyword.""" + signature = _call_signature(entry_fn) + positional_args = [] + keyword_args = {} + accepted_names = set() + accepts_arbitrary_keywords = False + + for name, parameter in signature.parameters.items(): + if parameter.kind is inspect.Parameter.VAR_KEYWORD: + accepts_arbitrary_keywords = True + continue + if parameter.kind is inspect.Parameter.VAR_POSITIONAL: + continue + + accepted_names.add(name) + if name not in inputs: + if parameter.default is inspect.Parameter.empty: + raise TypeError(f"{label} requires Definition input '{name}'") + continue + + if parameter.kind is inspect.Parameter.POSITIONAL_ONLY: + if parameter.default is not inspect.Parameter.empty: + raise TypeError(f"{label} has an optional positional-only parameter '{name}', which is unsupported") + positional_args.append(inputs[name]) + elif parameter.kind is inspect.Parameter.POSITIONAL_OR_KEYWORD: + if parameter.default is inspect.Parameter.empty: + positional_args.append(inputs[name]) + else: + keyword_args[name] = inputs[name] + elif parameter.kind is inspect.Parameter.KEYWORD_ONLY: + keyword_args[name] = inputs[name] + + unexpected_names = sorted(set(inputs) - accepted_names) + if accepts_arbitrary_keywords: + keyword_args.update({name: inputs[name] for name in unexpected_names}) + unexpected_names = [] + if unexpected_names: + raise TypeError(f"{label} does not accept Definition inputs: {unexpected_names}") + return entry_fn(*positional_args, **keyword_args) + + +def _as_outputs(value: Any) -> tuple[Any, ...]: + """Normalize a return value to a tuple without changing its declared arity.""" + if isinstance(value, tuple): + return value + if isinstance(value, list): + return tuple(value) + if value is None: + return () + return (value,) + + +def _clone_value(value: Any) -> Any: + """Clone tensors while preserving scalar values for independent invocations.""" + if hasattr(value, "clone"): + return value.clone() + return value diff --git a/tests/kernel_inventory/test_kernel_definition_solution_runtime.py b/tests/kernel_inventory/test_kernel_definition_solution_runtime.py new file mode 100644 index 0000000..033434c --- /dev/null +++ b/tests/kernel_inventory/test_kernel_definition_solution_runtime.py @@ -0,0 +1,227 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# SPDX-License-Identifier: MIT + +import functools +import sys +import types + +import pytest + +from tests.kernel_inventory.kernel_runtime_utils import _as_outputs +from tests.kernel_inventory.kernel_runtime_utils import _assert_matching_entry_signatures +from tests.kernel_inventory.kernel_runtime_utils import _assert_return_contract +from tests.kernel_inventory.kernel_runtime_utils import _boolean_branch_assignments +from tests.kernel_inventory.kernel_runtime_utils import _call_entry_strictly +from tests.kernel_inventory.kernel_runtime_utils import _current_compute_capability_label +from tests.kernel_inventory.kernel_runtime_utils import _isolated_solution_modules +from tests.kernel_inventory.kernel_runtime_utils import _satisfy_boolean_constraints +from tests.kernel_inventory.kernel_runtime_utils import _skip_if_solution_does_not_target_current_compute_capability +from tests.kernel_inventory.kernel_runtime_utils import run_definition_solution_runtime + + +def test_kernel_definition_solution_runtime(definition_path, solution_path): + run_definition_solution_runtime(definition_path, solution_path) + + +class _FakeCuda: + @staticmethod + def get_device_name(index): + assert index == 0 + return "NVIDIA GB300" + + @staticmethod + def get_device_capability(index): + assert index == 0 + return 10, 3 + + +class _FakeTorch: + cuda = _FakeCuda() + + +def test_runtime_derives_target_label_from_cuda_compute_capability(): + assert _current_compute_capability_label(_FakeTorch) == "SM103" + + +def test_runtime_skips_solutions_that_do_not_target_current_compute_capability(): + solution = { + "spec": { + "target_hardware": ["SM100"], + } + } + with pytest.raises(pytest.skip.Exception, match="current compute capability is SM103"): + _skip_if_solution_does_not_target_current_compute_capability(solution, _FakeTorch) + + +def test_runtime_accepts_solutions_that_target_current_compute_capability(): + solution = { + "spec": { + "target_hardware": ["SM100", "SM103"], + } + } + _skip_if_solution_does_not_target_current_compute_capability(solution, _FakeTorch) + + +def test_runtime_rejects_mismatched_reference_and_solution_signatures(): + def reference(q, scale=None): + return q + + def solution(q, initial_state=None, scale=None): + return q + + definition = {"name": "test_definition"} + schema = {"spec": {"entry_point": "test.py::solution"}} + with pytest.raises(AssertionError, match="does not match Solution entry point"): + _assert_matching_entry_signatures(definition, reference, schema, solution) + + +def test_runtime_rejects_reference_and_solution_default_mismatch(): + def reference(q, scale): + return q + + def solution(q, scale=None): + return q + + definition = {"name": "test_definition"} + schema = {"spec": {"entry_point": "test.py::solution"}} + with pytest.raises(AssertionError, match="does not match Solution entry point"): + _assert_matching_entry_signatures(definition, reference, schema, solution) + + +def test_runtime_calls_required_parameters_positionally_and_optional_parameters_by_keyword(): + calls = [] + + def entry(required, optional=None): + return required, optional + + @functools.wraps(entry) + def recorded_entry(*args, **kwargs): + calls.append((args, kwargs)) + return entry(*args, **kwargs) + + assert _call_entry_strictly(recorded_entry, {"required": 1, "optional": 2}, "test entry") == (1, 2) + assert calls == [((1,), {"optional": 2})] + + +def test_runtime_preserves_none_outputs_in_return_arity(): + output = object() + assert _as_outputs((output, None)) == (output, None) + + +def test_runtime_uses_boolean_constraints_to_select_a_schema_case(): + definition = { + "name": "constrained_boolean_case", + "inputs": { + "layout": {"shape": None, "dtype": "bool"}, + "emit": {"shape": None, "dtype": "bool"}, + }, + "constraints": ["layout is True", "emit is True"], + } + inputs = {"layout": False, "emit": False} + + _satisfy_boolean_constraints(definition, inputs, {}) + + assert inputs == {"layout": True, "emit": True} + + +def test_runtime_enumerates_every_unconstrained_boolean_branch(): + definition = { + "inputs": { + "layout": {"shape": None, "dtype": "bool"}, + "emit": {"shape": None, "dtype": "bool"}, + }, + "constraints": [], + } + + assert _boolean_branch_assignments(definition) == [ + {"layout": False, "emit": False}, + {"layout": False, "emit": True}, + {"layout": True, "emit": False}, + {"layout": True, "emit": True}, + ] + + +def test_runtime_enumerates_boolean_branches_allowed_by_constraints(): + definition = { + "name": "constrained_boolean_branches", + "inputs": { + "layout": {"shape": None, "dtype": "bool"}, + "emit": {"shape": None, "dtype": "bool"}, + }, + "constraints": ["layout is True"], + } + + assert _boolean_branch_assignments(definition) == [ + {"layout": True, "emit": False}, + {"layout": True, "emit": True}, + ] + + +def test_runtime_ignores_unrelated_constraints_when_enumerating_booleans(): + definition = { + "name": "unrelated_constraints", + "inputs": { + "layout": {"shape": None, "dtype": "bool"}, + "emit": {"shape": None, "dtype": "bool"}, + }, + "constraints": [ + "H % 2 == 0", + "layout selects a documented implementation branch", + ], + } + + assert _boolean_branch_assignments(definition, {"H": 2}) == [ + {"layout": False, "emit": False}, + {"layout": False, "emit": True}, + {"layout": True, "emit": False}, + {"layout": True, "emit": True}, + ] + + +def test_runtime_compares_named_nested_returns_and_matching_none_values(): + torch = pytest.importorskip("torch") + axes = {"B": 1, "ONE": 1, "H": 2, "K": 4, "V": 3} + output_specs = { + "output": {"shape": ["B", "H", "V"], "dtype": "bfloat16"}, + "final_state": {"shape": ["B", "H", "K", "V"], "dtype": "float32"}, + "z_state": {"shape": ["B", "ONE", "H", "K"], "dtype": "bfloat16"}, + } + output = torch.randn(1, 2, 3, dtype=torch.bfloat16) + z_state = torch.randn(1, 1, 2, 4, dtype=torch.bfloat16) + + _assert_return_contract( + (output.clone(), (None, z_state.clone())), + (output, (None, z_state)), + ("output", ("final_state", "z_state")), + output_specs, + axes, + torch, + "test definition", + ) + + +def test_solution_module_isolation_restores_existing_packages(): + original_tilegym = sys.modules.get("tilegym") + original_flashinfer = sys.modules.get("flashinfer") + tilegym_sentinel = types.ModuleType("tilegym") + flashinfer_sentinel = types.ModuleType("flashinfer") + sys.modules["tilegym"] = tilegym_sentinel + sys.modules["flashinfer"] = flashinfer_sentinel + try: + with _isolated_solution_modules(): + assert sys.modules["tilegym"] is not tilegym_sentinel + assert sys.modules["flashinfer"] is not flashinfer_sentinel + sys.modules["tilegym.generated"] = types.ModuleType("tilegym.generated") + assert sys.modules["tilegym"] is tilegym_sentinel + assert sys.modules["flashinfer"] is flashinfer_sentinel + assert "tilegym.generated" not in sys.modules + finally: + if original_tilegym is None: + sys.modules.pop("tilegym", None) + else: + sys.modules["tilegym"] = original_tilegym + if original_flashinfer is None: + sys.modules.pop("flashinfer", None) + else: + sys.modules["flashinfer"] = original_flashinfer diff --git a/tests/transformers/test_kernel_inventory.py b/tests/kernel_inventory/test_kernel_inventory.py similarity index 75% rename from tests/transformers/test_kernel_inventory.py rename to tests/kernel_inventory/test_kernel_inventory.py index 3098b58..716060b 100644 --- a/tests/transformers/test_kernel_inventory.py +++ b/tests/kernel_inventory/test_kernel_inventory.py @@ -15,31 +15,32 @@ REPO_ROOT = Path(__file__).resolve().parents[2] # Import inventory modules without running tilegym/__init__.py. The top-level # package initializes CUDA/Torch backends, which is unrelated to JSON schema checks. -tilegym_pkg = types.ModuleType("tilegym") -tilegym_pkg.__path__ = [str(REPO_ROOT / "src/tilegym")] -sys.modules.setdefault("tilegym", tilegym_pkg) -transformers_pkg = types.ModuleType("tilegym.transformers") -transformers_pkg.__path__ = [str(REPO_ROOT / "src/tilegym/transformers")] -sys.modules.setdefault("tilegym.transformers", transformers_pkg) - -from tilegym.transformers.inventory_common import SourceContractError -from tilegym.transformers.inventory_common import validate_reference_source_contract -from tilegym.transformers.kernel_inventory import KernelInventoryError -from tilegym.transformers.kernel_inventory import iter_kernel_definition_paths -from tilegym.transformers.kernel_inventory import iter_kernel_python_paths -from tilegym.transformers.kernel_inventory import iter_kernel_solution_paths -from tilegym.transformers.kernel_inventory import load_json -from tilegym.transformers.kernel_inventory import materialize_solution_sources -from tilegym.transformers.kernel_inventory import normalize_solution_source_paths -from tilegym.transformers.kernel_inventory import validate_definition -from tilegym.transformers.kernel_inventory import validate_solution -from tilegym.transformers.kernel_inventory import validate_solution_entry_point - -FLOAT32_TENSOR_ALLOWLIST = { - ("qwen3_5_gdr_preprocess", "inputs", "A_log"), - ("qwen3_5_gdr_preprocess", "inputs", "dt_bias"), - ("qwen3_5_gdr_preprocess", "outputs", "g"), -} +_original_tilegym = sys.modules.get("tilegym") +if _original_tilegym is None: + tilegym_pkg = types.ModuleType("tilegym") + tilegym_pkg.__path__ = [str(REPO_ROOT / "src/tilegym")] + sys.modules["tilegym"] = tilegym_pkg +try: + from tilegym.kernel_inventory import KernelInventoryError + from tilegym.kernel_inventory import iter_kernel_definition_paths + from tilegym.kernel_inventory import iter_kernel_python_paths + from tilegym.kernel_inventory import iter_kernel_solution_paths + from tilegym.kernel_inventory import load_json + from tilegym.kernel_inventory import materialize_solution_sources + from tilegym.kernel_inventory import normalize_solution_source_paths + from tilegym.kernel_inventory import validate_definition + from tilegym.kernel_inventory import validate_solution + from tilegym.kernel_inventory import validate_solution_entry_point + from tilegym.kernel_inventory.generation import make_solution + from tilegym.kernel_inventory.generation import materialize_solution_for_fib + from tilegym.kernel_inventory.return_contract import CAPTURE_RETURN_NAME + from tilegym.kernel_inventory.return_contract import ReturnContractError + from tilegym.kernel_inventory.return_contract import instrument_reference_returns + from tilegym.kernel_inventory.source_contract import SourceContractError + from tilegym.kernel_inventory.source_contract import validate_reference_source_contract +finally: + if _original_tilegym is None: + sys.modules.pop("tilegym", None) def _definition(): @@ -78,7 +79,7 @@ def _solution(): "author": "tilegym-agent", "spec": { "language": "cuda-tile", - "target_hardware": ["NVIDIA_B200"], + "target_hardware": ["SM100"], "entry_point": "src/tilegym/transformers/test_model/kernels/rmsnorm_d128.py::run", "destination_passing_style": False, "dependencies": ["torch", "cuda-tile"], @@ -120,13 +121,11 @@ def _assert_definition_reference_contract(path: Path, definition: dict): assert len(run_nodes) == 1, f"{path}: Definition.reference must define exactly one global run function" run_args = run_nodes[0].args - assert run_args.vararg is None and run_args.kwarg is None, ( - f"{path}: Definition.reference run function must not use *args or **kwargs" - ) - arg_names = [arg.arg for arg in (*run_args.posonlyargs, *run_args.args, *run_args.kwonlyargs)] - assert arg_names == list(definition["inputs"]), ( - f"{path}: Definition.reference run arguments {arg_names} must match Definition.inputs " - f"{list(definition['inputs'])}" + assert run_args.vararg is None, f"{path}: Definition.reference run function must not use *args" + parameter_names = [arg.arg for arg in (*run_args.posonlyargs, *run_args.args, *run_args.kwonlyargs)] + input_names = list(definition["inputs"]) + assert run_args.kwarg is not None or set(input_names) <= set(parameter_names), ( + f"{path}: Definition.inputs must be accepted by Definition.reference run" ) @@ -195,19 +194,73 @@ def test_validate_definition_requires_precise_source_permalink(): validate_definition(definition) -def test_validate_definition_requires_run_args_to_match_inputs(): +def test_validate_definition_requires_inputs_for_required_run_args(): definition = _definition() definition["reference"] = ( "# Source: https://github.com/huggingface/transformers/blob/" "0123456789abcdef0123456789abcdef01234567/src/transformers/models/test/modeling_test.py#L1-L2\n" "import torch\n\n" - "def run(weight, input, eps):\n" + "def run(input, weight, eps, required):\n" " return input * weight" ) - with pytest.raises(KernelInventoryError, match="must match Definition.inputs"): + with pytest.raises(KernelInventoryError, match="must include every required"): validate_definition(definition) +def test_validate_definition_accepts_optional_run_args_omitted_from_inputs(): + definition = _definition() + definition["reference"] = ( + "# Source: https://github.com/huggingface/transformers/blob/" + "0123456789abcdef0123456789abcdef01234567/src/transformers/models/test/modeling_test.py#L1-L2\n" + "import torch\n\n" + "def run(input, weight, eps, optional=None, **kwargs):\n" + " return input * weight" + ) + validate_definition(definition) + + +def test_reference_return_contract_tracks_conditional_nested_ssa_names(): + reference = """ +def run(output, final_state, z_state, normalize, emit): + if normalize: + return output, (final_state if emit else None, z_state) + return output, final_state if emit else None +""" + module = instrument_reference_returns(reference, ("output", "final_state", "z_state")) + namespace = {CAPTURE_RETURN_NAME: lambda value, contract: (value, contract)} + exec(compile(module, "", "exec"), namespace) + + assert namespace["run"](1, 2, 3, False, False) == ((1, None), ("output", None)) + assert namespace["run"](1, 2, 3, False, True) == ((1, 2), ("output", "final_state")) + assert namespace["run"](1, 2, 3, True, False) == ( + (1, (None, 3)), + ("output", (None, "z_state")), + ) + assert namespace["run"](1, 2, 3, True, True) == ( + (1, (2, 3)), + ("output", ("final_state", "z_state")), + ) + + +def test_reference_return_contract_requires_ssa_names_for_extra_outputs(): + reference = """ +def run(output, state, z): + return output, (state, z) +""" + with pytest.raises(ReturnContractError, match="SSA variable named after a Definition output"): + instrument_reference_returns(reference, ("output", "final_state", "z_state")) + + +def test_reference_return_contract_ignores_nested_helper_returns(): + reference = """ +def run(output): + def helper(): + return None + return output +""" + instrument_reference_returns(reference, ("output",)) + + def test_validate_solution_accepts_path_only_sources(tmp_path): source_path = tmp_path / "src/tilegym/transformers/test_model/kernels/rmsnorm_d128.py" source_path.parent.mkdir(parents=True) @@ -252,8 +305,6 @@ def test_materialize_solution_for_fib_accepts_path_only_sources(tmp_path): source_path.parent.mkdir(parents=True) source_path.write_text("def run(input, weight, eps):\n return input\n", encoding="utf-8") - from tilegym.transformers.inventory_generation import materialize_solution_for_fib - fib_solution = materialize_solution_for_fib(_solution(), repo_root=tmp_path) assert fib_solution.spec.language == "python" assert fib_solution.sources[0].path == "src/tilegym/transformers/test_model/kernels/rmsnorm_d128.py" @@ -265,8 +316,6 @@ def test_make_solution_accepts_single_source_string(tmp_path): source_path.parent.mkdir(parents=True) source_path.write_text("def run(input, weight, eps):\n return input\n", encoding="utf-8") - from tilegym.transformers.inventory_generation import make_solution - source = "src/tilegym/transformers/test_model/kernels/rmsnorm_d128.py" solution = make_solution( name="rmsnorm_d128_cutile", @@ -285,6 +334,13 @@ def test_solution_rejects_missing_source_path(tmp_path): validate_solution(_solution(), repo_root=tmp_path) +def test_solution_requires_compute_capability_target_hardware(): + solution = _solution() + solution["spec"]["target_hardware"] = ["NVIDIA_B200"] + with pytest.raises(KernelInventoryError, match="SM"): + validate_solution(solution) + + def test_solution_rejects_source_path_outside_repo(tmp_path): source_path = tmp_path / "src/tilegym/transformers/test_model/kernels/rmsnorm_d128.py" source_path.parent.mkdir(parents=True) @@ -348,7 +404,6 @@ def test_all_current_kernel_definitions_validate(): for path in iter_kernel_definition_paths(REPO_ROOT): definition = load_json(path) assert path.stem == definition["name"], f"{path}: Definition filename must match Definition.name" - _assert_tensor_dtypes_match_model_precision(path, definition) validate_definition(definition) _assert_definition_reference_contract(path, definition) @@ -384,33 +439,21 @@ def test_kernel_definition_solution_catalog_is_complete(): assert not missing_solutions, f"Definitions without a matching Solution: {missing_solutions}" -def test_kernel_solution_sources_stay_in_dedicated_kernel_modules(): +def test_transformer_solution_sources_stay_in_dedicated_kernel_modules(): for path in iter_kernel_solution_paths(REPO_ROOT): solution = load_json(path) source_paths = normalize_solution_source_paths(solution) entry_path = solution["spec"]["entry_point"].split("::", 1)[0] + if not _is_transformer_kernel_path(entry_path): + continue assert entry_path in source_paths, f"{path}: Solution entry point file must be listed in sources.path" assert _is_transformer_kernel_path(entry_path), ( - f"{path}: Solution entry point must live under src/tilegym/transformers//kernels/" + f"{path}: Solution entry point must live in a transformer kernel module" ) for source_path in source_paths: assert _is_transformer_kernel_path(source_path), ( - f"{path}: Solution source path must live under src/tilegym/transformers//kernels/" - ) - - -def _assert_tensor_dtypes_match_model_precision(path: Path, definition: dict): - definition_name = definition["name"] - for section in ("inputs", "outputs"): - for name, spec in definition[section].items(): - if spec["shape"] is None or spec["dtype"] != "float32": - continue - key = (definition_name, section, name) - assert key in FLOAT32_TENSOR_ALLOWLIST, ( - f"{path}: tensor {section}.{name} uses float32. Model activations/weights should keep the " - "model dtype unless the upstream model/kernel semantics require float32; add a narrow " - "allowlist entry for verified float32 tensors." + f"{path}: Solution source path must live in a transformer kernel module" ) diff --git a/tests/transformers/kernel_runtime_utils.py b/tests/transformers/kernel_runtime_utils.py deleted file mode 100644 index 3d5ccc0..0000000 --- a/tests/transformers/kernel_runtime_utils.py +++ /dev/null @@ -1,261 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# -# SPDX-License-Identifier: MIT - -from __future__ import annotations - -import importlib.util -import inspect -import sys -import types -from pathlib import Path -from typing import Any - -import pytest - -REPO_ROOT = Path(__file__).resolve().parents[2] -# Import inventory modules without running tilegym/__init__.py. Runtime tests -# import the individual solution modules directly from Solution.spec.entry_point. -tilegym_pkg = types.ModuleType("tilegym") -tilegym_pkg.__path__ = [str(REPO_ROOT / "src/tilegym")] -sys.modules.setdefault("tilegym", tilegym_pkg) -transformers_pkg = types.ModuleType("tilegym.transformers") -transformers_pkg.__path__ = [str(REPO_ROOT / "src/tilegym/transformers")] -sys.modules.setdefault("tilegym.transformers", transformers_pkg) - -from tilegym.transformers.kernel_inventory import iter_kernel_definition_paths -from tilegym.transformers.kernel_inventory import load_json -from tilegym.transformers.kernel_inventory import validate_definition -from tilegym.transformers.kernel_inventory import validate_solution -from tilegym.transformers.kernel_inventory import validate_solution_entry_point - -DEFAULT_AXIS_VALUES = { - "N": 3, - "D": 64, - "H": 64, - "T": 5, -} -MUTATED_INPUTS_BY_DEFINITION = { - "olmo3_dual_rms_norm": ("q", "k"), - "olmoe_dual_rms_norm": ("q", "k"), - "qwen3_5_causal_conv1d_update_silu": ("conv_state",), -} - - -def all_definition_solution_cases() -> list[Any]: - return definition_solution_cases_for_submodule(None) - - -def definition_solution_cases_for_submodule(submodule: str | Path | None) -> list[Any]: - search_root = _transformer_submodule_root(submodule) - cases = [] - definition_paths = ( - iter_kernel_definition_paths(REPO_ROOT) - if search_root == REPO_ROOT - else sorted(search_root.glob("kernel_definitions/*.json")) - ) - for definition_path in definition_paths: - solution_path = definition_path.parent.parent / "kernel_solutions" / definition_path.name - definition_name = load_json(definition_path)["name"] - cases.append(pytest.param(definition_path, solution_path, id=definition_name)) - if search_root != REPO_ROOT and not cases: - raise ValueError(f"Transformer submodule has no kernel Definitions: {search_root}") - return cases - - -def _transformer_submodule_root(submodule: str | Path | None) -> Path: - if submodule is None or str(submodule) == "": - return REPO_ROOT - - raw_path = Path(submodule) - if raw_path.is_absolute(): - submodule_root = raw_path - else: - for candidate in ( - Path.cwd() / raw_path, - REPO_ROOT / raw_path, - REPO_ROOT / "src/tilegym/transformers" / raw_path, - ): - if candidate.exists(): - submodule_root = candidate - break - else: - submodule_root = REPO_ROOT / "src/tilegym/transformers" / raw_path - - submodule_root = submodule_root.resolve() - transformers_root = (REPO_ROOT / "src/tilegym/transformers").resolve() - try: - submodule_root.relative_to(transformers_root) - except ValueError as exc: - raise ValueError(f"Transformer submodule must live under {transformers_root}: {submodule}") from exc - - if not submodule_root.is_dir(): - raise ValueError(f"Transformer submodule does not exist: {submodule}") - return submodule_root - - -def run_definition_solution_runtime(definition_path: Path, solution_path: Path) -> None: - torch = pytest.importorskip("torch") - pytest.importorskip("flashinfer_bench") - pytest.importorskip("cuda.tile") - if not torch.cuda.is_available(): - pytest.skip("CUDA is required for transformer kernel runtime checks") - - definition = load_json(definition_path) - solution = load_json(solution_path) - validate_definition(definition) - validate_solution(solution, repo_root=REPO_ROOT) - validate_solution_entry_point(solution, repo_root=REPO_ROOT) - _skip_if_solution_does_not_target_current_hardware(solution, torch) - - device = torch.device("cuda") - torch.manual_seed(2026) - inputs = _make_inputs(definition, torch, device) - reference_inputs = {name: _clone_value(value) for name, value in inputs.items()} - solution_inputs = {name: _clone_value(value) for name, value in inputs.items()} - - reference_fn = _load_reference(definition["reference"], definition_path) - reference_outputs = _as_tuple(reference_fn(*(reference_inputs[name] for name in definition["inputs"]))) - - solution_fn = _load_solution_entry(solution) - solution_outputs = _as_tuple(_call_solution(solution_fn, solution_inputs)) - torch.cuda.synchronize() - - output_names = list(definition["outputs"]) - assert len(solution_outputs) == len(reference_outputs) == len(output_names), ( - f"{definition['name']}: output arity mismatch for outputs {output_names}" - ) - for name, actual, expected in zip(output_names, solution_outputs, reference_outputs): - torch.testing.assert_close( - actual, - expected, - rtol=2e-2, - atol=2e-2, - msg=lambda msg: f"{definition['name']} output {name} mismatch\n{msg}", - ) - - for name in MUTATED_INPUTS_BY_DEFINITION.get(definition["name"], ()): - torch.testing.assert_close( - solution_inputs[name], - reference_inputs[name], - rtol=2e-2, - atol=2e-2, - msg=lambda msg: f"{definition['name']} mutated input {name} mismatch\n{msg}", - ) - - -def _axis_values(definition: dict[str, Any]) -> dict[str, int]: - values = {name: axis["value"] for name, axis in definition["axes"].items() if axis.get("type") == "const"} - for name, axis in definition["axes"].items(): - if axis.get("type") == "var": - values[name] = DEFAULT_AXIS_VALUES.get(name, 4) - if "T_padded" in values and "T" in values and "K" in values: - values["T_padded"] = values["T"] + values["K"] - 1 - return values - - -def _make_inputs(definition: dict[str, Any], torch: Any, device: Any) -> dict[str, Any]: - axes = _axis_values(definition) - inputs = {} - for name, spec in definition["inputs"].items(): - inputs[name] = _make_input(name, spec, axes, torch, device) - return inputs - - -def _make_input(name: str, spec: dict[str, Any], axes: dict[str, int], torch: Any, device: Any) -> Any: - dtype = spec["dtype"] - shape_spec = spec["shape"] - if shape_spec is None: - if name == "eps": - return 1e-6 - if name == "offset": - return 1.0 - if name == "seq_len": - return axes["T"] - if dtype.startswith("int"): - return 1 - return 1.0 - - shape = tuple(axes[axis] for axis in shape_spec) - torch_dtype = _torch_dtype(dtype, torch) - if dtype == "bool": - return torch.randint(0, 2, shape, device=device, dtype=torch.bool) - if dtype.startswith("int"): - return torch.randint(-3, 4, shape, device=device, dtype=torch_dtype) - - base = 0.1 * torch.randn(shape, device=device, dtype=torch.float32) - if "weight" in name: - base = 1.0 + base - if name == "A_log": - base = -base.abs() - return base.to(torch_dtype).contiguous() - - -def _torch_dtype(dtype: str, torch: Any) -> Any: - return { - "float32": torch.float32, - "float16": torch.float16, - "bfloat16": torch.bfloat16, - "int64": torch.int64, - "int32": torch.int32, - "int16": torch.int16, - "int8": torch.int8, - "bool": torch.bool, - }[dtype] - - -def _skip_if_solution_does_not_target_current_hardware(solution: dict[str, Any], torch: Any) -> None: - target_hardware = solution["spec"].get("target_hardware", []) - current_hardware = _current_hardware_label(torch) - if target_hardware and current_hardware not in target_hardware: - pytest.skip( - f"Solution targets {target_hardware}; current hardware is {current_hardware} " - f"({torch.cuda.get_device_name(0)})" - ) - - -def _current_hardware_label(torch: Any) -> str: - return torch.cuda.get_device_name(0).upper().replace(" ", "_").replace("-", "_") - - -def _load_reference(reference: str, definition_path: Path) -> Any: - namespace: dict[str, Any] = {} - exec(compile(reference, str(definition_path), "exec"), namespace) - return namespace["run"] - - -def _load_solution_entry(solution: dict[str, Any]) -> Any: - file_path, symbol = solution["spec"]["entry_point"].split("::", 1) - source_path = REPO_ROOT / file_path - module_name = f"_tilegym_transformer_runtime_{source_path.stem}_{abs(hash(source_path))}" - spec = importlib.util.spec_from_file_location(module_name, source_path) - assert spec is not None - assert spec.loader is not None - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - return getattr(module, symbol) - - -def _call_solution(solution_fn: Any, inputs: dict[str, Any]) -> Any: - signature = inspect.signature(solution_fn) - kwargs = {} - for name, parameter in signature.parameters.items(): - if name in inputs: - kwargs[name] = inputs[name] - elif parameter.default is inspect.Parameter.empty: - raise TypeError(f"Solution entry point requires argument not present in Definition.inputs: {name}") - return solution_fn(**kwargs) - - -def _as_tuple(value: Any) -> tuple[Any, ...]: - if isinstance(value, tuple): - return value - if isinstance(value, list): - return tuple(value) - return (value,) - - -def _clone_value(value: Any) -> Any: - if hasattr(value, "clone"): - return value.clone() - return value diff --git a/tests/transformers/test_kernel_definition_solution_runtime.py b/tests/transformers/test_kernel_definition_solution_runtime.py deleted file mode 100644 index ec1a87b..0000000 --- a/tests/transformers/test_kernel_definition_solution_runtime.py +++ /dev/null @@ -1,42 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# -# SPDX-License-Identifier: MIT - -import pytest - -from tests.transformers.kernel_runtime_utils import _skip_if_solution_does_not_target_current_hardware -from tests.transformers.kernel_runtime_utils import run_definition_solution_runtime - - -def test_kernel_definition_solution_runtime(definition_path, solution_path): - run_definition_solution_runtime(definition_path, solution_path) - - -class _FakeCuda: - @staticmethod - def get_device_name(index): - assert index == 0 - return "NVIDIA GB300" - - -class _FakeTorch: - cuda = _FakeCuda() - - -def test_runtime_skips_solutions_that_do_not_target_current_hardware(): - solution = { - "spec": { - "target_hardware": ["NVIDIA_B200"], - } - } - with pytest.raises(pytest.skip.Exception, match="current hardware is NVIDIA_GB300"): - _skip_if_solution_does_not_target_current_hardware(solution, _FakeTorch) - - -def test_runtime_accepts_solutions_that_target_current_hardware(): - solution = { - "spec": { - "target_hardware": ["NVIDIA_B200", "NVIDIA_GB300"], - } - } - _skip_if_solution_does_not_target_current_hardware(solution, _FakeTorch) From 448b8e8e3d39b7b8aa9eb0ac681d065ac82f5f45 Mon Sep 17 00:00:00 2001 From: "Jiahui Liu (Engrg-Hardware 1)" Date: Wed, 8 Jul 2026 20:58:48 -0700 Subject: [PATCH 4/6] [tilegym] Skip cutile backend when cuda.tile.tune submodule is missing --- src/tilegym/__init__.py | 14 -------------- src/tilegym/backend/selector.py | 10 +++++++++- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/src/tilegym/__init__.py b/src/tilegym/__init__.py index 0da4ada..70ed383 100644 --- a/src/tilegym/__init__.py +++ b/src/tilegym/__init__.py @@ -17,19 +17,6 @@ def _check_torch_dependencies(): ) from None -def _check_ct_tune_dependency(): - """Verify that cuda-tile with tune support is installed with helpful error message.""" - try: - import cuda.tile.tune # noqa: F401 - except (ImportError, ModuleNotFoundError): - raise ImportError( - "\n\n[TileGym] cuda.tile.tune is required but not available.\n" - "Please install or upgrade cuda-tile:\n\n" - " pip install cuda-tile\n\n" - "See: https://github.com/NVIDIA/cutile-python" - ) from None - - # Check dependencies before any imports _check_torch_dependencies() @@ -54,7 +41,6 @@ def _check_ct_tune_dependency(): # Setup cutile integration if is_backend_available("cutile"): - _check_ct_tune_dependency() # Apply experimental kernel tracking patch from .experimental import _apply_patch as _apply_experimental_patch diff --git a/src/tilegym/backend/selector.py b/src/tilegym/backend/selector.py index a6a87a8..662438b 100644 --- a/src/tilegym/backend/selector.py +++ b/src/tilegym/backend/selector.py @@ -29,17 +29,25 @@ def is_nvt_available(): try: import cuda.tile as ct + import cuda.tile.tune # noqa: F401 # required by every op under ops/cutile/ CUTILE_AVAILABLE = True except ImportError: import warnings - warnings.warn("Failed to import cuda_tile_compiler, CUDA Tile backend is not available") + warnings.warn( + "Failed to import cuda.tile / cuda.tile.tune, CUDA Tile backend is not available. " + "To enable it: `pip install cuda-tile` " + "(see https://github.com/NVIDIA/cutile-python)" + ) CUTILE_AVAILABLE = False def is_cutile_available(): + if os.environ.get("TILEGYM_DISABLE_CUTILE") == "1": + print("[TileGym] TILEGYM_DISABLE_CUTILE=1; CUDA Tile backend force-disabled") + return False return CUTILE_AVAILABLE From 1dcc43c5ba3d80cf061f8286bd00e5e35681ad24 Mon Sep 17 00:00:00 2001 From: Dhruv Baronia Date: Thu, 9 Jul 2026 20:02:01 -0700 Subject: [PATCH 5/6] Fixing Tile C++ FMHA result mismatches due to unmasked loads --- src/tilegym/ops/tilecpp/attention.cuh | 36 ++++++++++++++++++--------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/src/tilegym/ops/tilecpp/attention.cuh b/src/tilegym/ops/tilecpp/attention.cuh index 9caf569..20249cf 100644 --- a/src/tilegym/ops/tilecpp/attention.cuh +++ b/src/tilegym/ops/tilecpp/attention.cuh @@ -108,7 +108,13 @@ __tile_global__ void prefill_fmha_fwd_kernel( auto Out_view = ct::partition_view(Out_span, ct::shape<1, 1, BLOCK_M, BLOCK_D>{}); // Load Q block: [BLOCK_M, BLOCK_D] - T_4D_M q_4d = Q_view.load(batch_idx, head_idx, pid_x, 0); + constexpr bool EVEN_Q = (S_QO % BLOCK_M) == 0; + T_4D_M q_4d; + if constexpr (EVEN_Q) { + q_4d = Q_view.load(batch_idx, head_idx, pid_x, 0); + } else { + q_4d = Q_view.load_masked(batch_idx, head_idx, pid_x, 0); + } auto q = ct::reshape(q_4d, ct::shape{}); using f32_Mx1 = ct::tile>; @@ -140,7 +146,6 @@ __tile_global__ void prefill_fmha_fwd_kernel( auto offs_n_2d = ct::reshape(offs_n_1d, ct::shape<1, BLOCK_N>{}); auto neg_inf_2d = ct::full(-INFINITY); - auto zero_2d = ct::full(0.0f); constexpr bool NEEDS_MASK = IS_CAUSAL || !EVEN_K; int unmasked_end = NEEDS_MASK ? (mask_start < num_kv_blocks ? mask_start : num_kv_blocks) @@ -189,8 +194,13 @@ __tile_global__ void prefill_fmha_fwd_kernel( int curr_n = kv_block * BLOCK_N; T_4D_N k_raw; - [[ using cutile : hint(1000, latency=2) ]] - k_raw = K_view.load(batch_idx, off_kv_h, kv_block, 0); + if constexpr (EVEN_K) { + [[ using cutile : hint(1000, latency=2) ]] + k_raw = K_view.load(batch_idx, off_kv_h, kv_block, 0); + } else { + [[ using cutile : hint(1000, latency=2) ]] + k_raw = K_view.load_masked(batch_idx, off_kv_h, kv_block, 0); + } auto k_4d_T = ct::permute(k_raw, ct::dimension_map<0, 1, 3, 2>{}); auto k_t = ct::reshape(k_4d_T, ct::shape{}); @@ -200,16 +210,13 @@ __tile_global__ void prefill_fmha_fwd_kernel( ct::shape<1, BLOCK_N>{}); if constexpr (IS_CAUSAL && !EVEN_K) { auto valid_bool = (n_pos < ct::full(S_KV)) & (offs_m_2d >= n_pos); - auto mask_val = ct::select(valid_bool, zero_2d, neg_inf_2d); - qk = qk + mask_val; + qk = ct::select(valid_bool, qk, neg_inf_2d); } else if constexpr (IS_CAUSAL) { auto valid_bool = (offs_m_2d >= n_pos); - auto mask_val = ct::select(valid_bool, zero_2d, neg_inf_2d); - qk = qk + mask_val; + qk = ct::select(valid_bool, qk, neg_inf_2d); } else { auto valid_bool = n_pos < ct::full(S_KV); - auto mask_val = ct::select(valid_bool, zero_2d, neg_inf_2d); - qk = qk + mask_val; + qk = ct::select(valid_bool, qk, neg_inf_2d); } auto qk_max_2d = ct::reduce_max(qk, ct::integral_constant<1>{}); @@ -224,8 +231,13 @@ __tile_global__ void prefill_fmha_fwd_kernel( acc = acc * alpha; T_4D_N v_4d; - [[ using cutile : hint(1000, latency=4) ]] - v_4d = V_view.load(batch_idx, off_kv_h, kv_block, 0); + if constexpr (EVEN_K) { + [[ using cutile : hint(1000, latency=4) ]] + v_4d = V_view.load(batch_idx, off_kv_h, kv_block, 0); + } else { + [[ using cutile : hint(1000, latency=4) ]] + v_4d = V_view.load_masked(batch_idx, off_kv_h, kv_block, 0); + } auto v = ct::reshape(v_4d, ct::shape{}); auto p_T = ct::element_cast(p); From 4dcc245941619ef3c778b1382820886dd5279c83 Mon Sep 17 00:00:00 2001 From: nvskills-svc-account Date: Fri, 10 Jul 2026 14:14:32 +0000 Subject: [PATCH 6/6] Attach NVSkills validation signatures Signed-off-by: nvskills-svc-account --- .../BENCHMARK.md | 10 ++++---- .../skill-card.md | 25 +++++++++++++------ .../skill.oms.sig | 2 +- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/skills/tilegym-monkey-patch-kernels-to-transformers/BENCHMARK.md b/skills/tilegym-monkey-patch-kernels-to-transformers/BENCHMARK.md index 1e7bfdd..2a110a6 100644 --- a/skills/tilegym-monkey-patch-kernels-to-transformers/BENCHMARK.md +++ b/skills/tilegym-monkey-patch-kernels-to-transformers/BENCHMARK.md @@ -7,7 +7,7 @@ This benchmark summarizes 3-Tier Evaluation from NVSkills-Eval results for the s ## Evaluation Summary - Skill: `tilegym-monkey-patch-kernels-to-transformers` -- Evaluation date: 2026-06-16 +- Evaluation date: 2026-07-10 - NVSkills-Eval profile: `external` - Environment: `astra-sandbox` - Dataset: 5 evaluation tasks @@ -55,10 +55,10 @@ Task composition is derived from the evaluation dataset when possible. Entries w | Dimension | Num | `claude-code` | `codex` | |---|---:|---:|---:| | Security | 5 | 100% (+0%) | 100% (+0%) | -| Correctness | 5 | 100% (+12%) | 99% (+12%) | -| Discoverability | 5 | 100% (+11%) | 94% (+2%) | -| Effectiveness | 5 | 98% (+18%) | 100% (+19%) | -| Efficiency | 5 | 96% (+13%) | 90% (+1%) | +| Correctness | 5 | 100% (+15%) | 99% (+14%) | +| Discoverability | 5 | 100% (+13%) | 99% (+9%) | +| Effectiveness | 5 | 98% (+17%) | 100% (+19%) | +| Efficiency | 5 | 96% (+13%) | 96% (+7%) | Score values show skill-assisted performance. Values in parentheses show uplift versus the no-skill baseline when baseline data is available. diff --git a/skills/tilegym-monkey-patch-kernels-to-transformers/skill-card.md b/skills/tilegym-monkey-patch-kernels-to-transformers/skill-card.md index 3f4f8b1..b7f8b19 100644 --- a/skills/tilegym-monkey-patch-kernels-to-transformers/skill-card.md +++ b/skills/tilegym-monkey-patch-kernels-to-transformers/skill-card.md @@ -9,11 +9,17 @@ NVIDIA
### License/Terms of Use:
CC-BY-4.0 AND Apache-2.0
## Use Case:
-Developers and engineers integrating TileGym GPU kernels into Hugging Face transformers models for LLM training and inference performance improvements.
+Developers and engineers integrating TileGym high-performance CUDA Tile kernels into Hugging Face transformers models to validate end-to-end functional correctness and improve inference and training throughput.
### Deployment Geography for Use:
Global
+## Requirements / Dependencies:
+**Requires API Key or External Credential:** [Not Specified]
+**Credential Type(s):** [None identified]
+ +Do not include secrets in prompts/logs/output; use least-privilege credentials; rotate keys as appropriate.
+ ## Known Risks and Mitigations:
Risk: Review before execution as proposals could introduce incorrect or misleading guidance into skills.
Mitigation: Review and scan skill before deployment.
@@ -23,11 +29,14 @@ Mitigation: Review and scan skill before deployment.
- [Kernel Integration](references/kernel-integration.md)
- [Auto Kernelize](references/auto-kernelize.md)
- [Kernel Inventory Schema](references/kernel-inventory-schema.md)
-- [NVIDIA CUDA Tile IR Documentation](https://docs.nvidia.com/cuda/tile-ir/latest/)
+- [Workflow Diagram](references/workflow-diagram.png)
+- [CUDA Tile IR Documentation](https://docs.nvidia.com/cuda/tile-ir/latest/)
+- [FlashInfer-Bench Definition Schema](https://github.com/flashinfer-ai/flashinfer-bench/blob/main/docs/flashinfer-trace/definition.mdx)
+- [FlashInfer-Bench Solution Schema](https://github.com/flashinfer-ai/flashinfer-bench/blob/main/docs/flashinfer-trace/solution.mdx)
## Skill Output:
-**Output Type(s):** [Code, Shell commands, Configuration instructions]
+**Output Type(s):** [Code, Shell commands, Analysis]
**Output Format:** [Markdown with inline code blocks]
**Output Parameters:** [1D]
**Other Properties Related to Output:** [None]
@@ -39,7 +48,7 @@ Mitigation: Review and scan skill before deployment.
## Evaluation Tasks:
-Evaluated against 5 tasks (1 positive skill-activation, 4 negative) in the NVSkills-Eval `external` profile.
+Evaluated against 5 tasks (1 positive skill-activation, 4 negative) via NVSkills-Eval external profile in astra-sandbox environment.
## Evaluation Metrics Used:
Reported benchmark dimensions:
@@ -64,10 +73,10 @@ Underlying evaluation signals used in this run:
| Dimension | Num | `claude-code` | `codex` | |---|---:|---:|---:| | Security | 5 | 100% (+0%) | 100% (+0%) | -| Correctness | 5 | 100% (+12%) | 99% (+12%) | -| Discoverability | 5 | 100% (+11%) | 94% (+2%) | -| Effectiveness | 5 | 98% (+18%) | 100% (+19%) | -| Efficiency | 5 | 96% (+13%) | 90% (+1%) | +| Correctness | 5 | 100% (+15%) | 99% (+14%) | +| Discoverability | 5 | 100% (+13%) | 99% (+9%) | +| Effectiveness | 5 | 98% (+17%) | 100% (+19%) | +| Efficiency | 5 | 96% (+13%) | 96% (+7%) | ## Skill Version(s):
2026.06.03 (source: frontmatter)
diff --git a/skills/tilegym-monkey-patch-kernels-to-transformers/skill.oms.sig b/skills/tilegym-monkey-patch-kernels-to-transformers/skill.oms.sig index 7af9e3a..444ee8d 100644 --- a/skills/tilegym-monkey-patch-kernels-to-transformers/skill.oms.sig +++ b/skills/tilegym-monkey-patch-kernels-to-transformers/skill.oms.sig @@ -1 +1 @@ -{"mediaType":"application/vnd.dev.sigstore.bundle.v0.3+json","verificationMaterial":{"x509CertificateChain":{"certificates":[{"rawBytes":"MIICgzCCAgmgAwIBAgIUKIyS7SxNteQIiWzK1dWj85E6520wCgYIKoZIzj0EAwMwVTELMAkGA1UEBhMCVVMxGzAZBgNVBAoMEk5WSURJQSBDb3Jwb3JhdGlvbjEpMCcGA1UEAwwgTlZJRElBIEFnZW50IENhcGFiaWxpdGllcyBJQ0EgMDEwHhcNMjYwNDAxMDAwMDAwWhcNMjgwNDIyMTUzMzA5WjBUMQswCQYDVQQGEwJVUzEbMBkGA1UECgwSTlZJRElBIENvcnBvcmF0aW9uMSgwJgYDVQQDDB9OVklESUEgQWdlbnQgU2tpbGxzIFNpZ25pbmcgMDAxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEYoRM9bQl/dGlwSRNi6bTpIJUXH8Nv9GciP6LSflJYYMLCc296kpyuTSsk5ddbAWiDcFX3C/ydX3jwc+qCLYP6uHy9XphyLjOQ27Yb2J6rBLVtRBS1mgGco/Gr7fL6ODco4GaMIGXMB0GA1UdDgQWBBRQ/5ZW3nJ6lmo9SVk7I15o7UGmpTAfBgNVHSMEGDAWgBRPGpILxMBBleJSsBGjrMKsby1CgjAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIHgDA3BggrBgEFBQcBAQQrMCkwJwYIKwYBBQUHMAGGG2h0dHA6Ly9vY3NwLm5kaXMubnZpZGlhLmNvbTAKBggqhkjOPQQDAwNoADBlAjAUygu/GiOCIXrgGr4SmLgeEVDcEitfFUv7ALbvLVGVyMysB3mxmO/uInZfXzWcJZsCMQDxuoxj4ZmO30jhkPIcCxGFCOvnUsnfU3TfGcouYm4M6iRpbKvtVnHPiy4bi6pcKf0="},{"rawBytes":"MIICiDCCAg6gAwIBAgIUZsIuSv9NkpJCNqtYEfCouVv5BzowCgYIKoZIzj0EAwMwUTELMAkGA1UEBhMCVVMxGzAZBgNVBAoMEk5WSURJQSBDb3Jwb3JhdGlvbjElMCMGA1UEAwwcTlZJRElBIEFnZW50IENhcGFiaWxpdGllcyBDQTAgFw0yNjA0MDEwMDAwMDBaGA85OTk5MTIzMTIzNTk1OVowVTELMAkGA1UEBhMCVVMxGzAZBgNVBAoMEk5WSURJQSBDb3Jwb3JhdGlvbjEpMCcGA1UEAwwgTlZJRElBIEFnZW50IENhcGFiaWxpdGllcyBJQ0EgMDEwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASI72cR3ctKGg4VWnB3bNja6g1Z2PnOmFEopkPof+QeIcPk9rT+g9MjJnq51EQXL93a7C2GJ9J985G4o2V85VD7wJ1RaXhluHW2rf3y8bQGeAYaKMr5s/hUgn+M3/9WlWejgaAwgZ0wHQYDVR0OBBYEFE8akgvEwEGV4lKwEaOswqxvLUKCMB8GA1UdIwQYMBaAFItnoAjjfuCEUvzyvWyI2vOGvwPjMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgEGMDcGCCsGAQUFBwEBBCswKTAnBggrBgEFBQcwAYYbaHR0cDovL29jc3AubmRpcy5udmlkaWEuY29tMAoGCCqGSM49BAMDA2gAMGUCMQCeIMMfAbyzPDacw2MxG+Yt1cikrJX/DVxiGfXuHmkkXn6VgSzE79+lkqDErpVO2gYCMCNEColOyvUvkzZGUEI1hQ3PfMgi3FIo9tHoBKMw4/wGBLFpu/0ubtmbBXM6/UMOEw=="},{"rawBytes":"MIICRTCCAcygAwIBAgIUeJdY3rV86EdvFmG7L8LJBsyQFYkwCgYIKoZIzj0EAwMwUTELMAkGA1UEBhMCVVMxGzAZBgNVBAoMEk5WSURJQSBDb3Jwb3JhdGlvbjElMCMGA1UEAwwcTlZJRElBIEFnZW50IENhcGFiaWxpdGllcyBDQTAgFw0yNjA0MDEwMDAwMDBaGA85OTk5MTIzMTIzNTk1OVowUTELMAkGA1UEBhMCVVMxGzAZBgNVBAoMEk5WSURJQSBDb3Jwb3JhdGlvbjElMCMGA1UEAwwcTlZJRElBIEFnZW50IENhcGFiaWxpdGllcyBDQTB2MBAGByqGSM49AgEGBSuBBAAiA2IABAYpiXCDjJ9NT2eSDhyHJVSw1Tbze18cGG2F/578oWvHxg23eQAhNRYdq88i1iOshZSO6C29doKui5Xpmo/7Ctw9Sx4PP2RzOmIuOLCuTdNtKcTRwi4GEsd5BAFvWj42M6NjMGEwHQYDVR0OBBYEFItnoAjjfuCEUvzyvWyI2vOGvwPjMB8GA1UdIwQYMBaAFItnoAjjfuCEUvzyvWyI2vOGvwPjMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2cAMGQCMCwtAjWLaNwgGWNCgdyNoTyvNhqWRECRJV2r3+7w8g0PL6NHLOsbkgE09BH95h8XlgIwTaQmbbUh2ChAJ5TA1wRiVDnCcvbzHlZl2jM2FcwQQZlk19LOAbyGMRixbu2Ww/rj"}]},"tlogEntries":[]},"dsseEnvelope":{"payload":"ewogICJfdHlwZSI6ICJodHRwczovL2luLXRvdG8uaW8vU3RhdGVtZW50L3YxIiwKICAic3ViamVjdCI6IFsKICAgIHsKICAgICAgIm5hbWUiOiAidGlsZWd5bS1tb25rZXktcGF0Y2gta2VybmVscy10by10cmFuc2Zvcm1lcnMiLAogICAgICAiZGlnZXN0IjogewogICAgICAgICJzaGEyNTYiOiAiYjY3ZWEzY2FiYzUwYWNhMmNkYWIyYmZlZGZlYmY5MTVjYzQ4MDg1NmU5MDY3Yzk2Yjg0MjYzOWJjNDhiOGVmMiIKICAgICAgfQogICAgfQogIF0sCiAgInByZWRpY2F0ZVR5cGUiOiAiaHR0cHM6Ly9tb2RlbF9zaWduaW5nL3NpZ25hdHVyZS92MS4wIiwKICAicHJlZGljYXRlIjogewogICAgInNlcmlhbGl6YXRpb24iOiB7CiAgICAgICJoYXNoX3R5cGUiOiAic2hhMjU2IiwKICAgICAgIm1ldGhvZCI6ICJmaWxlcyIsCiAgICAgICJpZ25vcmVfcGF0aHMiOiBbCiAgICAgICAgIi5naXRodWIiLAogICAgICAgICIuZ2l0aWdub3JlIiwKICAgICAgICAiLmdpdCIsCiAgICAgICAgIi5naXRhdHRyaWJ1dGVzIgogICAgICBdLAogICAgICAiYWxsb3dfc3ltbGlua3MiOiBmYWxzZQogICAgfSwKICAgICJyZXNvdXJjZXMiOiBbCiAgICAgIHsKICAgICAgICAiYWxnb3JpdGhtIjogInNoYTI1NiIsCiAgICAgICAgIm5hbWUiOiAiQkVOQ0hNQVJLLm1kIiwKICAgICAgICAiZGlnZXN0IjogIjYxY2E2MWMwMzUzN2JhNGEzMGNiNWZjYmRjNWRiOGIzYTcwYzFhNGZlZjQzNTI2OTEyYTYxZjM3OWExMWJkZWMiCiAgICAgIH0sCiAgICAgIHsKICAgICAgICAiYWxnb3JpdGhtIjogInNoYTI1NiIsCiAgICAgICAgIm5hbWUiOiAiU0tJTEwubWQiLAogICAgICAgICJkaWdlc3QiOiAiZGU3YTAwZWU2OWM4NTljY2JkYTRiYThhYzkxYTI2NjU1NDkxZTllMzAzOWIxYWUwY2FkYzc5NjZhOGFhN2E5MyIKICAgICAgfSwKICAgICAgewogICAgICAgICJhbGdvcml0aG0iOiAic2hhMjU2IiwKICAgICAgICAibmFtZSI6ICJldmFscy9ldmFscy5qc29uIiwKICAgICAgICAiZGlnZXN0IjogImIzMTEwNThlN2RlNWIxOGFhMjI4YjVmNGU4NTBhMzE2MDNhMzRlN2FjZjk0Yzk0MTQwYmIwMGE3YWY1NTlkYTQiCiAgICAgIH0sCiAgICAgIHsKICAgICAgICAiYWxnb3JpdGhtIjogInNoYTI1NiIsCiAgICAgICAgIm5hbWUiOiAicmVmZXJlbmNlcy9hdXRvLWtlcm5lbGl6ZS5tZCIsCiAgICAgICAgImRpZ2VzdCI6ICI3NTk3YjA0MjJjMjQ3YmFhNGZiMTgwZmI5MjU2NWNkOWI3ZDM5N2NlYmEwMDFmMDY3Y2IzNWE4NDg4YmU3NGQ3IgogICAgICB9LAogICAgICB7CiAgICAgICAgImFsZ29yaXRobSI6ICJzaGEyNTYiLAogICAgICAgICJuYW1lIjogInJlZmVyZW5jZXMvZW52aXJvbm1lbnQtc2V0dXAubWQiLAogICAgICAgICJkaWdlc3QiOiAiYjM5ZWM4ZTUzZTJkODU0MTFkM2ZlMTZlYjczODEzMGVlOGFmYWM0N2RkODhlOTBhNzY1NGNjYTA4Y2E4Y2E0NyIKICAgICAgfSwKICAgICAgewogICAgICAgICJhbGdvcml0aG0iOiAic2hhMjU2IiwKICAgICAgICAibmFtZSI6ICJyZWZlcmVuY2VzL2tlcm5lbC1pbnRlZ3JhdGlvbi5tZCIsCiAgICAgICAgImRpZ2VzdCI6ICIyNGY4NGE0NDA0MWUzM2VhMWIwYWRlYTBjYWUwNWExNmM1Zjc4ZjU2NDg2YzQ0ZjFlZDdkYjliOTU5YWU5ODc5IgogICAgICB9LAogICAgICB7CiAgICAgICAgImFsZ29yaXRobSI6ICJzaGEyNTYiLAogICAgICAgICJuYW1lIjogInJlZmVyZW5jZXMva2VybmVsLWludmVudG9yeS1zY2hlbWEubWQiLAogICAgICAgICJkaWdlc3QiOiAiN2Y1MDdhNWY1MGJiMjc0OTBiYjNiMmY4YzJmYjRkODQ0NjBkYjAxZDU5OThhMmUzZjNkZDQ2YTBjNDcyZWM5ZSIKICAgICAgfSwKICAgICAgewogICAgICAgICJhbGdvcml0aG0iOiAic2hhMjU2IiwKICAgICAgICAibmFtZSI6ICJyZWZlcmVuY2VzL3dvcmtmbG93LWRpYWdyYW0ucG5nIiwKICAgICAgICAiZGlnZXN0IjogIjIyZWRkZDNkODFiM2MzN2QyYjQ3NjY1ZDZmZjE5NzAxODY1MzY0NDk3OTkxZDY1MDYyZTJmOWNhM2VmNmVmODUiCiAgICAgIH0sCiAgICAgIHsKICAgICAgICAiYWxnb3JpdGhtIjogInNoYTI1NiIsCiAgICAgICAgIm5hbWUiOiAic2tpbGwtY2FyZC5tZCIsCiAgICAgICAgImRpZ2VzdCI6ICJmMWI2ODc1NjBiYTJlNDE1MzU1ODE0NDI2YmE0NTJiNmY2NTdhOTZmY2FmMDBkMzhjODhjNTcxNDNiNTE5MWEyIgogICAgICB9CiAgICBdCiAgfQp9","payloadType":"application/vnd.in-toto+json","signatures":[{"sig":"MGUCMQDbfR92FeMjEcZQzIbdqnlmgaxCNd7nGVXKp6/pladsoMnT3ckTYOfsHF6MxAi9+5ECME668JO/ltcRgtzmnMcr2y+8p56SBhMcuQt7UYx6w8Z+9ShLyaZHF6+3gK7hFd6LFw==","keyid":""}]}} \ No newline at end of file +{"mediaType":"application/vnd.dev.sigstore.bundle.v0.3+json","verificationMaterial":{"x509CertificateChain":{"certificates":[{"rawBytes":"MIICgzCCAgmgAwIBAgIUKIyS7SxNteQIiWzK1dWj85E6520wCgYIKoZIzj0EAwMwVTELMAkGA1UEBhMCVVMxGzAZBgNVBAoMEk5WSURJQSBDb3Jwb3JhdGlvbjEpMCcGA1UEAwwgTlZJRElBIEFnZW50IENhcGFiaWxpdGllcyBJQ0EgMDEwHhcNMjYwNDAxMDAwMDAwWhcNMjgwNDIyMTUzMzA5WjBUMQswCQYDVQQGEwJVUzEbMBkGA1UECgwSTlZJRElBIENvcnBvcmF0aW9uMSgwJgYDVQQDDB9OVklESUEgQWdlbnQgU2tpbGxzIFNpZ25pbmcgMDAxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEYoRM9bQl/dGlwSRNi6bTpIJUXH8Nv9GciP6LSflJYYMLCc296kpyuTSsk5ddbAWiDcFX3C/ydX3jwc+qCLYP6uHy9XphyLjOQ27Yb2J6rBLVtRBS1mgGco/Gr7fL6ODco4GaMIGXMB0GA1UdDgQWBBRQ/5ZW3nJ6lmo9SVk7I15o7UGmpTAfBgNVHSMEGDAWgBRPGpILxMBBleJSsBGjrMKsby1CgjAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIHgDA3BggrBgEFBQcBAQQrMCkwJwYIKwYBBQUHMAGGG2h0dHA6Ly9vY3NwLm5kaXMubnZpZGlhLmNvbTAKBggqhkjOPQQDAwNoADBlAjAUygu/GiOCIXrgGr4SmLgeEVDcEitfFUv7ALbvLVGVyMysB3mxmO/uInZfXzWcJZsCMQDxuoxj4ZmO30jhkPIcCxGFCOvnUsnfU3TfGcouYm4M6iRpbKvtVnHPiy4bi6pcKf0="},{"rawBytes":"MIICiDCCAg6gAwIBAgIUZsIuSv9NkpJCNqtYEfCouVv5BzowCgYIKoZIzj0EAwMwUTELMAkGA1UEBhMCVVMxGzAZBgNVBAoMEk5WSURJQSBDb3Jwb3JhdGlvbjElMCMGA1UEAwwcTlZJRElBIEFnZW50IENhcGFiaWxpdGllcyBDQTAgFw0yNjA0MDEwMDAwMDBaGA85OTk5MTIzMTIzNTk1OVowVTELMAkGA1UEBhMCVVMxGzAZBgNVBAoMEk5WSURJQSBDb3Jwb3JhdGlvbjEpMCcGA1UEAwwgTlZJRElBIEFnZW50IENhcGFiaWxpdGllcyBJQ0EgMDEwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASI72cR3ctKGg4VWnB3bNja6g1Z2PnOmFEopkPof+QeIcPk9rT+g9MjJnq51EQXL93a7C2GJ9J985G4o2V85VD7wJ1RaXhluHW2rf3y8bQGeAYaKMr5s/hUgn+M3/9WlWejgaAwgZ0wHQYDVR0OBBYEFE8akgvEwEGV4lKwEaOswqxvLUKCMB8GA1UdIwQYMBaAFItnoAjjfuCEUvzyvWyI2vOGvwPjMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgEGMDcGCCsGAQUFBwEBBCswKTAnBggrBgEFBQcwAYYbaHR0cDovL29jc3AubmRpcy5udmlkaWEuY29tMAoGCCqGSM49BAMDA2gAMGUCMQCeIMMfAbyzPDacw2MxG+Yt1cikrJX/DVxiGfXuHmkkXn6VgSzE79+lkqDErpVO2gYCMCNEColOyvUvkzZGUEI1hQ3PfMgi3FIo9tHoBKMw4/wGBLFpu/0ubtmbBXM6/UMOEw=="},{"rawBytes":"MIICRTCCAcygAwIBAgIUeJdY3rV86EdvFmG7L8LJBsyQFYkwCgYIKoZIzj0EAwMwUTELMAkGA1UEBhMCVVMxGzAZBgNVBAoMEk5WSURJQSBDb3Jwb3JhdGlvbjElMCMGA1UEAwwcTlZJRElBIEFnZW50IENhcGFiaWxpdGllcyBDQTAgFw0yNjA0MDEwMDAwMDBaGA85OTk5MTIzMTIzNTk1OVowUTELMAkGA1UEBhMCVVMxGzAZBgNVBAoMEk5WSURJQSBDb3Jwb3JhdGlvbjElMCMGA1UEAwwcTlZJRElBIEFnZW50IENhcGFiaWxpdGllcyBDQTB2MBAGByqGSM49AgEGBSuBBAAiA2IABAYpiXCDjJ9NT2eSDhyHJVSw1Tbze18cGG2F/578oWvHxg23eQAhNRYdq88i1iOshZSO6C29doKui5Xpmo/7Ctw9Sx4PP2RzOmIuOLCuTdNtKcTRwi4GEsd5BAFvWj42M6NjMGEwHQYDVR0OBBYEFItnoAjjfuCEUvzyvWyI2vOGvwPjMB8GA1UdIwQYMBaAFItnoAjjfuCEUvzyvWyI2vOGvwPjMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2cAMGQCMCwtAjWLaNwgGWNCgdyNoTyvNhqWRECRJV2r3+7w8g0PL6NHLOsbkgE09BH95h8XlgIwTaQmbbUh2ChAJ5TA1wRiVDnCcvbzHlZl2jM2FcwQQZlk19LOAbyGMRixbu2Ww/rj"}]},"tlogEntries":[]},"dsseEnvelope":{"payload":"ewogICJfdHlwZSI6ICJodHRwczovL2luLXRvdG8uaW8vU3RhdGVtZW50L3YxIiwKICAic3ViamVjdCI6IFsKICAgIHsKICAgICAgIm5hbWUiOiAidGlsZWd5bS1tb25rZXktcGF0Y2gta2VybmVscy10by10cmFuc2Zvcm1lcnMiLAogICAgICAiZGlnZXN0IjogewogICAgICAgICJzaGEyNTYiOiAiMjRkZDY4YzIxMTk0ZmEyYzc3ODc4NGIwYzM0Y2I1Y2MyZjMxMjRjODM2YmRkYjcxY2U5YWIyY2MyNDcyN2JhOCIKICAgICAgfQogICAgfQogIF0sCiAgInByZWRpY2F0ZVR5cGUiOiAiaHR0cHM6Ly9tb2RlbF9zaWduaW5nL3NpZ25hdHVyZS92MS4wIiwKICAicHJlZGljYXRlIjogewogICAgInJlc291cmNlcyI6IFsKICAgICAgewogICAgICAgICJkaWdlc3QiOiAiNGRhZDg0ZjFlZTJiYTNmMDQyYTQ2YmZiMzA2NGZiM2FmOGYzYmU1MmZlZWNiMTIzYTVlZWZjZGJhNDM2YTNkZCIsCiAgICAgICAgImFsZ29yaXRobSI6ICJzaGEyNTYiLAogICAgICAgICJuYW1lIjogIkJFTkNITUFSSy5tZCIKICAgICAgfSwKICAgICAgewogICAgICAgICJkaWdlc3QiOiAiZGU3YTAwZWU2OWM4NTljY2JkYTRiYThhYzkxYTI2NjU1NDkxZTllMzAzOWIxYWUwY2FkYzc5NjZhOGFhN2E5MyIsCiAgICAgICAgImFsZ29yaXRobSI6ICJzaGEyNTYiLAogICAgICAgICJuYW1lIjogIlNLSUxMLm1kIgogICAgICB9LAogICAgICB7CiAgICAgICAgImRpZ2VzdCI6ICJiMzExMDU4ZTdkZTViMThhYTIyOGI1ZjRlODUwYTMxNjAzYTM0ZTdhY2Y5NGM5NDE0MGJiMDBhN2FmNTU5ZGE0IiwKICAgICAgICAiYWxnb3JpdGhtIjogInNoYTI1NiIsCiAgICAgICAgIm5hbWUiOiAiZXZhbHMvZXZhbHMuanNvbiIKICAgICAgfSwKICAgICAgewogICAgICAgICJkaWdlc3QiOiAiNzU5N2IwNDIyYzI0N2JhYTRmYjE4MGZiOTI1NjVjZDliN2QzOTdjZWJhMDAxZjA2N2NiMzVhODQ4OGJlNzRkNyIsCiAgICAgICAgImFsZ29yaXRobSI6ICJzaGEyNTYiLAogICAgICAgICJuYW1lIjogInJlZmVyZW5jZXMvYXV0by1rZXJuZWxpemUubWQiCiAgICAgIH0sCiAgICAgIHsKICAgICAgICAiZGlnZXN0IjogImIzOWVjOGU1M2UyZDg1NDExZDNmZTE2ZWI3MzgxMzBlZThhZmFjNDdkZDg4ZTkwYTc2NTRjY2EwOGNhOGNhNDciLAogICAgICAgICJhbGdvcml0aG0iOiAic2hhMjU2IiwKICAgICAgICAibmFtZSI6ICJyZWZlcmVuY2VzL2Vudmlyb25tZW50LXNldHVwLm1kIgogICAgICB9LAogICAgICB7CiAgICAgICAgImRpZ2VzdCI6ICIyNGY4NGE0NDA0MWUzM2VhMWIwYWRlYTBjYWUwNWExNmM1Zjc4ZjU2NDg2YzQ0ZjFlZDdkYjliOTU5YWU5ODc5IiwKICAgICAgICAiYWxnb3JpdGhtIjogInNoYTI1NiIsCiAgICAgICAgIm5hbWUiOiAicmVmZXJlbmNlcy9rZXJuZWwtaW50ZWdyYXRpb24ubWQiCiAgICAgIH0sCiAgICAgIHsKICAgICAgICAiZGlnZXN0IjogIjljNWE3NjEyZGQyYTUyMzdlZTY2YTBkM2QzOGVjZTM0YjEzMGFkNTBiODk5MWY0N2FlODE1NjA3NGJkMjIwNDIiLAogICAgICAgICJhbGdvcml0aG0iOiAic2hhMjU2IiwKICAgICAgICAibmFtZSI6ICJyZWZlcmVuY2VzL2tlcm5lbC1pbnZlbnRvcnktc2NoZW1hLm1kIgogICAgICB9LAogICAgICB7CiAgICAgICAgImRpZ2VzdCI6ICIyMmVkZGQzZDgxYjNjMzdkMmI0NzY2NWQ2ZmYxOTcwMTg2NTM2NDQ5Nzk5MWQ2NTA2MmUyZjljYTNlZjZlZjg1IiwKICAgICAgICAiYWxnb3JpdGhtIjogInNoYTI1NiIsCiAgICAgICAgIm5hbWUiOiAicmVmZXJlbmNlcy93b3JrZmxvdy1kaWFncmFtLnBuZyIKICAgICAgfSwKICAgICAgewogICAgICAgICJkaWdlc3QiOiAiM2ZmODg2NGQ5MGE4OGRiZjg1Y2ZmMmYxNzk3ZmRiMzY2ZTExZjc2YmNjYTUwZTU0MjQwNzM2ZjdlYzM5ZDEwOCIsCiAgICAgICAgImFsZ29yaXRobSI6ICJzaGEyNTYiLAogICAgICAgICJuYW1lIjogInNraWxsLWNhcmQubWQiCiAgICAgIH0KICAgIF0sCiAgICAic2VyaWFsaXphdGlvbiI6IHsKICAgICAgIm1ldGhvZCI6ICJmaWxlcyIsCiAgICAgICJhbGxvd19zeW1saW5rcyI6IGZhbHNlLAogICAgICAiaWdub3JlX3BhdGhzIjogWwogICAgICAgICIuZ2l0aHViIiwKICAgICAgICAiLmdpdCIsCiAgICAgICAgIi5naXRpZ25vcmUiLAogICAgICAgICIuZ2l0YXR0cmlidXRlcyIKICAgICAgXSwKICAgICAgImhhc2hfdHlwZSI6ICJzaGEyNTYiCiAgICB9CiAgfQp9","payloadType":"application/vnd.in-toto+json","signatures":[{"sig":"MGQCMDWygNgixRS5Om5qKqmF0KeGegPpXb3Hqk6XlkrTnKM7l+9zOLmhYcyML1ncceiL9AIwVNBfYl9mWsMkkd1fNSALxLytTGTtohgh1wSZwPhZCgKwMRWJL7aX5dwl7F8E7n9R","keyid":""}]}} \ No newline at end of file