diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 69ee8fb1..670857be 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,7 +5,7 @@ Use [abseil-py](https://github.com/abseil/abseil-py/tree/main)'s **logging**, **testing** and **flags** instead of Python's own **logging**, **unittest** and **argparse**. We use [uv](https://docs.astral.sh/uv/) for managing dependencies. For reproducible builds, our project tracks the generated `uv.lock` file in the repository. -On a weekly basis, the CI attemps an update of the lock file to test against upstream dependencies. +On a weekly basis, the CI attempts an update of the lock file to test against upstream dependencies. New required dependencies can be added by `uv add $DEPENDENCY`. @@ -47,7 +47,7 @@ We generally follow [Google's style guides](https://google.github.io/styleguide/ Although common, **mixed case is not allowed** in any code. -Run pre-commit at local before submitting merge request. You can also read [.pre-commit-config.yaml]( .pre-commit-config.yaml) to understand what are being forced. The **flake8** and **mypy** settings are inherited from PyTorch. +Run pre-commit at local before submitting merge request. You can also read [.pre-commit-config.yaml]( .pre-commit-config.yaml) to understand what are being forced. The **mypy** settings are inherited from PyTorch. ## Test diff --git a/README.md b/README.md index cb91a008..c7fec98f 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ Refer to tests for usage of different optimizers, e.g. [`tests/test_orthogonali ### Integration with Megatron Core -Integration with Megatron Core is available in **dev** branch, e.g. [muon.py](https://github.com/NVIDIA/Megatron-LM/blob/dev/megatron/core/optimizer/muon.py) +Integration with Megatron Core is available in **dev** branch, e.g. [emerging_optimizers.py](https://github.com/NVIDIA/Megatron-LM/blob/dev/megatron/core/optimizer/emerging_optimizers.py) ## Benchmarks diff --git a/docs/apidocs/soap.md b/docs/apidocs/soap.md index b22c4a57..ac6c580b 100644 --- a/docs/apidocs/soap.md +++ b/docs/apidocs/soap.md @@ -22,7 +22,7 @@ emerging_optimizers.soap .. autofunction:: update_kronecker_factors_kl_shampoo -.. autofunction:: update_eigenbasis_and_momentum +.. autofunction:: update_eigenbasis_and_exp_avgs :hidden:`REKLS` diff --git a/docs/conf.py b/docs/conf.py index b4e45bbe..e37f1d4b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -30,7 +30,7 @@ project = "Emerging-Optimizers" copyright = "2025, NVIDIA Corporation" author = "NVIDIA Corporation" -release = "0.1.0" +release = "0.2.0" # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration diff --git a/emerging_optimizers/orthogonalized_optimizers/muon_hyperball.py b/emerging_optimizers/orthogonalized_optimizers/muon_hyperball.py index 5138ec37..c9a8c09d 100644 --- a/emerging_optimizers/orthogonalized_optimizers/muon_hyperball.py +++ b/emerging_optimizers/orthogonalized_optimizers/muon_hyperball.py @@ -17,12 +17,14 @@ import torch +from emerging_optimizers import registry from emerging_optimizers.orthogonalized_optimizers import muon __all__ = ["MuonHyperball"] +@registry.register_optimizer("muon_hyperball") class MuonHyperball(muon.Muon): """Muon optimizer with hyperball-style norm-preserving weight updates. diff --git a/emerging_optimizers/riemannian_optimizers/__init__.py b/emerging_optimizers/riemannian_optimizers/__init__.py new file mode 100644 index 00000000..eb0254b0 --- /dev/null +++ b/emerging_optimizers/riemannian_optimizers/__init__.py @@ -0,0 +1,15 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from emerging_optimizers.riemannian_optimizers.normalized_optimizer import * diff --git a/emerging_optimizers/riemannian_optimizers/normalized_optimizer.py b/emerging_optimizers/riemannian_optimizers/normalized_optimizer.py index 4b13270b..fc30f117 100644 --- a/emerging_optimizers/riemannian_optimizers/normalized_optimizer.py +++ b/emerging_optimizers/riemannian_optimizers/normalized_optimizer.py @@ -21,7 +21,13 @@ import torch from torch.optim.optimizer import Optimizer +from emerging_optimizers import registry + +__all__ = ["ObliqueSGD", "ObliqueAdam"] + + +@registry.register_optimizer("oblique_sgd") class ObliqueSGD(Optimizer): """SGD optimizer for row- or column-normalized 2D parameters on oblique manifolds. @@ -120,6 +126,7 @@ def step(self, closure: Callable[[], float] | None = None) -> float | None: return loss +@registry.register_optimizer("oblique_adam") class ObliqueAdam(Optimizer): """Adam optimizer for row- or column-normalized 2D parameters on oblique manifolds. diff --git a/emerging_optimizers/soap/soap.py b/emerging_optimizers/soap/soap.py index 9e4c5def..6a39c4ec 100644 --- a/emerging_optimizers/soap/soap.py +++ b/emerging_optimizers/soap/soap.py @@ -36,7 +36,7 @@ "precondition", "init_kronecker_factors", "update_kronecker_factors", - "update_eigenbasis_and_momentum", + "update_eigenbasis_and_exp_avgs", ] @@ -194,7 +194,6 @@ def step(self, closure: Callable[[], float] | None = None) -> float | None: if p.grad is None: continue - # TODO(skyw): Fix the double cast. It is casted once in _init_group and once here. grad = p.grad.to(torch.float32) state = self.state[p] @@ -252,11 +251,11 @@ def step(self, closure: Callable[[], float] | None = None) -> float | None: ) if not skip_update: with utils.fp32_matmul_precision(self.qr_fp32_matmul_prec): - updated_eigenbasis_list, exp_avg, exp_avg_sq = update_eigenbasis_and_momentum( + updated_eigenbasis_list, exp_avg, exp_avg_sq = update_eigenbasis_and_exp_avgs( kronecker_factor_list=kronecker_factor_list, eigenbasis_list=eigenbasis_list, exp_avg_sq=state["exp_avg_sq"], - momentum=state["exp_avg"], + exp_avg=state["exp_avg"], use_eigh=use_eigh, power_iter_steps=self.power_iter_steps, ) @@ -436,22 +435,22 @@ def update_kronecker_factors_kl_shampoo( @torch.no_grad() # type: ignore[misc] -def update_eigenbasis_and_momentum( +def update_eigenbasis_and_exp_avgs( kronecker_factor_list: list[torch.Tensor], eigenbasis_list: list[torch.Tensor], exp_avg_sq: torch.Tensor, - momentum: torch.Tensor, + exp_avg: torch.Tensor, use_eigh: bool = False, power_iter_steps: int = 1, ) -> tuple[list[torch.Tensor], torch.Tensor, torch.Tensor]: - """Updates the eigenbases using QR decomposition and power iteration or eigh. + """Updates the eigenbases and moving averages. This function performs an update of the eigenbases (QL and QR) used for preconditioning. It follows these steps: - 1. Projects momentum back to the original basis + 1. Projects exp_avg back to the original basis 2. Updates the eigenbases using QR decomposition and power iteration (orthogonal iteration) - 3. Projects momentum back to the new eigenbasis + 3. Projects exp_avg back to the new eigenbasis Args: kronecker_factor_list: List of preconditioner matrices (L and R) that define @@ -460,7 +459,7 @@ def update_eigenbasis_and_momentum( used for preconditioning. These will be updated by this function. exp_avg_sq: Inner Adam's second moment tensor, used for scaling the preconditioner updates. This tensor is modified in-place. - momentum: Inner Adam's first moment tensor, used for tracking gradient momentum. + exp_avg: Inner Adam's first moment tensor, used for tracking gradient momentum. This tensor is modified in-place. use_eigh: Whether to use full symmetric eigendecomposition (eigh) to compute the eigenbasis. If False, use orthogonal iteration to compute the eigenbasis. @@ -470,7 +469,8 @@ def update_eigenbasis_and_momentum( Returns: A tuple containing: - Updated list of eigenbases (QL and QR) - - Updated momentum tensor projected to the new eigenbasis + - Updated exp_avg tensor projected to the new eigenbasis + - Updated exp_avg_sq tensor Example: >>> L = torch.randn(10, 10) @@ -478,15 +478,15 @@ def update_eigenbasis_and_momentum( >>> QL = torch.randn(10, 10) >>> QR = torch.randn(20, 20) >>> exp_avg_sq = torch.randn(10, 20) - >>> momentum = torch.randn(10, 20) - >>> updated_eigenbases = update_eigenbasis( - ... [L, R], [QL, QR], exp_avg_sq, momentum) + >>> exp_avg = torch.randn(10, 20) + >>> updated_eigenbasis_list, updated_exp_avg, updated_exp_avg_sq = update_eigenbasis_and_exp_avgs( + ... [L, R], [QL, QR], exp_avg_sq, exp_avg) """ - # Step 1: Project momentum back to the original basis + # Step 1: Project exp_avg back to the original basis torch.cuda.nvtx.range_push("eigenbasis update step 1: precondition") - momentum = precondition( - momentum, + exp_avg = precondition( + exp_avg, eigenbasis_list, dims=[[0], [1]], ) @@ -508,16 +508,16 @@ def update_eigenbasis_and_momentum( ) torch.cuda.nvtx.range_pop() - # Step 3: Project momentum to the new eigenbasis using the updated eigenbases - torch.cuda.nvtx.range_push("eigenbasis update step 3: project momentum") - momentum = precondition( - momentum, + # Step 3: Project exp_avg to the new eigenbasis using the updated eigenbases + torch.cuda.nvtx.range_push("eigenbasis update step 3: project exp_avg") + exp_avg = precondition( + exp_avg, updated_eigenbasis_list, dims=[[0], [0]], ) torch.cuda.nvtx.range_pop() - return updated_eigenbasis_list, momentum, exp_avg_sq + return updated_eigenbasis_list, exp_avg, exp_avg_sq @torch.no_grad() # type: ignore[misc] diff --git a/pyproject.toml b/pyproject.toml index 076da028..603e0d2f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,8 +76,6 @@ docs = [ ] test = [ "coverage>=7.8.1", - "flake8>=7.2.0", - "pylint>=3.3.7", "triton>=3.4.0", ] dev = [ diff --git a/tests/test_soap.py b/tests/test_soap.py index 8938f9cf..25ce45b0 100644 --- a/tests/test_soap.py +++ b/tests/test_soap.py @@ -273,12 +273,12 @@ def test_clip_update_rms(self, max_rms: float) -> None: N=[4, 8, 33], use_eigh=[True, False], ) - def test_update_eigenbasis_and_momentum(self, M: int, N: int, use_eigh: bool) -> None: - """Tests that update_eigenbasis_and_momentum returns valid outputs. + def test_update_eigenbasis_and_exp_avgs(self, M: int, N: int, use_eigh: bool) -> None: + """Tests that update_eigenbasis_and_exp_avgs returns valid outputs. Verifies output shapes, eigenbasis orthogonality, and that the round-trip projection (original → eigenbasis → original → new eigenbasis) preserves the - norm of momentum. + norm of exp_avg. """ # Create symmetric positive definite kronecker factors g = torch.randn(M, N, device=self.device) @@ -292,14 +292,14 @@ def test_update_eigenbasis_and_momentum(self, M: int, N: int, use_eigh: bool) -> eigenbasis_list = [Q_L, Q_R] exp_avg_sq = torch.abs(torch.randn(M, N, device=self.device)) - momentum = torch.randn(M, N, device=self.device) - momentum_norm_before = torch.linalg.norm(momentum) + exp_avg = torch.randn(M, N, device=self.device) + exp_avg_norm_before = torch.linalg.norm(exp_avg) - updated_eigenbasis_list, updated_momentum, updated_exp_avg_sq = soap.update_eigenbasis_and_momentum( + updated_eigenbasis_list, updated_exp_avg, updated_exp_avg_sq = soap.update_eigenbasis_and_exp_avgs( kronecker_factor_list=kronecker_factor_list, eigenbasis_list=eigenbasis_list, exp_avg_sq=exp_avg_sq, - momentum=momentum, + exp_avg=exp_avg, use_eigh=use_eigh, ) @@ -307,7 +307,7 @@ def test_update_eigenbasis_and_momentum(self, M: int, N: int, use_eigh: bool) -> self.assertEqual(len(updated_eigenbasis_list), 2) self.assertEqual(updated_eigenbasis_list[0].shape, (M, M)) self.assertEqual(updated_eigenbasis_list[1].shape, (N, N)) - self.assertEqual(updated_momentum.shape, (M, N)) + self.assertEqual(updated_exp_avg.shape, (M, N)) self.assertEqual(updated_exp_avg_sq.shape, (M, N)) # Check eigenbasis orthogonality @@ -321,13 +321,13 @@ def test_update_eigenbasis_and_momentum(self, M: int, N: int, use_eigh: bool) -> msg="Updated eigenbasis is not orthogonal.", ) - # Momentum is projected via orthogonal transforms, so norm should be preserved + # exp_avg is projected via orthogonal transforms, so norm should be preserved torch.testing.assert_close( - torch.linalg.norm(updated_momentum), - momentum_norm_before, + torch.linalg.norm(updated_exp_avg), + exp_avg_norm_before, atol=1e-5, rtol=1e-5, - msg="Momentum norm not preserved after eigenbasis update.", + msg="exp_avg norm not preserved after eigenbasis update.", ) @parameterized.parameters( diff --git a/uv.lock b/uv.lock index 79d4bb5b..1c464550 100644 --- a/uv.lock +++ b/uv.lock @@ -222,15 +222,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/08/b6/fff6609354deba9aeec466e4bcaeb9d1ed3e5d60b14b57df2a36fb2273f2/coverage-7.10.5-py3-none-any.whl", hash = "sha256:0be24d35e4db1d23d0db5c0f6a74a962e2ec83c426b5cac09f4234aadef38e4a", size = 208736, upload-time = "2025-08-23T14:42:43.145Z" }, ] -[[package]] -name = "dill" -version = "0.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/12/80/630b4b88364e9a8c8c5797f4602d0f76ef820909ee32f0bacb9f90654042/dill-0.4.0.tar.gz", hash = "sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0", size = 186976, upload-time = "2025-04-16T00:41:48.867Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl", hash = "sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049", size = 119668, upload-time = "2025-04-16T00:41:47.671Z" }, -] - [[package]] name = "distlib" version = "0.4.0" @@ -276,8 +267,6 @@ docs = [ ] test = [ { name = "coverage" }, - { name = "flake8" }, - { name = "pylint" }, { name = "triton" }, ] @@ -306,8 +295,6 @@ docs = [ ] test = [ { name = "coverage", specifier = ">=7.8.1" }, - { name = "flake8", specifier = ">=7.2.0" }, - { name = "pylint", specifier = ">=3.3.7" }, { name = "triton", specifier = ">=3.4.0" }, ] @@ -320,20 +307,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/42/14/42b2651a2f46b022ccd948bca9f2d5af0fd8929c4eec235b8d6d844fbe67/filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d", size = 15988, upload-time = "2025-08-14T16:56:01.633Z" }, ] -[[package]] -name = "flake8" -version = "7.3.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mccabe" }, - { name = "pycodestyle" }, - { name = "pyflakes" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9b/af/fbfe3c4b5a657d79e5c47a2827a362f9e1b763336a52f926126aa6dc7123/flake8-7.3.0.tar.gz", hash = "sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872", size = 48326, upload-time = "2025-06-20T19:31:35.838Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9f/56/13ab06b4f93ca7cac71078fbe37fcea175d3216f31f85c3168a6bbd0bb9a/flake8-7.3.0-py2.py3-none-any.whl", hash = "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e", size = 57922, upload-time = "2025-06-20T19:31:34.425Z" }, -] - [[package]] name = "fsspec" version = "2025.9.0" @@ -379,15 +352,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769, upload-time = "2022-07-01T12:21:02.467Z" }, ] -[[package]] -name = "isort" -version = "6.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b8/21/1e2a441f74a653a144224d7d21afe8f4169e6c7c20bb13aec3a2dc3815e0/isort-6.0.1.tar.gz", hash = "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450", size = 821955, upload-time = "2025-02-26T21:13:16.955Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/11/114d0a5f4dabbdcedc1125dee0888514c3c3b16d3e9facad87ed96fad97c/isort-6.0.1-py3-none-any.whl", hash = "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615", size = 94186, upload-time = "2025-02-26T21:13:14.911Z" }, -] - [[package]] name = "jinja2" version = "3.1.6" @@ -450,15 +414,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, ] -[[package]] -name = "mccabe" -version = "0.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658, upload-time = "2022-01-24T01:14:51.113Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350, upload-time = "2022-01-24T01:14:49.62Z" }, -] - [[package]] name = "mdit-py-plugins" version = "0.5.0" @@ -746,15 +701,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl", hash = "sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8", size = 220965, upload-time = "2025-08-09T18:56:13.192Z" }, ] -[[package]] -name = "pycodestyle" -version = "2.14.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/11/e0/abfd2a0d2efe47670df87f3e3a0e2edda42f055053c85361f19c0e2c1ca8/pycodestyle-2.14.0.tar.gz", hash = "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783", size = 39472, upload-time = "2025-06-20T18:49:48.75Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl", hash = "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d", size = 31594, upload-time = "2025-06-20T18:49:47.491Z" }, -] - [[package]] name = "pydata-sphinx-theme" version = "0.16.1" @@ -773,15 +719,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e2/0d/8ba33fa83a7dcde13eb3c1c2a0c1cc29950a048bfed6d9b0d8b6bd710b4c/pydata_sphinx_theme-0.16.1-py3-none-any.whl", hash = "sha256:225331e8ac4b32682c18fcac5a57a6f717c4e632cea5dd0e247b55155faeccde", size = 6723264, upload-time = "2024-12-17T10:53:35.645Z" }, ] -[[package]] -name = "pyflakes" -version = "3.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/45/dc/fd034dc20b4b264b3d015808458391acbf9df40b1e54750ef175d39180b1/pyflakes-3.4.0.tar.gz", hash = "sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58", size = 64669, upload-time = "2025-06-20T18:45:27.834Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/2f/81d580a0fb83baeb066698975cb14a618bdbed7720678566f1b046a95fe8/pyflakes-3.4.0-py2.py3-none-any.whl", hash = "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f", size = 63551, upload-time = "2025-06-20T18:45:26.937Z" }, -] - [[package]] name = "pygments" version = "2.19.2" @@ -791,24 +728,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, ] -[[package]] -name = "pylint" -version = "3.3.8" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "astroid" }, - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "dill" }, - { name = "isort" }, - { name = "mccabe" }, - { name = "platformdirs" }, - { name = "tomlkit" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9d/58/1f614a84d3295c542e9f6e2c764533eea3f318f4592dc1ea06c797114767/pylint-3.3.8.tar.gz", hash = "sha256:26698de19941363037e2937d3db9ed94fb3303fdadf7d98847875345a8bb6b05", size = 1523947, upload-time = "2025-08-09T09:12:57.234Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2d/1a/711e93a7ab6c392e349428ea56e794a3902bb4e0284c1997cff2d7efdbc1/pylint-3.3.8-py3-none-any.whl", hash = "sha256:7ef94aa692a600e82fabdd17102b73fc226758218c97473c7ad67bd4cb905d83", size = 523153, upload-time = "2025-08-09T09:12:54.836Z" }, -] - [[package]] name = "pyyaml" version = "6.0.2" @@ -1095,15 +1014,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" }, ] -[[package]] -name = "tomlkit" -version = "0.13.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cc/18/0bbf3884e9eaa38819ebe46a7bd25dcd56b67434402b66a58c4b8e552575/tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1", size = 185207, upload-time = "2025-06-05T07:13:44.947Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/75/8539d011f6be8e29f339c42e633aae3cb73bffa95dd0f9adec09b9c58e85/tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0", size = 38901, upload-time = "2025-06-05T07:13:43.546Z" }, -] - [[package]] name = "torch" version = "2.8.0"