Compare commits
12 Commits
c473e48b5b
...
0.3.0
| Author | SHA1 | Date | |
|---|---|---|---|
| ba0c804d5e | |||
| b59749c8d8 | |||
| a395a08d5c | |||
| 85d176862e | |||
| 9a0b0e5626 | |||
| 69e8e88047 | |||
| 30fb6fa107 | |||
| 95d7bc68ce | |||
| c2e4294c8c | |||
| e867bc0e7f | |||
| faeef9c72a | |||
| 805262dfc4 |
21
README.md
21
README.md
@@ -1,17 +1,12 @@
|
||||
# Overview
|
||||
Package summary goes here, ideally with a diagram
|
||||
Minimal framework for ML modeling, supporting advanced dataset operations and
|
||||
streamlined training workflows.
|
||||
|
||||
# Install
|
||||
Installation instructions
|
||||
The `trainlib` package can be installed from PyPI:
|
||||
|
||||
```sh
|
||||
pip install <package>
|
||||
```
|
||||
|
||||
or as a CLI tool
|
||||
|
||||
```sh
|
||||
uv tool install <package>
|
||||
pip install trainlib
|
||||
```
|
||||
|
||||
# Development
|
||||
@@ -20,16 +15,16 @@ uv tool install <package>
|
||||
- Depending on needs, install the development dependencies with `uv sync
|
||||
--extra dev`.
|
||||
|
||||
# Testing
|
||||
## Testing
|
||||
- To run the unit tests, make sure to first have the test dependencies
|
||||
installed with `uv sync --extra test`, then run `make test`.
|
||||
- For notebook testing, run `make install-kernel` to make the environment
|
||||
available as a Jupyter kernel (to be selected when running notebooks).
|
||||
|
||||
# Documentation
|
||||
## Documentation
|
||||
- Install the documentation dependencies with `uv sync --extra doc`.
|
||||
- Run `make docs-build` (optionally preceded by `make docs-clean`), and serve
|
||||
locally with `docs-serve`.
|
||||
locally with `make docs-serve`.
|
||||
|
||||
# Development remarks
|
||||
- Across `Trainer` / `Estimator` / `Dataset`, I've considered a
|
||||
@@ -91,7 +86,7 @@ uv tool install <package>
|
||||
class SequenceDataset[I, **P](HomogenousDataset[int, I, I, P]):
|
||||
...
|
||||
|
||||
class TupleDataset[I](SequenceDataset[tuple[I, ...], ??]):
|
||||
class TupleDataset[I](SequenceDataset[tuple[I, ...], "?"]):
|
||||
...
|
||||
```
|
||||
|
||||
|
||||
11
TODO.md
Normal file
11
TODO.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# Long-term
|
||||
- Implement a dataloader in-house, with a clear, lightweight mechanism for
|
||||
collection-of-structures to structure-of-collections. For multi-proc handling
|
||||
(happens in torch's dataloader, as well as the BatchedDataset for two
|
||||
different purposes), we should rely on (a hopefully more stable) `execlib`.
|
||||
- `Domains` may be externalized (`co3` or `convlib`)
|
||||
- Up next: CLI, fully JSON-ification of model selection + train.
|
||||
- Consider a "multi-train" alternative (or arg support in `train()`) for
|
||||
training many "rollouts" from the same base estimator (basically forks under
|
||||
different seeds). For architecture benchmarking above all, seeing average
|
||||
training behavior. Consider corresponding `Plotter` methods (error bars)
|
||||
20
doc/Makefile
Normal file
20
doc/Makefile
Normal file
@@ -0,0 +1,20 @@
|
||||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line, and also
|
||||
# from the environment for the first two.
|
||||
SPHINXOPTS ?=
|
||||
SPHINXBUILD ?= sphinx-build
|
||||
SOURCEDIR = .
|
||||
BUILDDIR = _build
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: help Makefile
|
||||
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
9
doc/_templates/autosummary.md
vendored
Normal file
9
doc/_templates/autosummary.md
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
# {{ fullname | escape }}
|
||||
|
||||
```{automodule}
|
||||
{{ fullname }}
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
:imported-members:
|
||||
```
|
||||
8
doc/_templates/autosummary/module.rst
vendored
Normal file
8
doc/_templates/autosummary/module.rst
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
{{ fullname | escape | underline}}
|
||||
|
||||
.. automodule:: {{ fullname }}
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
:imported-members:
|
||||
|
||||
112
doc/conf.py
Normal file
112
doc/conf.py
Normal file
@@ -0,0 +1,112 @@
|
||||
# Configuration file for the Sphinx documentation builder.
|
||||
#
|
||||
# For the full list of built-in configuration values, see the documentation:
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
||||
|
||||
|
||||
# -- Styling: type hints ------------------------------------------------------
|
||||
# There are several possible style combinations for rendering types, none of
|
||||
# which are optimal in my view. The main switches are:
|
||||
#
|
||||
# - Parameter type hints in the signature vs in the separate parameter list
|
||||
# - Show type hints as plaintext vs rendered HTML elements
|
||||
#
|
||||
# The `sphinx_autodoc_typehints` extension enables more context-aware
|
||||
# rendering, but it's often way too explicit (e.g., unwrapping type variables)
|
||||
# and makes things difficult to read. It does, however, allow for automatic
|
||||
# inclusion of default values, which is nice.
|
||||
#
|
||||
# I'd like type hints to be rendered in an inline code element, but that
|
||||
# doesn't happen by default in either case unless you render them in the
|
||||
# signature. This is sloppy, however, often just a jumbled mess or parameter
|
||||
# names and types. The current preferred option is to just use the native
|
||||
# `autodoc` settings for rendering type hints, leaving them out of the
|
||||
# signature (for easy heading readability). Type hints in the parameter list
|
||||
# are also as short as possible, not rendered crazily (by default this is in
|
||||
# italics; not my favorite but it's what we have). No
|
||||
# `sphinx_autodoc_typehints` needed at this point; you can toggle it if you
|
||||
# want automatic default values or different formatting for type hints.
|
||||
|
||||
|
||||
# -- Project information ------------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
||||
|
||||
project = "trainlib"
|
||||
copyright = "2026, Sam Griesemer"
|
||||
author = "Sam Griesemer"
|
||||
|
||||
|
||||
# -- General configuration ----------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
||||
|
||||
extensions = [
|
||||
"sphinx.ext.autodoc",
|
||||
|
||||
# enables a directive to be specified manually that gathers module/object
|
||||
# summary details in a table
|
||||
"sphinx.ext.autosummary",
|
||||
|
||||
# allow viewing source in the HTML pages
|
||||
"sphinx.ext.viewcode",
|
||||
|
||||
# only really applies to manual docs; docstrings still need RST-like
|
||||
"myst_parser",
|
||||
|
||||
# enables Google-style docstring formats
|
||||
"sphinx.ext.napoleon",
|
||||
|
||||
# external extension that allows arg types to be inferred by type hints;
|
||||
# without this, type hints show up inside method signatures as plaintext,
|
||||
# but when enabled they are pulled into the parameter/description block and
|
||||
# rendered as native nested markup. What's best for a given package may
|
||||
# vary.
|
||||
# "sphinx_autodoc_typehints",
|
||||
]
|
||||
autosummary_generate = True
|
||||
autosummary_imported_members = True
|
||||
|
||||
# include __init__ definitions in autodoc
|
||||
autodoc_default_options = {
|
||||
"special-members": "__init__",
|
||||
}
|
||||
|
||||
templates_path = ["_templates"]
|
||||
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
|
||||
|
||||
|
||||
# -- Options for autodoc ------------------------------------------------------
|
||||
# class signatures show up only in __init__ rather than at the class header;
|
||||
# generally cleaner, avoids redundancy
|
||||
autodoc_class_signature = "separated"
|
||||
|
||||
# if `sphinx_autodoc_typehints` extension is enabled, this is redundant: type
|
||||
# hints are rendered natively and already show up in the parameter block. If
|
||||
# it's disabled, this setting will do the same job of moving the types to the
|
||||
# parameter block, but it renders them in plaintext (with links to in-package
|
||||
# type refs).
|
||||
autodoc_typehints = "description" # "signature"
|
||||
autodoc_typehints_format = "short"
|
||||
autodoc_preserve_defaults = True
|
||||
autodoc_use_type_comments = False
|
||||
python_use_unqualified_type_names = True
|
||||
|
||||
# push parameters to their own lines in the signature block
|
||||
# python_maximum_signature_line_length = 60
|
||||
|
||||
|
||||
# -- Options for autodoc_typehints --------------------------------------------
|
||||
# always_use_bars_union = True # always on for Python 3.14+
|
||||
# typehints_defaults = "braces-after" # render defaults in param block
|
||||
# typehints_use_signature = False # False is default; enable if wanted in sig
|
||||
# always_document_param_types = True # show types even when not in docstring
|
||||
|
||||
|
||||
# -- Options for HTML output --------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
||||
|
||||
html_theme = "furo" # "pydata_sphinx_theme"
|
||||
html_static_path = ["_static"]
|
||||
|
||||
# html_sidebars = {
|
||||
# '**': ['/modules.html'],
|
||||
# }
|
||||
37
doc/index.md
Normal file
37
doc/index.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# `trainlib` package docs
|
||||
|
||||
{ref}`genindex`
|
||||
{ref}`modindex`
|
||||
|
||||
```{eval-rst}
|
||||
.. autosummary::
|
||||
:nosignatures:
|
||||
:recursive:
|
||||
:caption: Modules
|
||||
|
||||
trainlib.dataset
|
||||
trainlib.domain
|
||||
trainlib.estimator
|
||||
trainlib.trainer
|
||||
trainlib.transform
|
||||
```
|
||||
|
||||
```{toctree}
|
||||
:maxdepth: 3
|
||||
:caption: Autoref
|
||||
:hidden:
|
||||
|
||||
_autoref/trainlib.rst
|
||||
```
|
||||
|
||||
```{toctree}
|
||||
:maxdepth: 3
|
||||
:caption: Contents
|
||||
:hidden:
|
||||
|
||||
reference/documentation/index
|
||||
```
|
||||
|
||||
```{include} ../README.md
|
||||
:heading-offset: 1
|
||||
```
|
||||
35
doc/make.bat
Normal file
35
doc/make.bat
Normal file
@@ -0,0 +1,35 @@
|
||||
@ECHO OFF
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set SOURCEDIR=.
|
||||
set BUILDDIR=_build
|
||||
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.https://www.sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
goto end
|
||||
|
||||
:help
|
||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
|
||||
:end
|
||||
popd
|
||||
5
doc/reference/documentation/index.md
Normal file
5
doc/reference/documentation/index.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Documentation
|
||||
|
||||
```{toctree}
|
||||
sphinx
|
||||
```
|
||||
111
doc/reference/documentation/sphinx.md
Normal file
111
doc/reference/documentation/sphinx.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# Sphinx
|
||||
The primary driver of this package's documentation is Sphinx's `autodoc` extension,
|
||||
using the [Furo theme][1].
|
||||
|
||||
**High-level details**:
|
||||
|
||||
- `sphinx-apidoc` generates package-based documentation to the `_autoref/` directory,
|
||||
with navigation available under "Autoref" in the sidebar.
|
||||
- Markdown-based documentation files are manually written under the `reference/`
|
||||
directory, showing up under "Contents" in the sidebar.
|
||||
|
||||
## Detailed directory structure
|
||||
All files are placed under `docs/sphinx`:
|
||||
|
||||
- `_`-prefixed are Sphinx-managed directories
|
||||
* `_build/html/` houses output HTML files
|
||||
* `_autoref/` is the target for module-based RST files written by `autodoc`
|
||||
- `reference/`: houses all manually written documentation (totally separate from
|
||||
auto-generated package docs)
|
||||
- `conf.py`: single Sphinx configuration file
|
||||
- `index.md`: documentation index, setups up a persistent sidebar across all other pages
|
||||
|
||||
For manually written documentation under `reference/`, topics are nested as needed. Within
|
||||
a nested directory `reference/<topic>`, an `index.md` should created with content like:
|
||||
|
||||
```
|
||||
# <Topic>
|
||||
|
||||
\`\`\`{toctree}
|
||||
:hidden:
|
||||
|
||||
sub-topic-1.rst
|
||||
sub-topic-2.rst
|
||||
...
|
||||
\`\`\`
|
||||
```
|
||||
|
||||
This will add the nested directory to the sidebar navigation, using the name set under the
|
||||
top-level header. See [Markdown syntax][#markdown-syntax] for more details on the syntax.
|
||||
|
||||
## Sphinx autodoc
|
||||
Sphinx's `autodoc` extension allows automatic generation of documents according to
|
||||
(Python) subpackage structure and available docstrings. A few notes here:
|
||||
|
||||
- In the `conf.py` file, autodoc is enabled by adding `"sphinx.ext.autodoc"` to
|
||||
the extensions list. `"sphinx.ext.viewcode"` can also be added to provide
|
||||
links to source code.
|
||||
- Documents are actually generated by calling the `sphinx-apidoc` CLI command. The
|
||||
current Makefile uses the following call:
|
||||
|
||||
```sh
|
||||
sphinx-apidoc --module-first -o docs/sphinx/_autoref/ localsys
|
||||
```
|
||||
|
||||
This writes the automatically generated docs for modules in the package at the
|
||||
local directory `localsys/` to the `docs/sphinx/_autoref` directory. These are
|
||||
reStructuredText files by default.
|
||||
* `--module-first` places the module-level descriptions at the top of the module page.
|
||||
By default, this is placed at the bottom (oddly), and can be obscured by large lists
|
||||
of subpackages if this flag isn't provided.
|
||||
* See available `sphinx-apidoc` options [here][2], as well as more advanced config
|
||||
[here][3].
|
||||
|
||||
|
||||
## Markdown syntax
|
||||
The `myst_parser` extension enables Markdown (or something close to it) to be used when
|
||||
writing documentation files. The Sphinx directives can be difficult to track, and
|
||||
they change slightly under the MyST Markdown syntax. The following are a few common
|
||||
blocks:
|
||||
|
||||
**Page hierarchies**: the following will generate link hierarchy according to the provided
|
||||
pages:
|
||||
|
||||
```
|
||||
\`\`\`{toctree}
|
||||
:maxdepth: <n>
|
||||
:caption: <caption>
|
||||
:hidden:
|
||||
|
||||
example-file-1
|
||||
example-file-2
|
||||
example-dir/index
|
||||
...
|
||||
\`\`\`
|
||||
```
|
||||
|
||||
- `:maxdepth:` limits the depth of nesting
|
||||
- `:caption:` title for the group of pages
|
||||
- `:hidden:` if provided, links will only show in the sidebar (hidden on the page)
|
||||
- Constituent files: listed files will be rendered as a link directly. If a listed file
|
||||
has a `{toctree}` directive, this tree will be rendered in place of the page's link as a
|
||||
dropdown. The dropdown will be named according to the file's top-level heading, and
|
||||
clicking directly on the dropdown header will show that page's content. Files found in
|
||||
the tree will be placed as links under the dropdown, recursively subject to same rules
|
||||
described here.
|
||||
|
||||
**Include files**: the following will include file content
|
||||
pages:
|
||||
|
||||
```
|
||||
\`\`\`{include} README.md
|
||||
\`\`\`
|
||||
```
|
||||
|
||||
**Reference directives**
|
||||
|
||||
|
||||
[1]: https://pradyunsg.me/furo/
|
||||
[2]: https://www.sphinx-doc.org/en/master/man/sphinx-apidoc.html
|
||||
[3]: https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#
|
||||
|
||||
20
example/dataset.py
Normal file
20
example/dataset.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from typing import NamedTuple
|
||||
|
||||
from trainlib.domain import SequenceDomain
|
||||
from trainlib.datasets.memory import TupleDataset
|
||||
|
||||
|
||||
class Record(NamedTuple):
|
||||
a: int
|
||||
b: str
|
||||
|
||||
tl_domain = SequenceDomain[Record]([
|
||||
Record(1, "1"),
|
||||
Record(2, "2"),
|
||||
])
|
||||
|
||||
class R0(TupleDataset[Record]):
|
||||
item_tuple = Record
|
||||
|
||||
def _process_item_data(self, item_data, item_index):
|
||||
return (item_data[0],)
|
||||
29
example/example.json
Normal file
29
example/example.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"estimator_name": "mlp",
|
||||
"dataset_name": "random_xy_dataset",
|
||||
"dataloader_name": "supervised_data_loader",
|
||||
"estimator_kwargs": {
|
||||
"input_dim": 4,
|
||||
"output_dim": 2
|
||||
},
|
||||
"dataset_kwargs": {
|
||||
"num_samples": 100000,
|
||||
"preload": true,
|
||||
"input_dim": 4,
|
||||
"output_dim": 2
|
||||
},
|
||||
"dataset_split_fracs": {
|
||||
"train": 0.4,
|
||||
"val": 0.3,
|
||||
"aux": [0.3]
|
||||
},
|
||||
"dataloader_kwargs": {
|
||||
"batch_size": 16
|
||||
},
|
||||
"train_kwargs": {
|
||||
"summarize_every": 20,
|
||||
"max_epochs": 100,
|
||||
"stop_after_epochs": 100
|
||||
},
|
||||
"load_only": false
|
||||
}
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "trainlib"
|
||||
version = "0.1.0"
|
||||
version = "0.3.0"
|
||||
description = "Minimal framework for ML modeling. Supports advanced dataset operations and streamlined training."
|
||||
requires-python = ">=3.13"
|
||||
authors = [
|
||||
@@ -24,12 +24,13 @@ classifiers = [
|
||||
"Intended Audience :: End Users/Desktop",
|
||||
]
|
||||
dependencies = [
|
||||
"torch",
|
||||
"colorama>=0.4.6",
|
||||
"matplotlib>=3.10.8",
|
||||
"numpy>=2.4.1",
|
||||
"tensorboard>=2.20.0",
|
||||
"torch>=2.5.1",
|
||||
"tqdm>=4.67.1",
|
||||
"setuptools<=81.0.0", # here currently b/c tensorboard breaks @ v82.0.0
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
@@ -41,6 +42,7 @@ dev = [
|
||||
]
|
||||
doc = [
|
||||
"furo",
|
||||
# "pydata-sphinx-theme",
|
||||
"myst-parser",
|
||||
"sphinx",
|
||||
"sphinx-togglebutton",
|
||||
@@ -82,3 +84,11 @@ force-sort-within-sections = false
|
||||
quote-style = "double"
|
||||
indent-style = "space"
|
||||
docstring-code-format = true
|
||||
|
||||
[tool.uv.sources]
|
||||
torch = { index = "pytorch" }
|
||||
|
||||
[[tool.uv.index]]
|
||||
name = "pytorch"
|
||||
url = "https://download.pytorch.org/whl/cu128"
|
||||
explicit = true
|
||||
|
||||
18
trainlib/__main__.py
Normal file
18
trainlib/__main__.py
Normal file
@@ -0,0 +1,18 @@
|
||||
import logging
|
||||
|
||||
from trainlib.cli import create_parser
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = create_parser()
|
||||
args = parser.parse_args()
|
||||
|
||||
# skim off log level to handle higher-level option
|
||||
if hasattr(args, "log_level") and args.log_level is not None:
|
||||
logging.basicConfig(level=args.log_level)
|
||||
|
||||
args.func(args) if "func" in args else parser.print_help()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
26
trainlib/cli/__init__.py
Normal file
26
trainlib/cli/__init__.py
Normal file
@@ -0,0 +1,26 @@
|
||||
import logging
|
||||
import argparse
|
||||
|
||||
from trainlib.cli import train
|
||||
|
||||
logger: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def create_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="trainlib cli",
|
||||
# formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--log-level",
|
||||
type=int,
|
||||
metavar="int",
|
||||
choices=[10, 20, 30, 40, 50],
|
||||
help="Log level: 10=DEBUG, 20=INFO, 30=WARNING, 40=ERROR, 50=CRITICAL",
|
||||
)
|
||||
|
||||
subparsers = parser.add_subparsers(help="subcommand help")
|
||||
train.register_parser(subparsers)
|
||||
|
||||
return parser
|
||||
|
||||
164
trainlib/cli/train.py
Normal file
164
trainlib/cli/train.py
Normal file
@@ -0,0 +1,164 @@
|
||||
import gc
|
||||
import json
|
||||
import argparse
|
||||
from typing import Any
|
||||
from argparse import _SubParsersAction
|
||||
|
||||
import torch
|
||||
|
||||
from trainlib.trainer import Trainer
|
||||
from trainlib.datasets import dataset_map
|
||||
from trainlib.estimator import Estimator
|
||||
from trainlib.estimators import estimator_map
|
||||
from trainlib.dataloaders import dataloader_map
|
||||
|
||||
|
||||
def prepare_run() -> None:
|
||||
# prepare cuda memory
|
||||
memory_allocated = torch.cuda.memory_allocated() / 1024**3 # GB
|
||||
print(f"CUDA allocated: {memory_allocated}GB")
|
||||
if torch.cuda.is_available():
|
||||
torch.cuda.empty_cache()
|
||||
gc.collect()
|
||||
|
||||
|
||||
def run(
|
||||
estimator_name: str,
|
||||
dataset_name: str,
|
||||
dataloader_name: str,
|
||||
estimator_kwargs: dict[str, Any] | None = None,
|
||||
dataset_kwargs: dict[str, Any] | None = None,
|
||||
dataset_split_fracs: dict[str, Any] | None = None,
|
||||
dataset_split_kwargs: dict[str, Any] | None = None,
|
||||
dataloader_kwargs: dict[str, Any] | None = None,
|
||||
trainer_kwargs: dict[str, Any] | None = None,
|
||||
train_kwargs: dict[str, Any] | None = None,
|
||||
load_only: bool = False,
|
||||
) -> Trainer | Estimator:
|
||||
|
||||
try:
|
||||
estimator_cls = estimator_map[estimator_name]
|
||||
except KeyError as err:
|
||||
raise ValueError(
|
||||
f"Invalid estimator name '{estimator_name}',"
|
||||
f"must be one of {estimator_map.keys()}"
|
||||
) from err
|
||||
|
||||
try:
|
||||
dataset_cls = dataset_map[dataset_name]
|
||||
except KeyError as err:
|
||||
raise ValueError(
|
||||
f"Invalid dataset name '{dataset_name}',"
|
||||
f"must be one of {dataset_map.keys()}"
|
||||
) from err
|
||||
|
||||
try:
|
||||
dataloader_cls = dataloader_map[dataloader_name]
|
||||
except KeyError as err:
|
||||
raise ValueError(
|
||||
f"Invalid dataloader name '{dataloader_name}',"
|
||||
f"must be one of {dataloader_map.keys()}"
|
||||
) from err
|
||||
|
||||
estimator_kwargs = estimator_kwargs or {}
|
||||
dataset_kwargs = dataset_kwargs or {}
|
||||
dataset_split_fracs = dataset_split_fracs or {}
|
||||
dataset_split_kwargs = dataset_split_kwargs or {}
|
||||
dataloader_kwargs = dataloader_kwargs or {}
|
||||
trainer_kwargs = trainer_kwargs or {}
|
||||
train_kwargs = train_kwargs or {}
|
||||
|
||||
default_estimator_kwargs = {}
|
||||
default_dataset_kwargs = {}
|
||||
default_dataset_split_kwargs = {}
|
||||
default_dataset_split_fracs = {"train": 1.0, "val": 0.0, "aux": []}
|
||||
default_dataloader_kwargs = {}
|
||||
default_trainer_kwargs = {}
|
||||
default_train_kwargs = {}
|
||||
|
||||
estimator_kwargs = {**default_estimator_kwargs, **estimator_kwargs}
|
||||
dataset_kwargs = {**default_dataset_kwargs, **dataset_kwargs}
|
||||
dataset_split_kwargs = {**default_dataset_split_kwargs, **dataset_split_kwargs}
|
||||
dataset_split_fracs = {**default_dataset_split_fracs, **dataset_split_fracs}
|
||||
dataloader_kwargs = {**default_dataloader_kwargs, **dataloader_kwargs}
|
||||
trainer_kwargs = {**default_trainer_kwargs, **trainer_kwargs}
|
||||
train_kwargs = {**default_train_kwargs, **train_kwargs}
|
||||
|
||||
estimator = estimator_cls(**estimator_kwargs)
|
||||
dataset = dataset_cls(**dataset_kwargs)
|
||||
train_dataset, val_dataset, *aux_datasets = dataset.split(
|
||||
fracs=[
|
||||
dataset_split_fracs["train"],
|
||||
dataset_split_fracs["val"],
|
||||
*dataset_split_fracs["aux"]
|
||||
],
|
||||
**dataset_split_kwargs
|
||||
)
|
||||
train_loader = dataloader_cls(train_dataset, **dataloader_kwargs)
|
||||
val_loader = dataloader_cls(val_dataset, **dataloader_kwargs)
|
||||
aux_loaders = [
|
||||
dataloader_cls(aux_dataset, **dataloader_kwargs)
|
||||
for aux_dataset in aux_datasets
|
||||
]
|
||||
|
||||
trainer = Trainer(
|
||||
estimator,
|
||||
**trainer_kwargs,
|
||||
)
|
||||
|
||||
if load_only:
|
||||
return trainer
|
||||
|
||||
return trainer.train(
|
||||
train_loader=train_loader,
|
||||
val_loader=val_loader,
|
||||
aux_loaders=aux_loaders,
|
||||
**train_kwargs,
|
||||
)
|
||||
|
||||
|
||||
def run_from_json(
|
||||
parameters_json: str | None = None,
|
||||
parameters_file: str | None = None,
|
||||
) -> Trainer | Estimator:
|
||||
if not (parameters_json or parameters_file):
|
||||
raise ValueError("parameter json or file required")
|
||||
|
||||
parameters: dict[str, Any]
|
||||
if parameters_json:
|
||||
try:
|
||||
parameters = json.loads(parameters_json)
|
||||
except json.JSONDecodeError as e:
|
||||
raise ValueError(f"Invalid JSON format: {e}") from e
|
||||
except Exception as e:
|
||||
raise ValueError(f"Error loading JSON parameters: {e}") from e
|
||||
|
||||
elif parameters_file:
|
||||
try:
|
||||
with open(parameters_file, encoding="utf-8") as f:
|
||||
parameters = json.load(f)
|
||||
except FileNotFoundError as e:
|
||||
raise ValueError(f"JSON file not found: {parameters_file}") from e
|
||||
except Exception as e:
|
||||
raise ValueError(f"Error loading JSON parameters: {e}") from e
|
||||
|
||||
return run(**parameters)
|
||||
|
||||
|
||||
def handle_train(args: argparse.Namespace) -> None:
|
||||
run_from_json(args.parameters_json, args.parameters_file)
|
||||
|
||||
|
||||
def register_parser(subparsers: _SubParsersAction) -> None:
|
||||
parser = subparsers.add_parser("train", help="run training loop")
|
||||
parser.add_argument(
|
||||
"--parameters-json",
|
||||
type=str,
|
||||
help="Raw JSON string with train parameters",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--parameters-file",
|
||||
type=str,
|
||||
help="Path to JSON file with train parameters",
|
||||
)
|
||||
parser.set_defaults(func=handle_train)
|
||||
102
trainlib/dataloader.py
Normal file
102
trainlib/dataloader.py
Normal file
@@ -0,0 +1,102 @@
|
||||
"""
|
||||
This class took me a long time to really settle into. It's a connector, and it
|
||||
feels redundant in many ways, so I've nearly deleted it several times while
|
||||
talking through the design. But in total, I think it serves a clear purpose.
|
||||
Reasons:
|
||||
|
||||
- Need a typed dataloader, even if I know the type of my attached transform
|
||||
- Need a new scope that uses the same base dataset without interfering with the
|
||||
transform attribute; a design that sets or relies on that is subject to
|
||||
conflict
|
||||
|
||||
- Why not just use vanilla DataLoaders?
|
||||
|
||||
I'd like to, but the two reasons above make it clear why this is challenging:
|
||||
I don't get static checks on the structures returned during iteration, and
|
||||
while you can control ad hoc data transforms via dataset ``post_transforms``,
|
||||
things can get messy if you need to do that for many transforms using the
|
||||
same dataset (without copying). Simplest way around this is just a new scope
|
||||
with the same underlying dataset instance and a transform wrapper around the
|
||||
iterator; no interference with object attributes.
|
||||
|
||||
This is really just meant as the minimum viable logic needed to accomplish the
|
||||
above - it's a very lightweight wrapper on the base ``DataLoader`` object.
|
||||
There's an explicit type upper bound ``Kw: EstimatorKwargs``, but it is
|
||||
otherwise a completely general transform over dataloader batches, highlighting
|
||||
that it's *mostly* here to place nice with type checks.
|
||||
"""
|
||||
|
||||
from typing import Unpack
|
||||
from collections.abc import Iterator
|
||||
|
||||
from torch.utils.data import DataLoader
|
||||
|
||||
from trainlib.dataset import BatchedDataset
|
||||
from trainlib.estimator import EstimatorKwargs
|
||||
from trainlib.utils.type import LoaderKwargs
|
||||
|
||||
|
||||
class EstimatorDataLoader[B, Kw: EstimatorKwargs]:
|
||||
"""
|
||||
Data loaders for estimators.
|
||||
|
||||
This class exists to connect batched data from datasets to the expected
|
||||
representation for estimator methods. Datasets may be developed
|
||||
independently from a given model structures, and models should be trainable
|
||||
under any such data. We need a way to ensure the batched groups of items we
|
||||
get from dataloaders match on a type level, i.e., can be reshaped into the
|
||||
expected ``Kw`` signature.
|
||||
|
||||
Note: batch structure ``B`` cannot be directly inferred from type variables
|
||||
exposed by ``BatchedDatasets`` (namely ``R`` and ``I``). What's returned by
|
||||
a data loader wrapping any such dataset can be arbitrary (depending on the
|
||||
``collate_fn``), with default behavior being fairly consistent under nested
|
||||
collections but challenging to accurately type.
|
||||
|
||||
.. todo::
|
||||
|
||||
To log (have changed for Trainer):
|
||||
|
||||
- New compact eval pipeline for train/val/auxiliary dataloaders.
|
||||
Somewhat annoying logic, but handled consistently
|
||||
- Convergence tracker will dynamically use training loss (early
|
||||
stopping) when a validation set isn't provided. Same mechanics for
|
||||
stagnant epochs (although early stopping is generally a little more
|
||||
nuanced, having a rate-based stopper, b/c train loss generally quite
|
||||
monotonic). So that's to be updated, plus room for possible model
|
||||
selection strategies later.
|
||||
- Logging happens at each batch, but we append to an epoch-indexed list
|
||||
and later average. There was a bug in the last round of testing that
|
||||
I didn't pick up where I was just overwriting summaries using the
|
||||
last seen batch.
|
||||
- Reworked general dataset/dataloader handling for main train loop, now
|
||||
accepting objects of this class to bridge estimator and dataset
|
||||
communication. This cleans up the batch mapping model.
|
||||
- TODO: implement a version of this that canonically works with the
|
||||
device passing plus EstimatorKwargs input; this is the last fuzzy bit
|
||||
I think.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
dataset: BatchedDataset,
|
||||
**dataloader_kwargs: Unpack[LoaderKwargs],
|
||||
) -> None:
|
||||
self._dataloader = DataLoader(dataset, **dataloader_kwargs)
|
||||
|
||||
def batch_to_est_kwargs(self, batch_data: B) -> Kw:
|
||||
"""
|
||||
.. note::
|
||||
|
||||
Even if we have a concrete shape for the output kwarg dict for base
|
||||
estimators (requiring a tensor "inputs" attribute), we don't
|
||||
presuppose how a given batch object will map into this dict
|
||||
structure.
|
||||
|
||||
return EstimatorKwargs({"inputs":0})
|
||||
"""
|
||||
|
||||
raise NotImplementedError
|
||||
|
||||
def __iter__(self) -> Iterator[Kw]:
|
||||
return map(self.batch_to_est_kwargs, self._dataloader)
|
||||
12
trainlib/dataloaders/__init__.py
Normal file
12
trainlib/dataloaders/__init__.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from trainlib.dataloader import EstimatorDataLoader
|
||||
from trainlib.utils.text import camel_to_snake
|
||||
from trainlib.dataloaders.memory import SupervisedDataLoader
|
||||
|
||||
_dataloaders = [
|
||||
SupervisedDataLoader,
|
||||
]
|
||||
dataloader_map: dict[str, type[EstimatorDataLoader]] = {
|
||||
camel_to_snake(_dataloader.__name__): _dataloader
|
||||
for _dataloader in _dataloaders
|
||||
}
|
||||
|
||||
17
trainlib/dataloaders/memory.py
Normal file
17
trainlib/dataloaders/memory.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from torch import Tensor
|
||||
|
||||
from trainlib.estimator import SupervisedKwargs
|
||||
from trainlib.dataloader import EstimatorDataLoader
|
||||
|
||||
|
||||
class SupervisedDataLoader(
|
||||
EstimatorDataLoader[tuple[Tensor, Tensor], SupervisedKwargs]
|
||||
):
|
||||
def batch_to_est_kwargs(
|
||||
self,
|
||||
batch_data: tuple[Tensor, Tensor]
|
||||
) -> SupervisedKwargs:
|
||||
return SupervisedKwargs(
|
||||
inputs=batch_data[0],
|
||||
labels=batch_data[1],
|
||||
)
|
||||
@@ -1,121 +1,62 @@
|
||||
"""
|
||||
Marginalizing out the modality layer:
|
||||
Domain-generic dataset base with attribute-based splitting and balancing
|
||||
|
||||
With ``domain`` being an instance variable, one possible interpretation of
|
||||
the object structures here is that one could completely abstract away
|
||||
the domain model, defining only item structures and processing data. You
|
||||
could have a single dataset definition for a particular concrete dataset,
|
||||
and so long as we're talking about the same items, it can be instantiated
|
||||
using *any domain*. You wouldn't need specific subclasses for disk or
|
||||
network or in-memory; you can tell it directly at runtime.
|
||||
**Marginalizing out the modality layer**
|
||||
|
||||
That's an eventually possibility, anyway. As it stands, however, this is
|
||||
effectively impossible:
|
||||
With ``domain`` being an instance variable, one possible interpretation of
|
||||
the object structures here is that one could completely abstract away
|
||||
the domain model, defining only item structures and processing data. You
|
||||
could have a single dataset definition for a particular concrete dataset,
|
||||
and so long as we're talking about the same items, it can be instantiated
|
||||
using *any domain*. You wouldn't need specific subclasses for disk or
|
||||
network or in-memory structures; you can tell it directly at runtime.
|
||||
|
||||
You can't easily abstract the batch -> item splitting process, i.e.,
|
||||
``_process_batch_data()``. A list-based version of the dataset you're
|
||||
trying to define might have an individual item tuple at every index,
|
||||
whereas a disk-based version might have tuples batched across a few files.
|
||||
This can't reliably be inferred, nor can it be pushed to the
|
||||
``Domain``-level without needing equal levels of specialization (you'd just
|
||||
end up needing the exact same structural distinctions in the ``Domain``
|
||||
hierarchy). So *somewhere* you need a batch splitting implementation that
|
||||
is both item structure-dependent *and* domain-dependent...the question is
|
||||
how dynamic you're willing to be about where it comes from. Right now, we
|
||||
require this actually be defined in the ``_process_batch_data()`` method,
|
||||
meaning you'll need a specific ``Dataset`` class for each domain you want
|
||||
to support (e.g., ``MNISTDisk``, ``MNISTList``, ``MNISTNetwork``, etc), or
|
||||
at least for each domain where "interpreting" a batch could possibly
|
||||
differ. This is a case where the interface is all that enforces a
|
||||
distinction: if you've got two domains that can be counted on to yield
|
||||
batches in the exact same way and can use the same processing, then you
|
||||
could feasibly provide ``Domain`` objects from either at runtime and have
|
||||
no issues. We're "structurally blind" to any differentiation beyond the URI
|
||||
and resource types by design, so two different domain implementations with
|
||||
the same type signature ``Domain[U, R]`` should be expected to work fine at
|
||||
runtime (again, so long as they don't also need different batch
|
||||
processing), but that's not affording us much flexibility, i.e., most of
|
||||
the time we'll still be defining new dataset classes for each domain.
|
||||
That's an eventually possibility, anyway. As it stands, however, this is
|
||||
effectively impossible:
|
||||
|
||||
I initially flagged this as feasible, however, because one could imagine
|
||||
accepting a batch processing method upon instantiation rather than
|
||||
structurally bolting it into the ``Dataset`` definition. This would require
|
||||
knowledge of the item structure ``I`` as well as the ``Domain[U, R]``, so
|
||||
such a function will always have to be (I, U, R)-dependent. It nevertheless
|
||||
would take out some of the pain of having to define new dataset classes;
|
||||
instead, you'd just need to define the batch processing method. I see this
|
||||
as a worse alternative to just defining *inside* a safe context like a new
|
||||
dataset class: you know the types you have to respect, and you stick that
|
||||
method exactly in a context where it's understood. Freeing this up doesn't
|
||||
lighten the burden of processing logic, it just changes *when* it has to be
|
||||
provided, and that's not worth much (to me) in this case given the bump in
|
||||
complexity. (Taking this to the extreme: you could supply *all* of an
|
||||
object's methods "dynamically" and glue them together at runtime so long as
|
||||
they all played nice. But wherever you were "laying them out" beforehand is
|
||||
exactly the job of a class to begin with, so you don't end up with anything
|
||||
more dynamic. All we're really discussing here is pushing around
|
||||
unavoidable complexity inside and outside of the "class walls," and in the
|
||||
particular case of ``_process_batch_data()``, it feels much better when
|
||||
it's on the inside.)
|
||||
You can't easily abstract the batch-to-item splitting process, i.e.,
|
||||
``_process_batch_data()``. A list-based version of the dataset you're trying to
|
||||
define might have an individual item tuple at every index, whereas a disk-based
|
||||
version might have tuples batched across a few files. This can't reliably be
|
||||
inferred, nor can it be pushed to the ``Domain``-level without needing equal
|
||||
levels of specialization (you'd just end up needing the exact same structural
|
||||
distinctions in the ``Domain`` hierarchy). So *somewhere* you need a batch
|
||||
splitting implementation that is both item structure-dependent *and*
|
||||
domain-dependent...the question is how dynamic you're willing to be about where
|
||||
it comes from. Right now, we require this actually be defined in the
|
||||
``_process_batch_data()`` method, meaning you'll need a specific ``Dataset``
|
||||
class for each domain you want to support (e.g., ``MNISTDisk``, ``MNISTList``,
|
||||
``MNISTNetwork``, etc), or at least for each domain where "interpreting" a
|
||||
batch could possibly differ. This is a case where the interface is all that
|
||||
enforces a distinction: if you've got two domains that can be counted on to
|
||||
yield batches in the exact same way and can use the same processing, then you
|
||||
could feasibly provide ``Domain`` objects from either at runtime and have no
|
||||
issues. We're "structurally blind" to any differentiation beyond the URI and
|
||||
resource types by design, so two different domain implementations with the same
|
||||
type signature ``Domain[U, R]`` should be expected to work fine at runtime
|
||||
(again, so long as they don't also need different batch processing), but that's
|
||||
not affording us much flexibility, i.e., most of the time we'll still be
|
||||
defining new dataset classes for each domain.
|
||||
|
||||
Holding:
|
||||
@abstractmethod
|
||||
def _get_uri_groups(self) -> Iterable[tuple[U, ...]]:
|
||||
Get URI groups for each batch.
|
||||
|
||||
If there's more than one URI per batch (e.g., a data file and a
|
||||
metadata file), zip the URIs such that we have a tuple of URIs per
|
||||
batch.
|
||||
|
||||
Note that this effectively defines the index style over batches in the
|
||||
attached domain. We get an ``int -> tuple[U, ...]`` map that turns
|
||||
batch indices into URIs that can be read under the domain.
|
||||
``get_batch()`` turns an integer index into its corresponding
|
||||
``tuple[U, ...]``, reading the resources with ``_read_resources()`` in
|
||||
the tuple, treating them as providers of batched data.
|
||||
``_read_resources()`` passes through to the attached domain logic,
|
||||
which, although common, need not supply an explicit iterable of batch
|
||||
items: we just access items with ``__getitem__()`` and may ask for
|
||||
``__len__``. So the returned URI group collection (this method) does
|
||||
need to be iterable to measure the number of batches, but the batch
|
||||
objects that are ultimately produced by these URI groups need not be
|
||||
iterables themselves.
|
||||
|
||||
raise NotImplementedError
|
||||
|
||||
def _read_resources(
|
||||
self,
|
||||
uri_group: tuple[U, ...],
|
||||
batch_index: int
|
||||
) -> tuple[R, ...]:
|
||||
Read batch files at the provided paths.
|
||||
|
||||
This method should operate on a single tuple from the list of batch
|
||||
tuples returned by the ``_get_uri_groups()`` method. That is, it reads
|
||||
all of the resources for a single batch and returns a tuple of the same
|
||||
size with their contents.
|
||||
|
||||
Note: the dependence on a batch index is mostly here to make
|
||||
multi-dataset composition easier later. In-dataset, you don't need to
|
||||
know the batch index to to simply process URIs, but across datasets you
|
||||
need it to find out the origin of the batch (and process those URIs
|
||||
accordingly).
|
||||
|
||||
return tuple(self.domain.read(uri) for uri in uri_group)
|
||||
|
||||
# pulling the type variable out of the inline generic b/c `ty` has trouble
|
||||
# understanding bound type variables in subclasses (specifically with Self@)
|
||||
T = TypeVar("T", bound=NamedTuple)
|
||||
|
||||
class NamedTupleDataset[I](Dataset):
|
||||
def __init__(self, data_list: list[I]) -> None:
|
||||
self.data_list = data_list
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self.data_list)
|
||||
|
||||
def __getitem__(self, index: int) -> I:
|
||||
return self.data_list[index]
|
||||
I initially flagged this as feasible, however, because one could imagine
|
||||
accepting a batch processing method upon instantiation rather than structurally
|
||||
bolting it into the ``Dataset`` definition. This would require knowledge of the
|
||||
item structure ``I`` as well as the ``Domain[U, R]``, so such a function will
|
||||
always have to be ``(I, U, R)``-dependent. It nevertheless would take out some
|
||||
of the pain of having to define new dataset classes; instead, you'd just need
|
||||
to define the batch processing method. I see this as a worse alternative to
|
||||
just defining *inside* a safe context like a new dataset class: you know the
|
||||
types you have to respect, and you stick that method exactly in a context where
|
||||
it's understood. Freeing this up doesn't lighten the burden of processing
|
||||
logic, it just changes *when* it has to be provided, and that's not worth much
|
||||
(to me) in this case given the bump in complexity. (Taking this to the extreme:
|
||||
you could supply *all* of an object's methods "dynamically" and glue them
|
||||
together at runtime so long as they all played nice. But wherever you were
|
||||
"laying them out" beforehand is exactly the job of a class to begin with, so
|
||||
you don't end up with anything more dynamic. All we're really discussing here
|
||||
is pushing around unavoidable complexity inside and outside of the "class
|
||||
walls," and in the particular case of ``_process_batch_data()``, it feels much
|
||||
better when it's on the inside.)
|
||||
"""
|
||||
|
||||
import math
|
||||
@@ -154,40 +95,77 @@ class BatchedDataset[U, R, I](Dataset):
|
||||
|
||||
The class is generic over a URI type ``U``, a resource type ``R`` (both of
|
||||
which are used to concretize a domain ``Domain[U, R]``), and an item type
|
||||
``T`` (which has a ``tuple`` upper bound).
|
||||
``I``.
|
||||
|
||||
Pipeline overview:
|
||||
**Batch and item processing flow**
|
||||
|
||||
```
|
||||
Domain -> [U] (get _batch_uris)
|
||||
U -> R (domain access ; Rs provide batches)
|
||||
R -> [I] (cache here ; _process_batch_data to use load_transform)
|
||||
[I] -> I (human item obj ; _get_item)
|
||||
I -> **P (final packed item ; __getitem__ to use transform)
|
||||
```
|
||||
.. code-block:: text
|
||||
|
||||
Note^1: as far as positioning, this class is meant to play nice with
|
||||
PyTorch DataLoaders, hence the inheritance from ``torch.Dataset``. The
|
||||
value add for this over the ``torch.Dataset`` base is almost entirely in
|
||||
the logic it implements to map out of *batched resources* that are holding
|
||||
data, and flattening it out into typical dataset items. There are also some
|
||||
QoL items when it comes to splitting and balancing samples.
|
||||
Domain -> [U] :: self._batch_uris = list(domain)
|
||||
|
||||
Note^2: even though ``Domains`` implement iterators over their URIs, this
|
||||
doesn't imply a ``BatchedDataset`` is iterable. This just means we can walk
|
||||
over the resources that provide data, but we don't necessarily presuppose
|
||||
an ordered walk over samples within batches. Point being:
|
||||
Grab all URIs from Domain iterators. This is made concrete early to
|
||||
allow for Dataset sizing, and we need a Sequence representation to
|
||||
map integer batch indices into Domains, i.e., when getting the
|
||||
corresponding URI:
|
||||
|
||||
batch_uri = self._batch_uris[batch_index]
|
||||
|
||||
We let Domains implement iterators over their URIs, but explicitly
|
||||
exhaust when initializing Datasets.
|
||||
|
||||
U -> R :: batch_data = self.domain[batch_uri]
|
||||
|
||||
Retrieve resource from domain. Resources are viewed as batched
|
||||
data, even if only wrapping single items (happens in trivial
|
||||
settings).
|
||||
|
||||
R -> [I] :: self._process_batch_data(batch_data, batch_index)
|
||||
|
||||
Possibly domain-specific batch processing of resource data into
|
||||
explicit Sequence-like structures of items, each of which is
|
||||
subject to the provided pre_transform. Processed batches at this
|
||||
stage are cached (if enabled).
|
||||
|
||||
[I] -> I :: self.get_batch(batch_index)[index_in_batch]
|
||||
|
||||
Select individual items from batches in _get_item. At this stage,
|
||||
items are in intermediate states and pulled from the cached
|
||||
batches.
|
||||
|
||||
I -> I :: self._process_item_data(item_data, index)
|
||||
|
||||
Produce final items with __getitem__, getting intermediate items
|
||||
via _get_item and applying the provided post_transform.
|
||||
|
||||
.. note::
|
||||
|
||||
As far as positioning, this class is meant to play nice with PyTorch
|
||||
DataLoaders, hence the inheritance from ``torch.Dataset``. The value
|
||||
add for this over the ``torch.Dataset`` base is almost entirely in the
|
||||
logic it implements to map out of *batched resources* that are holding
|
||||
data, and flattening it out into typical dataset items. There are also
|
||||
some QoL features when it comes to splitting and balancing samples.
|
||||
|
||||
.. note::
|
||||
|
||||
Even though ``Domains`` implement iterators over their URIs, this
|
||||
doesn't imply a ``BatchedDataset`` is iterable. This just means we can
|
||||
walk over the resources that provide data, but we don't necessarily
|
||||
presuppose an ordered walk over samples within batches. Point being:
|
||||
``torch.Dataset``, not ``torch.IterableDataset``, is the appropriate
|
||||
superclass, even when we're working around iterable ``Domains``.
|
||||
|
||||
Note^3: transforms are expected to operate on ``I``-items and produce
|
||||
``I``-items. They shouldn't be the "introducers" of ``I`` types from some
|
||||
other intermediate representation, nor should they map from ``I`` to
|
||||
something else. Point being: the dataset definition should be able to map
|
||||
resources ``R`` to ``I`` without a transform: that much should be baked
|
||||
into the class definition. If you find you're expecting the transform to do
|
||||
that for you, you should consider pulling in some common structure across
|
||||
the allowed transforms and make it a fixed part of the class.
|
||||
.. note::
|
||||
|
||||
Transforms are expected to operate on ``I``-items and produce
|
||||
``I``-items. They shouldn't be the "introducers" of ``I`` types from
|
||||
some other intermediate representation, nor should they map from ``I``
|
||||
to something else. Point being: the dataset definition should be able
|
||||
to map resources ``R`` to ``I`` without a transform: that much should
|
||||
be baked into the class definition. If you find you're expecting the
|
||||
transform to do that for you, you should consider pulling in some
|
||||
common structure across the allowed transforms and make it a fixed part
|
||||
of the class.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
@@ -201,6 +179,7 @@ class BatchedDataset[U, R, I](Dataset):
|
||||
) -> None:
|
||||
"""
|
||||
Parameters:
|
||||
domain: ``Domain`` object providing access to batched data
|
||||
pre_transform: transform to apply over items during loading (in
|
||||
``_process_batch_data()``), i.e., *before* going into
|
||||
persistent storage
|
||||
@@ -210,6 +189,7 @@ class BatchedDataset[U, R, I](Dataset):
|
||||
batch_cache_limit: the max number of max batches to cache at any
|
||||
one time
|
||||
preload: whether to load all data into memory during instantiation
|
||||
num_workers: number of workers to use when preloading data
|
||||
"""
|
||||
|
||||
self.domain = domain
|
||||
@@ -249,6 +229,9 @@ class BatchedDataset[U, R, I](Dataset):
|
||||
The behavior of this method can vary depending on what we know about
|
||||
batch sizes, and should therefore be implemented by inheriting classes.
|
||||
|
||||
Parameters:
|
||||
item_index: index of item
|
||||
|
||||
Returns:
|
||||
batch_index: int
|
||||
index_in_batch: int
|
||||
@@ -292,6 +275,10 @@ class BatchedDataset[U, R, I](Dataset):
|
||||
place to use a provided ``post_transform``; items are pulled from the
|
||||
cache (if enabled) and processed before being returned as the final
|
||||
tuple outputs (so this processing is not persistent).
|
||||
|
||||
Parameters:
|
||||
item_data: item data
|
||||
item_index: index of item
|
||||
"""
|
||||
|
||||
raise NotImplementedError
|
||||
@@ -307,6 +294,9 @@ class BatchedDataset[U, R, I](Dataset):
|
||||
|
||||
Note that return values from `__getitem__()` are "cleaned up" versions
|
||||
of this representation, with minimal info needed for training.
|
||||
|
||||
Parameters:
|
||||
item_index: index of item
|
||||
"""
|
||||
|
||||
if item_index >= len(self):
|
||||
@@ -341,10 +331,13 @@ class BatchedDataset[U, R, I](Dataset):
|
||||
they're always connected, and nothing would notice if you waited
|
||||
between steps. The only way this could matter is if you split the
|
||||
resource reading and batch processing steps across methods, but when it
|
||||
actually comes to accessing/caching the batch, you'd have to expand
|
||||
any delayed reads here. There's no way around needing to see all batch
|
||||
data at once here, and we don't want to make that ambiguous: ``list``
|
||||
output type it is.
|
||||
actually comes to accessing/caching the batch, you'd have to expand any
|
||||
delayed reads here. There's no way around needing to see all batch data
|
||||
at once here, and we don't want to make that ambiguous: ``list`` output
|
||||
type it is.
|
||||
|
||||
Parameters:
|
||||
batch_index: index of batch
|
||||
"""
|
||||
|
||||
logger.debug("Batch cache miss, reading from root...")
|
||||
@@ -364,6 +357,9 @@ class BatchedDataset[U, R, I](Dataset):
|
||||
Can be useful when dynamically pulling data (as it's requested) isn't
|
||||
desired. Requires that `cache_sample_limit=None`, i.e., the cache won't
|
||||
continually remove previous batches as they're loaded.
|
||||
|
||||
Parameters:
|
||||
num_workers: number of parallel workers to use for data loading
|
||||
"""
|
||||
|
||||
assert self.batch_cache_limit is None, "Preloading under cache limit"
|
||||
@@ -396,36 +392,46 @@ class BatchedDataset[U, R, I](Dataset):
|
||||
"""
|
||||
Split dataset into fractional pieces by data attribute.
|
||||
|
||||
If `by_attr` is None, recovers typical fractional splitting of dataset
|
||||
items, partitioning by size. Using None anywhere will index each item
|
||||
into its own bucket, i.e., by its index. For instance,
|
||||
If ``by_attr`` is None, recovers typical fractional splitting of
|
||||
dataset items, partitioning by size. Using None anywhere will index
|
||||
each item into its own bucket, i.e., by its index. For instance:
|
||||
|
||||
- by_attr=["color"] -> {("red", 1), ("red", 2)},
|
||||
- Splits on the attribute such that each subset contains entire strata
|
||||
of the attribute. "Homogeneity within clusters:"
|
||||
|
||||
.. code-block::
|
||||
|
||||
by_attr=["color"] -> {("red", 1), ("red", 2)},
|
||||
{("blue", 1), ("blue", 2)}
|
||||
|
||||
Splits on the attribute such that each subset contains entire strata
|
||||
of the attribute. "Homogeneity within clusters"
|
||||
|
||||
- `by_attr=["color", None]` -> {("red", 1), ("blue", 1)},
|
||||
{("red", 2), ("blue", 2)}
|
||||
|
||||
Stratifies by attribute and then splits "by index" within, uniformly
|
||||
- Stratifies by attribute and then splits "by index" within, uniformly
|
||||
grabbing samples across strata to form new clusters. "Homogeneity
|
||||
across clusters"
|
||||
|
||||
.. code-block::
|
||||
|
||||
by_attr=["color", None] -> {("red", 1), ("blue", 1)},
|
||||
{("red", 2), ("blue", 2)}
|
||||
|
||||
Note that the final list of Subsets returned are built from shallow
|
||||
copies of the underlying dataset (i.e., `self`) to allow manual
|
||||
copies of the underlying dataset (i.e., ``self``) to allow manual
|
||||
intervention with dataset attributes (e.g., setting the splits to have
|
||||
different `transform`s). This is subject to possibly unexpected
|
||||
different ``transforms``). This is subject to possibly unexpected
|
||||
behavior if re-caching data or you need a true copy of all data in
|
||||
memory, but should otherwise leave most interactions unchanged.
|
||||
|
||||
Parameters:
|
||||
frac: split fractions for datasets
|
||||
dataset: dataset to split, defaults to ``self``. Facilitates
|
||||
recursive splitting when multi-attribute splits are needed.
|
||||
by_attr: attribute or attributes to use when grouping strata for
|
||||
dataset splits. Defaults to ``None``, which will use item
|
||||
indices.
|
||||
shuffle_strata: shuffle the strata order before split is drawn. We
|
||||
parameterize this because a dataloader-level shuffle operation
|
||||
parameterize this because a Dataloader-level shuffle operation
|
||||
will only change the order of the indices in the resulting
|
||||
splits; only a shuffle of items inside the strata can change
|
||||
the actual content of the splits themselves.
|
||||
splits; only a shuffle of the strata order can change the
|
||||
actual content of the splits themselves.
|
||||
"""
|
||||
|
||||
if by_attr == []:
|
||||
@@ -534,6 +540,32 @@ class BatchedDataset[U, R, I](Dataset):
|
||||
split_max_sizes: list[int] | None = None,
|
||||
shuffle_strata: bool = True,
|
||||
) -> None:
|
||||
"""
|
||||
Balance the distribution of provided attributes over dataset items.
|
||||
|
||||
This method sets the indices over the dataset according to the result
|
||||
of the rebalancing. The indices are produced by the recursive
|
||||
``_balance()`` method, which is necessarily separate due to the need
|
||||
for a contained recursive approach that doesn't change the underlying
|
||||
dataset during execution.
|
||||
|
||||
Parameters:
|
||||
dataset: dataset to split, defaults to ``self``. Facilitates
|
||||
recursive splitting when multi-attribute splits are needed.
|
||||
by_attr: attribute or attributes to use when grouping strata for
|
||||
dataset splits. Defaults to ``None``, which will use item
|
||||
indices.
|
||||
split_min_sizes: minimum allowed sizes of splits. Must have the
|
||||
same length as ``by_attr``.
|
||||
split_max_sizes: maximum allowed sizes of splits. Must have the
|
||||
same length as ``by_attr``.
|
||||
shuffle_strata: shuffle the strata order before split is drawn. We
|
||||
parameterize this because a Dataloader-level shuffle operation
|
||||
will only change the order of the indices in the resulting
|
||||
splits; only a shuffle of the strata order can change the
|
||||
actual content of the splits themselves.
|
||||
"""
|
||||
|
||||
self.indices = self._balance(
|
||||
dataset,
|
||||
by_attr,
|
||||
@@ -551,9 +583,29 @@ class BatchedDataset[U, R, I](Dataset):
|
||||
shuffle_strata: bool = True,
|
||||
) -> list[int]:
|
||||
"""
|
||||
Note: behavior is a little odd for nested behavior; not exactly
|
||||
perfectly uniform throughout. This is a little difficult: you can't
|
||||
exactly know ahead of time the size of the subgroups across splits
|
||||
Recursive balancing of items by attribute.
|
||||
|
||||
.. note::
|
||||
|
||||
Behavior is a little odd for nested behavior; not exactly perfectly
|
||||
uniform throughout. This is a little difficult: you can't exactly
|
||||
know ahead of time the size of the subgroups across splits
|
||||
|
||||
Parameters:
|
||||
dataset: dataset to split, defaults to ``self``. Facilitates
|
||||
recursive splitting when multi-attribute splits are needed.
|
||||
by_attr: attribute or attributes to use when grouping strata for
|
||||
dataset splits. Defaults to ``None``, which will use item
|
||||
indices.
|
||||
split_min_sizes: minimum allowed sizes of splits. Must have the
|
||||
same length as ``by_attr``.
|
||||
split_max_sizes: maximum allowed sizes of splits. Must have the
|
||||
same length as ``by_attr``.
|
||||
shuffle_strata: shuffle the strata order before split is drawn. We
|
||||
parameterize this because a Dataloader-level shuffle operation
|
||||
will only change the order of the indices in the resulting
|
||||
splits; only a shuffle of the strata order can change the
|
||||
actual content of the splits themselves.
|
||||
"""
|
||||
|
||||
if by_attr == []:
|
||||
@@ -643,6 +695,9 @@ class BatchedDataset[U, R, I](Dataset):
|
||||
dataset. The underlying data remain the same, but when indices get set,
|
||||
you're effectively applying a mask over any existing indices, always
|
||||
operating *relative* to the existing mask.
|
||||
|
||||
Parameters:
|
||||
indices: list of indices to set
|
||||
"""
|
||||
|
||||
# manually set new size
|
||||
@@ -670,6 +725,13 @@ class BatchedDataset[U, R, I](Dataset):
|
||||
return self._dataset_len
|
||||
|
||||
def __getitem__(self, index: int) -> I:
|
||||
"""
|
||||
Get the dataset item at the specified index.
|
||||
|
||||
Parameters:
|
||||
index: index of item to retrieve
|
||||
"""
|
||||
|
||||
item_data = self._get_item(index)
|
||||
index = self.indices[index]
|
||||
|
||||
@@ -691,9 +753,10 @@ class CompositeBatchedDataset[U, R, I](BatchedDataset[U, R, I]):
|
||||
"""
|
||||
Dataset class for wrapping individual datasets.
|
||||
|
||||
Note: because this remains a valid ``BatchedDataset``, we re-thread the
|
||||
generic type variables through the set of composed datasets. That is, they
|
||||
must have a common domain type ``Domain[U, R]``.
|
||||
.. note::
|
||||
Because this remains a valid ``BatchedDataset``, we re-thread the
|
||||
generic type variables through the set of composed datasets. That is,
|
||||
they must have a common domain type ``Domain[U, R]``.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
@@ -878,7 +941,7 @@ class HomogenousDataset[U, R, I](BatchedDataset[U, R, I]):
|
||||
|
||||
class HeterogenousDataset[U, R, I](BatchedDataset[U, R, I]):
|
||||
"""
|
||||
Batched dataset where batches have arbitrary size.
|
||||
Batched dataset where batches may have arbitrary size.
|
||||
|
||||
Methods left for inheriting classes:
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
from trainlib.dataset import BatchedDataset
|
||||
from trainlib.utils.text import camel_to_snake
|
||||
from trainlib.datasets.memory import RandomXYDataset
|
||||
|
||||
_datasets = [
|
||||
RandomXYDataset,
|
||||
]
|
||||
dataset_map: dict[str, type[BatchedDataset]] = {
|
||||
camel_to_snake(_dataset.__name__): _dataset
|
||||
for _dataset in _datasets
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ class DiskDataset[T: NamedTuple](HomogenousDataset[Path, bytes, T]):
|
||||
"""
|
||||
The following line is to satisfy the type checker, which
|
||||
|
||||
1. Can't recognize an appropriately re-typed constructor arg like
|
||||
1. Can't recognize an appropriately re-typed constructor arg like::
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -20,7 +20,8 @@ class DiskDataset[T: NamedTuple](HomogenousDataset[Path, bytes, T]):
|
||||
...
|
||||
): ...
|
||||
|
||||
This *does* match the parent generic for the U=Path, R=bytes context
|
||||
This *does* match the parent generic for the ``U=Path``, ``R=bytes``
|
||||
context::
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -32,19 +33,17 @@ class DiskDataset[T: NamedTuple](HomogenousDataset[Path, bytes, T]):
|
||||
|
||||
2. "Lifted" type variables out of generics can't be used as upper bounds,
|
||||
at least not without throwing type checker warnings (thanks to PEP695).
|
||||
So I'm not allowed to have
|
||||
So I'm not allowed to have::
|
||||
|
||||
```
|
||||
class BatchedDataset[U, R, D: Domain[U, R]]:
|
||||
...
|
||||
```
|
||||
|
||||
which could bring appropriately dynamic typing for ``Domain``s, but is
|
||||
which could bring appropriately dynamic typing for ``Domains``, but is
|
||||
not a sufficiently concrete upper bound.
|
||||
|
||||
So: we settle for a class-level type declaration, which despite not being
|
||||
technically appropriately scoped, it's not harming anything and satisfies
|
||||
``ty`` type checks downstream (e.g., when we access ``DiskDomain.root``.
|
||||
``ty`` type checks downstream (e.g., when we access ``DiskDomain.root``).
|
||||
"""
|
||||
|
||||
domain: DiskDomain
|
||||
|
||||
@@ -73,17 +73,37 @@ class RecordDataset[T: NamedTuple](HomogenousDataset[int, T, T]):
|
||||
|
||||
from typing import Unpack
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from torch import Tensor
|
||||
from torch.utils.data import TensorDataset
|
||||
|
||||
from trainlib.domain import SequenceDomain
|
||||
from trainlib.dataset import TupleDataset, DatasetKwargs
|
||||
|
||||
|
||||
class SlidingWindowDataset[T: Tensor](TupleDataset[T]):
|
||||
class RandomXYDataset(TupleDataset[Tensor]):
|
||||
def __init__(
|
||||
self,
|
||||
domain: SequenceDomain[tuple[T, ...]],
|
||||
num_samples: int,
|
||||
input_dim: int,
|
||||
output_dim: int,
|
||||
**kwargs: Unpack[DatasetKwargs],
|
||||
) -> None:
|
||||
domain = SequenceDomain[tuple[Tensor, Tensor]](
|
||||
TensorDataset(
|
||||
torch.randn((num_samples, input_dim)),
|
||||
torch.randn((num_samples, output_dim))
|
||||
),
|
||||
)
|
||||
|
||||
super().__init__(domain, **kwargs)
|
||||
|
||||
|
||||
class SlidingWindowDataset(TupleDataset[Tensor]):
|
||||
def __init__(
|
||||
self,
|
||||
domain: SequenceDomain[tuple[Tensor, ...]],
|
||||
lookback: int,
|
||||
offset: int = 0,
|
||||
lookahead: int = 1,
|
||||
@@ -99,9 +119,9 @@ class SlidingWindowDataset[T: Tensor](TupleDataset[T]):
|
||||
|
||||
def _process_batch_data(
|
||||
self,
|
||||
batch_data: tuple[T, ...],
|
||||
batch_data: tuple[Tensor, ...],
|
||||
batch_index: int,
|
||||
) -> list[tuple[T, ...]]:
|
||||
) -> list[tuple[Tensor, ...]]:
|
||||
"""
|
||||
Backward pads first sequence over (lookback-1) length, and steps the
|
||||
remaining items forward by the lookahead.
|
||||
|
||||
@@ -1,24 +1,5 @@
|
||||
"""
|
||||
Defines a knowledge domain. Wraps a Dataset / Simulator / Knowledge
|
||||
|
||||
Downstream exploration might include
|
||||
|
||||
- Calibrating Simulator / Knowledge with a Dataset
|
||||
- Amending Dataset with Simulator / Knowledge
|
||||
- Positioning Knowledge within Simulator context
|
||||
* Where to replace Simulator subsystem with Knowledge?
|
||||
|
||||
Other variations:
|
||||
|
||||
- Multi-fidelity simulators
|
||||
- Multi-scale models
|
||||
- Multi-system
|
||||
- Incomplete knowledge / divergence among sources
|
||||
|
||||
Questions:
|
||||
|
||||
- Should Simulator / Knowledge be unified as one (e.g., "Expert")
|
||||
|
||||
Generic URI-resource mapping structure
|
||||
"""
|
||||
|
||||
from collections.abc import Mapping, Iterator, Sequence
|
||||
@@ -83,3 +64,14 @@ class SequenceDomain[R](Domain[int, R]):
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self.sequence)
|
||||
|
||||
|
||||
class TupleDomain[T](SequenceDomain[tuple[T, ...]]):
|
||||
"""
|
||||
Domain for homogenous tuples of the same type.
|
||||
|
||||
This class header exists primarily as typed alias that aligns with
|
||||
TupleDataset.
|
||||
"""
|
||||
|
||||
...
|
||||
|
||||
@@ -8,10 +8,16 @@ class SimulatorDomain[P, R](Domain[int, R]):
|
||||
Base simulator domain, generic to arbitrary callables.
|
||||
|
||||
Note: we don't store simulation results here; that's left to a downstream
|
||||
object, like a `BatchedDataset`, to cache if needed. We also don't subclass
|
||||
`SequenceDataset` because the item getter type doesn't align: we accept an
|
||||
`int` in the parameter list, but don't return the items directly from that
|
||||
collection (we transform them first).
|
||||
object, like a ``BatchedDataset``, to cache if needed. We also don't
|
||||
subclass ``SequenceDataset`` because the item getter type doesn't align: we
|
||||
accept an ``int`` in the parameter list, but don't return the items
|
||||
directly from that collection (we transform them first).
|
||||
|
||||
Note: it's interesting to consider the idea of having parameters directly
|
||||
act as URIs. There is, however, no obvious way to iterate over allowed
|
||||
parameters (without additional components, like a prior or some other
|
||||
generator), so we leave that outside the class scope and simply operate
|
||||
over of a provided parameter sequence.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
"""
|
||||
Base class for trainable models
|
||||
|
||||
Development note
|
||||
|
||||
I'd rather lay out bare args and kwargs in the estimator methods, but the
|
||||
@@ -24,7 +26,7 @@ from torch import nn, Tensor
|
||||
from torch.optim import Optimizer
|
||||
from torch.utils.tensorboard import SummaryWriter
|
||||
|
||||
from trainlib.util.type import OptimizerKwargs
|
||||
from trainlib.utils.type import OptimizerKwargs
|
||||
|
||||
logger: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -33,6 +35,10 @@ class EstimatorKwargs(TypedDict):
|
||||
inputs: Tensor
|
||||
|
||||
|
||||
class SupervisedKwargs(EstimatorKwargs):
|
||||
labels: Tensor
|
||||
|
||||
|
||||
class Estimator[Kw: EstimatorKwargs](nn.Module):
|
||||
"""
|
||||
Estimator base class.
|
||||
@@ -162,7 +168,7 @@ class Estimator[Kw: EstimatorKwargs](nn.Module):
|
||||
self,
|
||||
writer: SummaryWriter,
|
||||
step: int | None = None,
|
||||
val: bool = False,
|
||||
group: str | None = None,
|
||||
**kwargs: Unpack[Kw],
|
||||
) -> None:
|
||||
"""
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
from trainlib.estimator import Estimator
|
||||
from trainlib.utils.text import camel_to_snake
|
||||
from trainlib.estimators.mlp import MLP
|
||||
from trainlib.estimators.rnn import LSTM, ConvGRU, MultiheadLSTM
|
||||
|
||||
_estimators: list[type[Estimator]] = [
|
||||
MLP,
|
||||
LSTM,
|
||||
MultiheadLSTM,
|
||||
ConvGRU,
|
||||
]
|
||||
estimator_map: dict[str, type[Estimator]] = {
|
||||
camel_to_snake(_estimator.__name__): _estimator
|
||||
for _estimator in _estimators
|
||||
}
|
||||
|
||||
148
trainlib/estimators/mlp.py
Normal file
148
trainlib/estimators/mlp.py
Normal file
@@ -0,0 +1,148 @@
|
||||
import logging
|
||||
from typing import Unpack, NotRequired
|
||||
from collections.abc import Callable, Generator
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from torch import nn, Tensor
|
||||
from torch.optim import Optimizer
|
||||
from torch.utils.tensorboard import SummaryWriter
|
||||
|
||||
from trainlib.estimator import Estimator, EstimatorKwargs
|
||||
from trainlib.utils.type import OptimizerKwargs
|
||||
from trainlib.utils.module import get_grad_norm
|
||||
from trainlib.estimators.tdnn import TDNNLayer
|
||||
|
||||
logger: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MLPKwargs(EstimatorKwargs):
|
||||
inputs: Tensor
|
||||
labels: NotRequired[Tensor]
|
||||
|
||||
|
||||
class MLP[Kw: MLPKwargs](Estimator[Kw]):
|
||||
"""
|
||||
Base MLP architecture.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
input_dim: int,
|
||||
output_dim: int,
|
||||
hidden_dims: list[int] | None = None,
|
||||
norm_layer: Callable[..., nn.Module] | None = None,
|
||||
activation_fn: nn.Module | None = None,
|
||||
inplace: bool = False,
|
||||
bias: bool = True,
|
||||
dropout: float = 0.0,
|
||||
verbose: bool = True,
|
||||
) -> None:
|
||||
"""
|
||||
Parameters:
|
||||
input_dim: dimensionality of the input
|
||||
output_dim: dimensionality of the output
|
||||
hidden_dims: dimensionalities of hidden layers
|
||||
"""
|
||||
|
||||
super().__init__()
|
||||
|
||||
self.input_dim = input_dim
|
||||
self.output_dim = output_dim
|
||||
self.hidden_dims = hidden_dims or []
|
||||
self.norm_layer = norm_layer
|
||||
self.activation_fn = activation_fn or nn.ReLU
|
||||
|
||||
# self._layers: nn.ModuleList = nn.ModuleList()
|
||||
self._layers = []
|
||||
|
||||
layer_in_dim = input_dim
|
||||
for layer_out_dim in self.hidden_dims:
|
||||
hidden_layer = nn.Linear(layer_in_dim, layer_out_dim, bias=bias)
|
||||
self._layers.append(hidden_layer)
|
||||
|
||||
if norm_layer is not None:
|
||||
self._layers.append(norm_layer(layer_out_dim))
|
||||
|
||||
self._layers.append(self.activation_fn(inplace=inplace))
|
||||
self._layers.append(nn.Dropout(dropout, inplace=inplace))
|
||||
|
||||
layer_in_dim = layer_out_dim
|
||||
|
||||
self._layers.append(nn.Linear(layer_in_dim, self.output_dim))
|
||||
self._net = nn.Sequential(*self._layers)
|
||||
|
||||
if verbose:
|
||||
self.log_arch()
|
||||
|
||||
def _clamp_rand(self, x: Tensor) -> Tensor:
|
||||
return torch.clamp(
|
||||
x + (1.0 / 127.0) * (torch.rand_like(x) - 0.5),
|
||||
min=-1.0,
|
||||
max=1.0,
|
||||
)
|
||||
|
||||
def forward(self, **kwargs: Unpack[Kw]) -> tuple[Tensor, ...]:
|
||||
inputs = kwargs["inputs"]
|
||||
x = self._net(inputs)
|
||||
|
||||
return (x,)
|
||||
|
||||
def loss(self, **kwargs: Unpack[Kw]) -> Generator[Tensor]:
|
||||
predictions = self(**kwargs)[0]
|
||||
labels = kwargs["labels"]
|
||||
|
||||
yield F.mse_loss(predictions, labels)
|
||||
|
||||
def metrics(self, **kwargs: Unpack[Kw]) -> dict[str, float]:
|
||||
with torch.no_grad():
|
||||
loss = next(self.loss(**kwargs)).item()
|
||||
|
||||
predictions = self(**kwargs)[0]
|
||||
labels = kwargs["labels"]
|
||||
mae = F.l1_loss(predictions, labels).item()
|
||||
|
||||
return {
|
||||
# "mse": loss,
|
||||
"mae": mae,
|
||||
"grad_norm": get_grad_norm(self)
|
||||
}
|
||||
|
||||
def optimizers(
|
||||
self,
|
||||
**kwargs: Unpack[OptimizerKwargs],
|
||||
) -> tuple[Optimizer, ...]:
|
||||
"""
|
||||
"""
|
||||
|
||||
default_kwargs: Unpack[OptimizerKwargs] = {
|
||||
"lr": 1e-3,
|
||||
"eps": 1e-8,
|
||||
}
|
||||
opt_kwargs = {**default_kwargs, **kwargs}
|
||||
|
||||
optimizer = torch.optim.AdamW(
|
||||
self.parameters(),
|
||||
**opt_kwargs,
|
||||
)
|
||||
|
||||
return (optimizer,)
|
||||
|
||||
def epoch_step(self) -> None:
|
||||
return None
|
||||
|
||||
def epoch_write(
|
||||
self,
|
||||
writer: SummaryWriter,
|
||||
step: int | None = None,
|
||||
group: str | None = None,
|
||||
**kwargs: Unpack[Kw],
|
||||
) -> None:
|
||||
return None
|
||||
|
||||
def log_arch(self) -> None:
|
||||
super().log_arch()
|
||||
|
||||
logger.info(f"| > {self.input_dim=}")
|
||||
logger.info(f"| > {self.hidden_dims=}")
|
||||
logger.info(f"| > {self.output_dim=}")
|
||||
@@ -21,7 +21,7 @@ class RNNKwargs(EstimatorKwargs):
|
||||
labels: NotRequired[Tensor]
|
||||
|
||||
|
||||
class LSTM[K: RNNKwargs](Estimator[K]):
|
||||
class LSTM[Kw: RNNKwargs](Estimator[Kw]):
|
||||
"""
|
||||
Base RNN architecture.
|
||||
"""
|
||||
@@ -85,7 +85,7 @@ class LSTM[K: RNNKwargs](Estimator[K]):
|
||||
max=1.0,
|
||||
)
|
||||
|
||||
def forward(self, **kwargs: Unpack[K]) -> tuple[Tensor, ...]:
|
||||
def forward(self, **kwargs: Unpack[Kw]) -> tuple[Tensor, ...]:
|
||||
inputs = kwargs["inputs"]
|
||||
|
||||
# data shaped (B, C, T); map to (B, T, C)
|
||||
@@ -97,18 +97,24 @@ class LSTM[K: RNNKwargs](Estimator[K]):
|
||||
|
||||
return z[:, -1, :], hidden
|
||||
|
||||
def loss(self, **kwargs: Unpack[K]) -> Generator[Tensor]:
|
||||
def loss(self, **kwargs: Unpack[Kw]) -> Generator[Tensor]:
|
||||
predictions = self(**kwargs)[0]
|
||||
labels = kwargs["labels"]
|
||||
|
||||
yield F.mse_loss(predictions, labels)
|
||||
|
||||
def metrics(self, **kwargs: Unpack[K]) -> dict[str, float]:
|
||||
def metrics(self, **kwargs: Unpack[Kw]) -> dict[str, float]:
|
||||
with torch.no_grad():
|
||||
loss = next(self.loss(**kwargs)).item()
|
||||
|
||||
predictions = self(**kwargs)[0]
|
||||
labels = kwargs["labels"]
|
||||
mae = F.l1_loss(predictions, labels).item()
|
||||
|
||||
return {
|
||||
"loss": loss,
|
||||
# "loss": loss,
|
||||
"mse": loss,
|
||||
"mae": mae,
|
||||
"grad_norm": get_grad_norm(self)
|
||||
}
|
||||
|
||||
@@ -139,8 +145,8 @@ class LSTM[K: RNNKwargs](Estimator[K]):
|
||||
self,
|
||||
writer: SummaryWriter,
|
||||
step: int | None = None,
|
||||
val: bool = False,
|
||||
**kwargs: Unpack[K],
|
||||
group: str | None = None,
|
||||
**kwargs: Unpack[Kw],
|
||||
) -> None:
|
||||
return None
|
||||
|
||||
@@ -159,7 +165,7 @@ class MultiheadLSTMKwargs(EstimatorKwargs):
|
||||
auxiliary: NotRequired[Tensor]
|
||||
|
||||
|
||||
class MultiheadLSTM[K: MultiheadLSTMKwargs](Estimator[K]):
|
||||
class MultiheadLSTM[Kw: MultiheadLSTMKwargs](Estimator[Kw]):
|
||||
def __init__(
|
||||
self,
|
||||
input_dim: int,
|
||||
@@ -217,7 +223,7 @@ class MultiheadLSTM[K: MultiheadLSTMKwargs](Estimator[K]):
|
||||
max=1.0,
|
||||
)
|
||||
|
||||
def forward(self, **kwargs: Unpack[K]) -> tuple[Tensor, ...]:
|
||||
def forward(self, **kwargs: Unpack[Kw]) -> tuple[Tensor, ...]:
|
||||
inputs = kwargs["inputs"]
|
||||
|
||||
# data shaped (B, C, T); map to (B, T, C)
|
||||
@@ -231,7 +237,7 @@ class MultiheadLSTM[K: MultiheadLSTMKwargs](Estimator[K]):
|
||||
|
||||
return z[:, -1, :], zs[:, -1, :]
|
||||
|
||||
def loss(self, **kwargs: Unpack[K]) -> Generator[Tensor]:
|
||||
def loss(self, **kwargs: Unpack[Kw]) -> Generator[Tensor]:
|
||||
pred, pred_aux = self(**kwargs)
|
||||
labels = kwargs["labels"]
|
||||
aux_labels = kwargs.get("auxiliary")
|
||||
@@ -241,12 +247,8 @@ class MultiheadLSTM[K: MultiheadLSTMKwargs](Estimator[K]):
|
||||
else:
|
||||
yield F.mse_loss(pred, labels) + F.mse_loss(pred_aux, aux_labels)
|
||||
|
||||
def metrics(self, **kwargs: Unpack[K]) -> dict[str, float]:
|
||||
with torch.no_grad():
|
||||
loss = next(self.loss(**kwargs)).item()
|
||||
|
||||
def metrics(self, **kwargs: Unpack[Kw]) -> dict[str, float]:
|
||||
return {
|
||||
"loss": loss,
|
||||
"grad_norm": get_grad_norm(self)
|
||||
}
|
||||
|
||||
@@ -277,8 +279,8 @@ class MultiheadLSTM[K: MultiheadLSTMKwargs](Estimator[K]):
|
||||
self,
|
||||
writer: SummaryWriter,
|
||||
step: int | None = None,
|
||||
val: bool = False,
|
||||
**kwargs: Unpack[K],
|
||||
group: str | None = None,
|
||||
**kwargs: Unpack[Kw],
|
||||
) -> None:
|
||||
return None
|
||||
|
||||
@@ -291,7 +293,7 @@ class MultiheadLSTM[K: MultiheadLSTMKwargs](Estimator[K]):
|
||||
logger.info(f"| > {self.output_dim=}")
|
||||
|
||||
|
||||
class ConvRNN[K: RNNKwargs](Estimator[K]):
|
||||
class ConvGRU[Kw: RNNKwargs](Estimator[Kw]):
|
||||
"""
|
||||
Base recurrent convolutional architecture.
|
||||
|
||||
@@ -402,7 +404,7 @@ class ConvRNN[K: RNNKwargs](Estimator[K]):
|
||||
max=1.0,
|
||||
)
|
||||
|
||||
def forward(self, **kwargs: Unpack[K]) -> tuple[Tensor, ...]:
|
||||
def forward(self, **kwargs: Unpack[Kw]) -> tuple[Tensor, ...]:
|
||||
inputs = kwargs["inputs"]
|
||||
|
||||
# embedding shaped (B, C, T)
|
||||
@@ -428,7 +430,7 @@ class ConvRNN[K: RNNKwargs](Estimator[K]):
|
||||
|
||||
return (z,)
|
||||
|
||||
def loss(self, **kwargs: Unpack[K]) -> Generator[Tensor]:
|
||||
def loss(self, **kwargs: Unpack[Kw]) -> Generator[Tensor]:
|
||||
predictions = self(**kwargs)[0]
|
||||
labels = kwargs["labels"]
|
||||
|
||||
@@ -437,12 +439,17 @@ class ConvRNN[K: RNNKwargs](Estimator[K]):
|
||||
|
||||
yield F.mse_loss(predictions, labels, reduction="mean")
|
||||
|
||||
def metrics(self, **kwargs: Unpack[K]) -> dict[str, float]:
|
||||
def metrics(self, **kwargs: Unpack[Kw]) -> dict[str, float]:
|
||||
with torch.no_grad():
|
||||
loss = next(self.loss(**kwargs)).item()
|
||||
|
||||
predictions = self(**kwargs)[0].squeeze(-1)
|
||||
labels = kwargs["labels"]
|
||||
mae = F.l1_loss(predictions, labels).item()
|
||||
|
||||
return {
|
||||
"loss": loss,
|
||||
"mse": loss,
|
||||
"mae": mae,
|
||||
"grad_norm": get_grad_norm(self)
|
||||
}
|
||||
|
||||
@@ -473,8 +480,8 @@ class ConvRNN[K: RNNKwargs](Estimator[K]):
|
||||
self,
|
||||
writer: SummaryWriter,
|
||||
step: int | None = None,
|
||||
val: bool = False,
|
||||
**kwargs: Unpack[K],
|
||||
group: str | None = None,
|
||||
**kwargs: Unpack[Kw],
|
||||
) -> None:
|
||||
return None
|
||||
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import logging
|
||||
from collections.abc import Generator
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from torch import nn, Tensor
|
||||
from torch.optim import Optimizer
|
||||
from torch.nn.utils.parametrizations import weight_norm
|
||||
|
||||
logger: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
528
trainlib/plotter.py
Normal file
528
trainlib/plotter.py
Normal file
@@ -0,0 +1,528 @@
|
||||
from typing import Any
|
||||
from collections.abc import Callable
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
import matplotlib.pyplot as plt
|
||||
from torch import Tensor
|
||||
|
||||
from trainlib.trainer import Trainer
|
||||
from trainlib.estimator import EstimatorKwargs
|
||||
from trainlib.dataloader import EstimatorDataLoader
|
||||
from trainlib.utils.type import AxesArray, SubplotsKwargs
|
||||
|
||||
type SubplotFn = Callable[[plt.Axes, int, Tensor, Tensor], None]
|
||||
type ContextFn = Callable[[plt.Axes, str], None]
|
||||
|
||||
|
||||
class Plotter[Kw: EstimatorKwargs]:
|
||||
"""
|
||||
TODOs:
|
||||
|
||||
- best fit lines for plots and residuals (compare to ideal lines in each
|
||||
case)
|
||||
- show val options across columns; preview how val is changing across
|
||||
natural training, and what the best will look (so plot like uniform
|
||||
intervals broken over the training epochs at 0, 50, 100, 150, ... and
|
||||
highlight the best one, even if that's not actually the single best
|
||||
epoch)
|
||||
- Implement data and dimension limits; in the instance dataloaders have
|
||||
huge numbers of samples or labels are high-dimensional
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
trainer: Trainer[Any, Kw],
|
||||
dataloaders: list[EstimatorDataLoader[Any, Kw]],
|
||||
kw_to_actual: Callable[[Kw], Tensor],
|
||||
dataloader_labels: list[str] | None = None,
|
||||
) -> None:
|
||||
self.trainer = trainer
|
||||
self.dataloaders = dataloaders
|
||||
self.dataloader_labels = (
|
||||
dataloader_labels or list(map(str, range(1, len(dataloaders)+1)))
|
||||
)
|
||||
self.kw_to_actual = kw_to_actual
|
||||
|
||||
self._outputs: list[list[Tensor]] | None = None
|
||||
self._metrics: list[list[dict[str, float]]] | None = None
|
||||
|
||||
self._data_tuples = None
|
||||
|
||||
@property
|
||||
def data_tuples(self) -> list[tuple[Tensor, Tensor, str]]:
|
||||
"""
|
||||
Produce data items; to be cached. Zip later with axes
|
||||
"""
|
||||
|
||||
self.trainer.estimator.eval()
|
||||
|
||||
if self._data_tuples is not None:
|
||||
return self._data_tuples
|
||||
|
||||
data_tuples = []
|
||||
for i, loader in enumerate(self.dataloaders):
|
||||
label = self.dataloader_labels[i]
|
||||
|
||||
actual = torch.cat([
|
||||
self.kw_to_actual(batch_kwargs).detach().cpu()
|
||||
for batch_kwargs in loader
|
||||
])
|
||||
output = torch.cat([
|
||||
self.trainer.estimator(**batch_kwargs)[0].detach().cpu()
|
||||
for batch_kwargs in loader
|
||||
])
|
||||
|
||||
data_tuples.append((actual, output, label))
|
||||
|
||||
self._data_tuples = data_tuples
|
||||
|
||||
return self._data_tuples
|
||||
|
||||
def get_transposed_handles_labels(
|
||||
self,
|
||||
ax: plt.Axes
|
||||
) -> tuple[list, list]:
|
||||
# transpose legend layout for more natural view
|
||||
handles, labels = ax.get_legend_handles_labels()
|
||||
handles = handles[::2] + handles[1::2]
|
||||
labels = labels[::2] + labels[1::2]
|
||||
|
||||
return handles, labels
|
||||
|
||||
def _lstsq_dim(
|
||||
self,
|
||||
dim_actual: Tensor,
|
||||
dim_output: Tensor
|
||||
) -> tuple[Tensor, Tensor]:
|
||||
A = torch.stack(
|
||||
[dim_actual, torch.ones_like(dim_actual)],
|
||||
dim=1
|
||||
)
|
||||
m, b = torch.linalg.lstsq(A, dim_output).solution
|
||||
|
||||
return m, b
|
||||
|
||||
def _prepare_figure_kwargs(
|
||||
self,
|
||||
rows: int,
|
||||
cols: int,
|
||||
row_size: int | float = 2,
|
||||
col_size: int | float = 4,
|
||||
figure_kwargs: SubplotsKwargs | None = None,
|
||||
) -> SubplotsKwargs:
|
||||
"""
|
||||
"""
|
||||
|
||||
default_figure_kwargs = {
|
||||
"sharex": True,
|
||||
"figsize": (col_size*cols, row_size*rows),
|
||||
}
|
||||
figure_kwargs = {
|
||||
**default_figure_kwargs,
|
||||
**(figure_kwargs or {}),
|
||||
}
|
||||
|
||||
return figure_kwargs
|
||||
|
||||
def _prepare_subplot_kwargs(
|
||||
self,
|
||||
subplot_kwargs: dict | None = None,
|
||||
) -> dict:
|
||||
"""
|
||||
"""
|
||||
|
||||
default_subplot_kwargs = {}
|
||||
subplot_kwargs = {
|
||||
**default_subplot_kwargs,
|
||||
**(subplot_kwargs or {}),
|
||||
}
|
||||
|
||||
return subplot_kwargs
|
||||
|
||||
def _prepare_gof_kwargs(
|
||||
self,
|
||||
gof_kwargs: dict | None = None,
|
||||
) -> dict:
|
||||
"""
|
||||
"""
|
||||
|
||||
default_gof_kwargs = {
|
||||
"alpha": 0.5
|
||||
}
|
||||
gof_kwargs = {
|
||||
**default_gof_kwargs,
|
||||
**(gof_kwargs or {}),
|
||||
}
|
||||
|
||||
return gof_kwargs
|
||||
|
||||
def _create_subplots(
|
||||
self,
|
||||
rows: int,
|
||||
cols: int,
|
||||
row_size: int | float = 2,
|
||||
col_size: int | float = 4,
|
||||
figure_kwargs: SubplotsKwargs | None = None,
|
||||
) -> tuple[plt.Figure, AxesArray]:
|
||||
"""
|
||||
"""
|
||||
|
||||
figure_kwargs: SubplotsKwargs = self._prepare_figure_kwargs(
|
||||
rows,
|
||||
cols,
|
||||
row_size=row_size,
|
||||
col_size=col_size,
|
||||
figure_kwargs=figure_kwargs,
|
||||
)
|
||||
fig, axes = plt.subplots(
|
||||
rows,
|
||||
cols,
|
||||
squeeze=False,
|
||||
**figure_kwargs,
|
||||
) # ty:ignore[no-matching-overload]
|
||||
|
||||
return fig, axes
|
||||
|
||||
def _plot_base(
|
||||
self,
|
||||
subplot_fn: SubplotFn,
|
||||
context_fn: ContextFn,
|
||||
row_size: int | float = 2,
|
||||
col_size: int | float = 4,
|
||||
norm_samples: bool = False,
|
||||
combine_dims: bool = True,
|
||||
figure_kwargs: SubplotsKwargs | None = None,
|
||||
) -> tuple[plt.Figure, AxesArray]:
|
||||
"""
|
||||
Note: transform samples in dataloader definitions beforehand if you
|
||||
want to change data
|
||||
|
||||
.. todo::
|
||||
|
||||
Merge in logic from general diagnostics, allowing collapse from
|
||||
either dim and transposing.
|
||||
|
||||
Later: multi-trial error bars, or at least the ability to pass that
|
||||
downstream
|
||||
|
||||
Parameters:
|
||||
row_size:
|
||||
col_size:
|
||||
figure_kwargs:
|
||||
subplot_kwargs:
|
||||
gof_kwargs:
|
||||
"""
|
||||
|
||||
ndims = self.data_tuples[0][0].size(-1)
|
||||
fig, axes = self._create_subplots(
|
||||
rows=len(self.dataloaders),
|
||||
cols=1 if (norm_samples or combine_dims) else ndims,
|
||||
row_size=row_size,
|
||||
col_size=col_size,
|
||||
figure_kwargs=figure_kwargs,
|
||||
)
|
||||
|
||||
for axes_row, data_tuple in zip(axes, self.data_tuples, strict=True):
|
||||
actual, output, loader_label = data_tuple
|
||||
|
||||
if norm_samples:
|
||||
actual = actual.norm(dim=1, keepdim=True)
|
||||
output = output.norm(dim=1, keepdim=True)
|
||||
|
||||
for dim in range(ndims):
|
||||
ax = axes_row[0 if combine_dims else dim]
|
||||
subplot_fn(ax, dim, actual, output)
|
||||
|
||||
add_plot_context = (
|
||||
norm_samples # dim=0 implicit b/c we break
|
||||
or not combine_dims # add to every subplot across grid
|
||||
or combine_dims and dim == ndims-1 # wait for last dim
|
||||
)
|
||||
|
||||
# always exec plot logic if not combining, o/w exec just once
|
||||
if add_plot_context:
|
||||
context_fn(ax, loader_label)
|
||||
|
||||
# transpose legend layout for more natural view
|
||||
if norm_samples or not combine_dims:
|
||||
ax.legend()
|
||||
else:
|
||||
handles, labels = self.get_transposed_handles_labels(ax)
|
||||
ax.legend(handles, labels, ncols=2)
|
||||
|
||||
# break dimension loop if collapsed by norm
|
||||
if norm_samples:
|
||||
break
|
||||
|
||||
return fig, axes
|
||||
|
||||
# def plot_ordered(...): ...
|
||||
# """
|
||||
# Simple ordered view of output dimensions, with actual and output
|
||||
# overlaid.
|
||||
# """
|
||||
|
||||
def plot_actual_output(
|
||||
self,
|
||||
row_size: int | float = 2,
|
||||
col_size: int | float = 4,
|
||||
norm_samples: bool = False,
|
||||
combine_dims: bool = True,
|
||||
figure_kwargs: SubplotsKwargs | None = None,
|
||||
subplot_kwargs: dict | None = None,
|
||||
gof_kwargs: dict | None = None,
|
||||
) -> tuple[plt.Figure, AxesArray]:
|
||||
"""
|
||||
Plot residual distribution.
|
||||
"""
|
||||
|
||||
subplot_kwargs = self._prepare_subplot_kwargs(subplot_kwargs)
|
||||
gof_kwargs = self._prepare_gof_kwargs(gof_kwargs)
|
||||
colors = plt.rcParams["axes.prop_cycle"].by_key()["color"]
|
||||
|
||||
def subplot_fn(
|
||||
ax: plt.Axes, dim: int, actual: Tensor, output: Tensor
|
||||
) -> None:
|
||||
dim_color = colors[dim % len(colors)]
|
||||
group_name = "norm" if norm_samples else f"$d_{dim}$"
|
||||
|
||||
dim_actual = actual[:, dim]
|
||||
dim_output = output[:, dim]
|
||||
|
||||
ax.scatter(
|
||||
dim_actual, dim_output,
|
||||
color=dim_color,
|
||||
label=group_name,
|
||||
**subplot_kwargs,
|
||||
)
|
||||
|
||||
# plot goodness of fit line
|
||||
m, b = self._lstsq_dim(dim_actual, dim_output)
|
||||
ax.plot(
|
||||
dim_actual, m * dim_actual + b,
|
||||
color=dim_color,
|
||||
label=f"GoF {group_name}",
|
||||
**gof_kwargs,
|
||||
)
|
||||
|
||||
def context_fn(ax: plt.Axes, loader_label: str) -> None:
|
||||
# plot perfect prediction reference line, y=x
|
||||
ax.plot(
|
||||
[0, 1], [0, 1],
|
||||
transform=ax.transAxes,
|
||||
c="black",
|
||||
alpha=0.2,
|
||||
)
|
||||
|
||||
ax.set_title(
|
||||
f"[{loader_label}] True labels vs Predictions"
|
||||
)
|
||||
ax.set_xlabel("actual")
|
||||
ax.set_ylabel("output")
|
||||
|
||||
return self._plot_base(
|
||||
subplot_fn, context_fn,
|
||||
row_size=row_size,
|
||||
col_size=col_size,
|
||||
norm_samples=norm_samples,
|
||||
combine_dims=combine_dims,
|
||||
figure_kwargs=figure_kwargs,
|
||||
)
|
||||
|
||||
def plot_actual_output_residual(
|
||||
self,
|
||||
row_size: int | float = 2,
|
||||
col_size: int | float = 4,
|
||||
order_residuals: bool = False,
|
||||
norm_samples: bool = False,
|
||||
combine_dims: bool = True,
|
||||
figure_kwargs: SubplotsKwargs | None = None,
|
||||
subplot_kwargs: dict | None = None,
|
||||
gof_kwargs: dict | None = None,
|
||||
) -> tuple[plt.Figure, AxesArray]:
|
||||
"""
|
||||
Plot prediction residuals.
|
||||
"""
|
||||
|
||||
subplot_kwargs = self._prepare_subplot_kwargs(subplot_kwargs)
|
||||
gof_kwargs = self._prepare_gof_kwargs(gof_kwargs)
|
||||
colors = plt.rcParams["axes.prop_cycle"].by_key()["color"]
|
||||
|
||||
def subplot_fn(
|
||||
ax: plt.Axes, dim: int, actual: Tensor, output: Tensor
|
||||
) -> None:
|
||||
dim_color = colors[dim % len(colors)]
|
||||
group_name = "norm" if norm_samples else f"$d_{dim}$"
|
||||
|
||||
dim_actual = actual[:, dim]
|
||||
dim_output = output[:, dim]
|
||||
|
||||
residuals = dim_actual - dim_output
|
||||
X, Y = dim_actual, residuals
|
||||
|
||||
if order_residuals:
|
||||
X = range(1, residuals.size(0)+1)
|
||||
Y = residuals[residuals.argsort()]
|
||||
|
||||
ax.scatter(
|
||||
X, Y,
|
||||
color=dim_color,
|
||||
label=group_name,
|
||||
**subplot_kwargs,
|
||||
)
|
||||
|
||||
# plot goodness of fit line
|
||||
if not order_residuals:
|
||||
m, b = self._lstsq_dim(dim_actual, residuals)
|
||||
ax.plot(
|
||||
dim_actual, m * dim_actual + b,
|
||||
color=dim_color,
|
||||
label=f"GoF {group_name}",
|
||||
**gof_kwargs,
|
||||
)
|
||||
|
||||
def context_fn(ax: plt.Axes, loader_label: str) -> None:
|
||||
# compare residuals to y=0
|
||||
ax.axhline(y=0, c="black", alpha=0.2)
|
||||
|
||||
ax.set_title(f"[{loader_label}] Prediction residuals")
|
||||
ax.set_xlabel("actual")
|
||||
ax.set_ylabel("residual")
|
||||
|
||||
return self._plot_base(
|
||||
subplot_fn, context_fn,
|
||||
row_size=row_size,
|
||||
col_size=col_size,
|
||||
norm_samples=norm_samples,
|
||||
combine_dims=combine_dims,
|
||||
figure_kwargs=figure_kwargs,
|
||||
)
|
||||
|
||||
def plot_actual_output_residual_dist(
|
||||
self,
|
||||
row_size: int | float = 2,
|
||||
col_size: int | float = 4,
|
||||
norm_samples: bool = False,
|
||||
combine_dims: bool = True,
|
||||
figure_kwargs: SubplotsKwargs | None = None,
|
||||
subplot_kwargs: dict | None = None,
|
||||
gof_kwargs: dict | None = None,
|
||||
) -> tuple[plt.Figure, AxesArray]:
|
||||
"""
|
||||
Plot residual distribution.
|
||||
"""
|
||||
|
||||
subplot_kwargs = self._prepare_subplot_kwargs(subplot_kwargs)
|
||||
gof_kwargs = self._prepare_gof_kwargs(gof_kwargs)
|
||||
colors = plt.rcParams["axes.prop_cycle"].by_key()["color"]
|
||||
|
||||
def subplot_fn(
|
||||
ax: plt.Axes, dim: int, actual: Tensor, output: Tensor
|
||||
) -> None:
|
||||
dim_color = colors[dim % len(colors)]
|
||||
group_name = "norm" if norm_samples else f"$d_{dim}$"
|
||||
|
||||
dim_actual = actual[:, dim]
|
||||
dim_output = output[:, dim]
|
||||
|
||||
N = dim_actual.size(0)
|
||||
residuals = dim_actual - dim_output
|
||||
|
||||
_, _, patches = ax.hist(
|
||||
residuals.abs(),
|
||||
bins=int(np.sqrt(N)),
|
||||
density=True,
|
||||
alpha=0.3,
|
||||
color=dim_color,
|
||||
label=group_name,
|
||||
**subplot_kwargs
|
||||
)
|
||||
|
||||
mu = residuals.abs().mean().item()
|
||||
ax.axvline(mu, linestyle=":", c=dim_color, label=f"$\\mu_{dim}$")
|
||||
|
||||
def context_fn(ax: plt.Axes, loader_label: str) -> None:
|
||||
ax.set_title(f"[{loader_label}] Residual distribution")
|
||||
ax.set_xlabel("actual")
|
||||
ax.set_ylabel("residual (density)")
|
||||
|
||||
return self._plot_base(
|
||||
subplot_fn, context_fn,
|
||||
row_size=row_size,
|
||||
col_size=col_size,
|
||||
norm_samples=norm_samples,
|
||||
combine_dims=combine_dims,
|
||||
figure_kwargs=figure_kwargs,
|
||||
)
|
||||
|
||||
def estimator_diagnostic(
|
||||
self,
|
||||
row_size: int | float = 2,
|
||||
col_size: int | float = 4,
|
||||
session_name: str | None = None,
|
||||
combine_groups: bool = False,
|
||||
combine_metrics: bool = False,
|
||||
transpose_layout: bool = False,
|
||||
figure_kwargs: SubplotsKwargs | None = None,
|
||||
) -> tuple[plt.Figure, AxesArray]:
|
||||
session_map = self.trainer._event_log
|
||||
session_name = session_name or next(iter(session_map))
|
||||
groups = session_map[session_name]
|
||||
num_metrics = len(groups[next(iter(groups))])
|
||||
# colors = plt.rcParams["axes.prop_cycle"].by_key()["color"]
|
||||
|
||||
rows = 1 if combine_groups else len(groups)
|
||||
cols = 1 if combine_metrics else num_metrics
|
||||
if transpose_layout:
|
||||
rows, cols = cols, rows
|
||||
|
||||
fig, axes = self._create_subplots(
|
||||
rows=rows,
|
||||
cols=cols,
|
||||
row_size=row_size,
|
||||
col_size=col_size,
|
||||
figure_kwargs=figure_kwargs,
|
||||
)
|
||||
if transpose_layout:
|
||||
axes = axes.T
|
||||
|
||||
for i, group_name in enumerate(groups):
|
||||
axes_row = axes[0 if combine_groups else i]
|
||||
group_metrics = groups[group_name]
|
||||
|
||||
for j, metric_name in enumerate(group_metrics):
|
||||
ax = axes_row[0 if combine_metrics else j]
|
||||
|
||||
metric_dict = group_metrics[metric_name]
|
||||
metric_data = np.array([
|
||||
(k, np.mean(v)) for k, v in metric_dict.items()
|
||||
])
|
||||
|
||||
if combine_groups and combine_metrics:
|
||||
label = f"{group_name}-{metric_name}"
|
||||
title_prefix = "all"
|
||||
elif combine_groups:
|
||||
label = group_name
|
||||
title_prefix = metric_name
|
||||
# elif combine_metrics:
|
||||
else:
|
||||
label = metric_name
|
||||
title_prefix = group_name
|
||||
# else:
|
||||
# label = ""
|
||||
# title_prefix = f"{group_name},{metric_name}"
|
||||
|
||||
ax.plot(
|
||||
metric_data[:, 0],
|
||||
metric_data[:, 1],
|
||||
label=label,
|
||||
# color=colors[j],
|
||||
)
|
||||
|
||||
ax.set_title(f"[{title_prefix}] Metrics over epochs")
|
||||
ax.set_xlabel("epoch")
|
||||
ax.set_ylabel("value")
|
||||
ax.legend()
|
||||
|
||||
return fig, axes
|
||||
@@ -1,49 +1,68 @@
|
||||
"""
|
||||
Core interface for training ``Estimators`` with ``Datasets``
|
||||
|
||||
.. admonition:: Design of preview ``get_dataloaders()``
|
||||
|
||||
|
||||
Note how much this method is doing, and the positivity in letting that be
|
||||
more explicit elsewhere. The assignment of transforms to datasets before
|
||||
wrapping as loaders is chief among these items, alongside the balancing and
|
||||
splitting; I think those are hamfisted here to make it work with the old
|
||||
setup, but I generally it's not consistent with the name "get dataloaders"
|
||||
(i.e., and also balance and split and set transforms)
|
||||
"""
|
||||
|
||||
import os
|
||||
import time
|
||||
import logging
|
||||
from io import BytesIO
|
||||
from copy import deepcopy
|
||||
from typing import Any, Self
|
||||
from typing import Any
|
||||
from pathlib import Path
|
||||
from collections import defaultdict
|
||||
from collections.abc import Callable
|
||||
|
||||
import torch
|
||||
from tqdm import tqdm
|
||||
from torch import cuda, Tensor
|
||||
from torch.optim import Optimizer
|
||||
from torch.nn.utils import clip_grad_norm_
|
||||
from torch.utils.data import Dataset, DataLoader
|
||||
from torch.utils.tensorboard import SummaryWriter
|
||||
|
||||
from trainlib.dataset import BatchedDataset
|
||||
from trainlib.estimator import Estimator, EstimatorKwargs
|
||||
from trainlib.transform import Transform
|
||||
from trainlib.utils.type import (
|
||||
SplitKwargs,
|
||||
LoaderKwargs,
|
||||
BalanceKwargs,
|
||||
)
|
||||
from trainlib.utils.map import nested_defaultdict
|
||||
from trainlib.dataloader import EstimatorDataLoader
|
||||
from trainlib.utils.module import ModelWrapper
|
||||
from trainlib.utils.session import ensure_same_device
|
||||
|
||||
logger: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Trainer[I, K: EstimatorKwargs]:
|
||||
class Trainer[I, Kw: EstimatorKwargs]:
|
||||
"""
|
||||
Training interface for updating ``Estimators`` with ``Datasets``.
|
||||
Training interface for optimizing parameters of ``Estimators`` with
|
||||
``Datasets``.
|
||||
|
||||
This class is generic to a dataset item type ``I`` and an estimator kwarg
|
||||
type ``Kw``. These are the two primary components ``Trainer`` objects need
|
||||
to coordinate: they ultimately rely on a provided map to ensure data items
|
||||
(type ``I``) from a dataset are appropriately routed as inputs to key
|
||||
estimator methods (like ``forward()`` and ``loss()``), which accept inputs
|
||||
of type ``Kw``.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
estimator: Estimator[K],
|
||||
estimator: Estimator[Kw],
|
||||
device: str | None = None,
|
||||
chkpt_dir: str = "chkpt/",
|
||||
tblog_dir: str = "tblog/",
|
||||
) -> None:
|
||||
"""
|
||||
Parameters:
|
||||
estimator: `Estimator` model object
|
||||
estimator: ``Estimator`` model object
|
||||
device: device on which to carry out training
|
||||
chkpt_dir: directory to write model checkpoints
|
||||
tblog_dir: directory to write TensorBoard logs
|
||||
"""
|
||||
|
||||
self.device: str
|
||||
@@ -78,6 +97,7 @@ class Trainer[I, K: EstimatorKwargs]:
|
||||
|
||||
self.estimator = estimator
|
||||
self.estimator.to(self.device)
|
||||
self._event_log = nested_defaultdict(4, list)
|
||||
|
||||
self.chkpt_dir = Path(chkpt_dir).resolve()
|
||||
self.tblog_dir = Path(tblog_dir).resolve()
|
||||
@@ -86,55 +106,259 @@ class Trainer[I, K: EstimatorKwargs]:
|
||||
|
||||
def reset(self) -> None:
|
||||
"""
|
||||
Set base tracking parameters.
|
||||
Set initial tracking parameters for the primary training loop.
|
||||
"""
|
||||
|
||||
self._step: int = 0
|
||||
self._epoch: int = 0
|
||||
self._summary: dict[str, list[tuple[float, int]]] = defaultdict(list)
|
||||
self._summary = defaultdict(lambda: defaultdict(list))
|
||||
|
||||
self._val_loss = float("inf")
|
||||
self._best_val_loss = float("inf")
|
||||
self._conv_loss = float("inf")
|
||||
self._best_conv_loss = float("inf")
|
||||
self._stagnant_epochs = 0
|
||||
self._best_model_state_dict: dict[str, Any] = {}
|
||||
|
||||
def _train_epoch(
|
||||
self,
|
||||
loader: EstimatorDataLoader[Any, Kw],
|
||||
optimizers: tuple[Optimizer, ...],
|
||||
max_grad_norm: float | None = None,
|
||||
progress_bar: tqdm | None = None,
|
||||
) -> list[float]:
|
||||
"""
|
||||
Train the estimator for a single epoch.
|
||||
"""
|
||||
|
||||
loss_sums = []
|
||||
self.estimator.train()
|
||||
for i, batch_kwargs in enumerate(loader):
|
||||
batch_kwargs = ensure_same_device(batch_kwargs, self.device)
|
||||
losses = self.estimator.loss(**batch_kwargs)
|
||||
|
||||
for o_idx, (loss, optimizer) in enumerate(
|
||||
zip(losses, optimizers, strict=True)
|
||||
):
|
||||
if len(loss_sums) <= o_idx:
|
||||
loss_sums.append(0.0)
|
||||
loss_sums[o_idx] += loss.item()
|
||||
|
||||
optimizer.zero_grad()
|
||||
loss.backward()
|
||||
|
||||
# clip gradients for optimizer's parameters
|
||||
if max_grad_norm is not None:
|
||||
clip_grad_norm_(
|
||||
self._get_optimizer_parameters(optimizer),
|
||||
max_norm=max_grad_norm
|
||||
)
|
||||
|
||||
optimizer.step()
|
||||
|
||||
# set loop loss to running average (reducing if multi-loss)
|
||||
loss_avg = sum(loss_sums) / (len(loss_sums)*(i+1))
|
||||
|
||||
if progress_bar:
|
||||
progress_bar.update(1)
|
||||
progress_bar.set_postfix(
|
||||
epoch=self._epoch,
|
||||
mode="opt",
|
||||
data="train",
|
||||
loss=f"{loss_avg:8.2f}",
|
||||
)
|
||||
|
||||
# step estimator hyperparam schedules
|
||||
self.estimator.epoch_step()
|
||||
|
||||
return loss_sums
|
||||
|
||||
def _eval_epoch(
|
||||
self,
|
||||
loader: EstimatorDataLoader[Any, Kw],
|
||||
label: str,
|
||||
progress_bar: tqdm | None = None,
|
||||
) -> list[float]:
|
||||
"""
|
||||
Perform and record validation scores for a single epoch.
|
||||
|
||||
.. On summary writers::
|
||||
|
||||
See the similarly titled note for ``_train_epoch()`` for general
|
||||
remarks about optimizers and how we're recording losses/metrics.
|
||||
The same mostly applies here in the validation setting, but we
|
||||
crucially aren't stepping forward ``_step`` between batches. This
|
||||
means that, even though we're writing losses and metrics once for
|
||||
each val batch, those values are simply piling up under the same
|
||||
summary item name. This is consistent with how we report training
|
||||
items: the model isn't changing across val batches, so these don't
|
||||
get plotted at different step points (which might be interpreted as
|
||||
performance along model progression when it's actually variation
|
||||
across batches). Currently, this "piling" means we actually write
|
||||
batch values to the same event name at the same step rather than
|
||||
manually averaging beforehand; we defer the handling to TB, and
|
||||
although that may technically be discouraged, the val plots render
|
||||
collections effectively as a vertical line between the min and max
|
||||
value (which I find to be a satisfactory way to view cross-batch
|
||||
variation during each val epoch).
|
||||
"""
|
||||
|
||||
loss_sums = []
|
||||
self.estimator.eval()
|
||||
for i, batch_kwargs in enumerate(loader):
|
||||
batch_kwargs = ensure_same_device(batch_kwargs, self.device)
|
||||
losses = self.estimator.loss(**batch_kwargs)
|
||||
|
||||
# once-per-session logging
|
||||
if self._epoch == 0 and i == 0:
|
||||
self._writer.add_graph(
|
||||
ModelWrapper(self.estimator), batch_kwargs
|
||||
)
|
||||
# once-per-epoch logging
|
||||
if i == 0:
|
||||
self.estimator.epoch_write(
|
||||
self._writer,
|
||||
step=self._epoch,
|
||||
group=label,
|
||||
**batch_kwargs
|
||||
)
|
||||
|
||||
loss_items = []
|
||||
for o_idx, loss in enumerate(losses):
|
||||
if len(loss_sums) <= o_idx:
|
||||
loss_sums.append(0.0)
|
||||
|
||||
loss_item = loss.item()
|
||||
loss_sums[o_idx] += loss_item
|
||||
loss_items.append(loss_item)
|
||||
|
||||
# set loop loss to running average (reducing if multi-loss)
|
||||
loss_avg = sum(loss_sums) / (len(loss_sums)*(i+1))
|
||||
|
||||
if progress_bar:
|
||||
progress_bar.update(1)
|
||||
progress_bar.set_postfix(
|
||||
epoch=self._epoch,
|
||||
mode="eval",
|
||||
data=label,
|
||||
loss=f"{loss_avg:8.2f}",
|
||||
)
|
||||
|
||||
# log individual loss terms after each batch
|
||||
for o_idx, loss_item in enumerate(loss_items):
|
||||
self._log_event(label, f"loss_{o_idx}", loss_item)
|
||||
|
||||
# log metrics for batch
|
||||
estimator_metrics = self.estimator.metrics(**batch_kwargs)
|
||||
for metric_name, metric_value in estimator_metrics.items():
|
||||
self._log_event(label, metric_name, metric_value)
|
||||
|
||||
avg_losses = [loss_sum / (i+1) for loss_sum in loss_sums]
|
||||
|
||||
return avg_losses
|
||||
|
||||
def _eval_loaders(
|
||||
self,
|
||||
train_loader: EstimatorDataLoader[Any, Kw],
|
||||
val_loader: EstimatorDataLoader[Any, Kw] | None = None,
|
||||
aux_loaders: list[EstimatorDataLoader[Any, Kw]] | None = None,
|
||||
progress_bar: tqdm | None = None,
|
||||
) -> tuple[list[float], list[float] | None, *list[float]]:
|
||||
"""
|
||||
Evaluate estimator over each provided dataloader.
|
||||
|
||||
This streamlines the general train/val/etc evaluation pipeline during
|
||||
training. This triggers logging of summary items, TensorBoard writes,
|
||||
etc, and is therefore exclusively intended for use inside the primary
|
||||
``train()`` loop.
|
||||
|
||||
If looking to use this method publicly, you should instead work with
|
||||
individual dataloaders and call helper methods like
|
||||
``get_batch_outputs()`` or ``get_batch_metrics()`` while iterating over
|
||||
batches. This will have no internal side effects and provides much more
|
||||
information (just aggregated losses are provided here).
|
||||
|
||||
.. admonition:: On epoch counting
|
||||
|
||||
Epoch counts start at 0 to allow for a sensible place to benchmark
|
||||
the initial (potentially untrained/pre-trained) model before any
|
||||
training data is seen. In the train loop, we increment the epoch
|
||||
immediately, and all logging happens under the epoch value that's
|
||||
set at the start of the iteration (rather than incrementing at the
|
||||
end). Before beginning an additional training iteration, the
|
||||
convergence condition in the ``while`` is effectively checking what
|
||||
happened during the last epoch (the counter has not yet been
|
||||
incremented); if no convergence, we begin again. (This is only
|
||||
being noted because the epoch counting was previously quite
|
||||
different: indexing started at ``1``, we incremented at the end of
|
||||
the loop, and we didn't evaluate the model before the loop began.
|
||||
This affects how we interpret plots and TensorBoard records, for
|
||||
instance, so it's useful to spell out the approach clearly
|
||||
somewhere given the many possible design choices here.)
|
||||
"""
|
||||
|
||||
train_loss = self._eval_epoch(train_loader, "train", progress_bar)
|
||||
|
||||
val_loss = None
|
||||
if val_loader is not None:
|
||||
val_loss = self._eval_epoch(val_loader, "val", progress_bar)
|
||||
|
||||
aux_loaders = aux_loaders or []
|
||||
aux_losses = [
|
||||
self._eval_epoch(aux_loader, f"aux{i}", progress_bar)
|
||||
for i, aux_loader in enumerate(aux_loaders)
|
||||
]
|
||||
|
||||
return train_loss, val_loss, *aux_losses
|
||||
|
||||
def train(
|
||||
self,
|
||||
dataset: BatchedDataset[..., ..., I],
|
||||
batch_estimator_map: Callable[[I, Self], K],
|
||||
train_loader: EstimatorDataLoader[Any, Kw],
|
||||
val_loader: EstimatorDataLoader[Any, Kw] | None = None,
|
||||
aux_loaders: list[EstimatorDataLoader[Any, Kw]] | None = None,
|
||||
*,
|
||||
lr: float = 1e-3,
|
||||
eps: float = 1e-8,
|
||||
max_grad_norm: float | None = None,
|
||||
max_epochs: int = 10,
|
||||
stop_after_epochs: int = 5,
|
||||
batch_size: int = 256,
|
||||
val_frac: float = 0.1,
|
||||
train_transform: Transform | None = None,
|
||||
val_transform: Transform | None = None,
|
||||
dataset_split_kwargs: SplitKwargs | None = None,
|
||||
dataset_balance_kwargs: BalanceKwargs | None = None,
|
||||
dataloader_kwargs: LoaderKwargs | None = None,
|
||||
summarize_every: int = 1,
|
||||
chkpt_every: int = 1,
|
||||
resume_latest: bool = False,
|
||||
session_name: str | None = None,
|
||||
summary_writer: SummaryWriter | None = None,
|
||||
) -> Estimator:
|
||||
"""
|
||||
.. todo::
|
||||
|
||||
- consider making the dataloader ``collate_fn`` an explicit
|
||||
parameter with a type signature that reflects ``B``, connecting
|
||||
the ``batch_estimator_map`` somewhere. Might also re-type a
|
||||
``DataLoader`` in-house to allow a generic around ``B``
|
||||
- Rework the validation specification. Accept something like a
|
||||
"validate_with" parameter, or perhaps just move entirely to
|
||||
accepting a dataloader list, label list. You might then also need
|
||||
a "train_with," and you could set up sensible defaults so you
|
||||
basically have the same interaction as now. The "problem" is you
|
||||
always need a train set, and there's some clearly dependent logic
|
||||
on a val set, but you don't *need* val, so this should be
|
||||
slightly reworked (and the more general, *probably* the better in
|
||||
this case, given I want to plug into the Plotter with possibly
|
||||
several purely eval sets over the model training lifetime).
|
||||
|
||||
Note: this method attempts to implement a general scheme for passing
|
||||
needed items to the estimator's loss function from the dataloader. The
|
||||
abstract `Estimator` base only requires the model output be provided
|
||||
abstract ``Estimator`` base only requires the model output be provided
|
||||
for any given loss calculation, but concrete estimators will often
|
||||
require additional arguments (e.g., labels or length masks, as
|
||||
is the case with sequential models). In any case, this method defers
|
||||
any further logic to the `loss` method of the underlying estimator, so
|
||||
require additional arguments (e.g., labels or length masks, as is the
|
||||
case with sequential models). In any case, this method defers any
|
||||
further logic to the ``loss`` method of the underlying estimator, so
|
||||
one should take care to synchronize the sample structure with `dataset`
|
||||
to match that expected by `self.estimator.loss(...)`.
|
||||
to match that expected by ``self.estimator.loss(...)``.
|
||||
|
||||
On batch_estimator_map:
|
||||
.. admonition:: On ``batch_estimator_map``
|
||||
|
||||
Dataloader collate functions are responsible for mapping a collection
|
||||
of items into an item of collections, roughly speaking. If items are
|
||||
tuples of tensors,
|
||||
Dataloader collate functions are responsible for mapping a
|
||||
collection of items into an item of collections, roughly speaking.
|
||||
If items are tuples of tensors,
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
[
|
||||
( [1, 1], [1, 1] ),
|
||||
@@ -145,6 +369,8 @@ class Trainer[I, K: EstimatorKwargs]:
|
||||
the collate function maps back into the item skeleton, producing a
|
||||
single tuple of (stacked) tensors
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
( [[1, 1],
|
||||
[2, 2],
|
||||
[3, 3]],
|
||||
@@ -153,13 +379,58 @@ class Trainer[I, K: EstimatorKwargs]:
|
||||
[2, 2],
|
||||
[3, 3]] )
|
||||
|
||||
This function should map from batches (which should be *item shaped*,
|
||||
i.e., have an `I` skeleton, even if stacked items may be different on
|
||||
the inside) into estimator keyword arguments (type `K`).
|
||||
This function should map from batches - which *may* be item
|
||||
shaped, i.e., have an ``I`` skeleton, even if stacked items may be
|
||||
different on the inside - into estimator keyword arguments (type
|
||||
``Kw``). Collation behavior from a DataLoader (which can be
|
||||
customized) doesn't consistently yield a known type shape, however,
|
||||
so it's not appropriate to use ``I`` as the callable param type.
|
||||
|
||||
.. admonition:: On session management
|
||||
|
||||
This method works around an implicit notion of training sessions.
|
||||
Estimators are set during instantiation and effectively coupled
|
||||
with ``Trainer`` instances, but datasets can be supplied
|
||||
dynamically here. One can, for instance, run under one condition
|
||||
(specific dataset, number of epochs, etc), then resume later under
|
||||
another. Relevant details persist across calls: the estimator is
|
||||
still attached, best val scores stowed, current epoch tracked. By
|
||||
default, new ``session_names`` are always generated, but you can
|
||||
write to the same TB location if you using the same
|
||||
``session_name`` across calls; that's about as close to a direct
|
||||
training resume as you could want.
|
||||
|
||||
If restarting training on new datasets, including short
|
||||
fine-tuning on training-plus-validation data, it's often sensible
|
||||
to call ``.reset()`` between ``.train()`` calls. While the same
|
||||
estimator will be used, tracked variables will be wiped; subsequent
|
||||
model updates take place under a fresh epoch, no val losses, and be
|
||||
logged under a separate TB session. This is the general approach to
|
||||
"piecemeal" training, i.e., incremental model updates under varying
|
||||
conditions (often just data changes).
|
||||
|
||||
.. warning::
|
||||
|
||||
Validation convergence when there are multiple losses may be
|
||||
ambiguous. These are cases where certain parameter sets are
|
||||
optimized independently; the sum over these losses may not reflect
|
||||
expected or consistent behavior. For instance, we may record a low
|
||||
cumulative loss early with a small 1st loss and moderate 2nd loss,
|
||||
while later encountering a moderate 1st lost and small 2nd loss. We
|
||||
might prefer the latter case, while ``_converged()`` will stick to
|
||||
the former -- we need to consider possible weighting across losses,
|
||||
or storing possibly several best models (e.g., for each loss, the
|
||||
model that scores best, plus the one scoring best cumulatively,
|
||||
etc).
|
||||
|
||||
Parameters:
|
||||
dataset: dataset to train the estimator
|
||||
batch_estimator_map: function mapping from batch data to expected
|
||||
estimator kwargs
|
||||
lr: learning rate (default: 1e-3)
|
||||
eps: adam EPS (default: 1e-8)
|
||||
max_grad_norm: upper bound to use when clipping gradients. If left
|
||||
as ``None``, no gradient clipping is performed.
|
||||
max_epochs: maximum number of training epochs
|
||||
stop_after_epochs: number of epochs with stagnant validation losses
|
||||
to allow before early stopping. If training stops earlier, the
|
||||
@@ -172,182 +443,63 @@ class Trainer[I, K: EstimatorKwargs]:
|
||||
dataset
|
||||
val_split_frac: fraction of dataset to use for validation
|
||||
chkpt_every: how often model checkpoints should be saved
|
||||
resume_latest: resume training from the latest available checkpoint
|
||||
in the `chkpt_dir`
|
||||
"""
|
||||
|
||||
logger.info("> Begin train loop:")
|
||||
logger.info(f"| > {lr=}")
|
||||
logger.info(f"| > {eps=}")
|
||||
logger.info(f"| > {max_epochs=}")
|
||||
logger.info(f"| > {batch_size=}")
|
||||
logger.info(f"| > {val_frac=}")
|
||||
logger.info(f"| > {chkpt_every=}")
|
||||
logger.info(f"| > {resume_latest=}")
|
||||
logger.info(f"| > with device: {self.device}")
|
||||
logger.info(f"| > core count: {os.cpu_count()}")
|
||||
|
||||
writer: SummaryWriter
|
||||
dir_prefix = str(int(time.time()))
|
||||
if summary_writer is None:
|
||||
writer = SummaryWriter(f"{self.tblog_dir}")
|
||||
else:
|
||||
writer = summary_writer
|
||||
|
||||
train_loader, val_loader = self.get_dataloaders(
|
||||
dataset,
|
||||
batch_size,
|
||||
val_frac=val_frac,
|
||||
train_transform=train_transform,
|
||||
val_transform=val_transform,
|
||||
dataset_split_kwargs=dataset_split_kwargs,
|
||||
dataset_balance_kwargs=dataset_balance_kwargs,
|
||||
dataloader_kwargs=dataloader_kwargs,
|
||||
)
|
||||
self._session_name = session_name or str(int(time.time()))
|
||||
tblog_path = Path(self.tblog_dir, self._session_name)
|
||||
self._writer = summary_writer or SummaryWriter(f"{tblog_path}")
|
||||
progress_bar = tqdm(train_loader, unit="batch")
|
||||
|
||||
# evaluate model on dataloaders once before training starts
|
||||
self._eval_loaders(train_loader, val_loader, aux_loaders, progress_bar)
|
||||
optimizers = self.estimator.optimizers(lr=lr, eps=eps)
|
||||
|
||||
self._step = 0
|
||||
self._epoch = 1 # start from 1 for logging convenience
|
||||
while self._epoch <= max_epochs and not self._converged(
|
||||
while self._epoch < max_epochs and not self._converged(
|
||||
self._epoch, stop_after_epochs
|
||||
):
|
||||
print(f"Training epoch {self._epoch}/{max_epochs}...")
|
||||
print(f"Stagnant epochs {self._stagnant_epochs}/{stop_after_epochs}...")
|
||||
self._epoch += 1
|
||||
train_frac = f"{self._epoch}/{max_epochs}"
|
||||
stag_frac = f"{self._stagnant_epochs}/{stop_after_epochs}"
|
||||
#print(f"Training epoch {train_frac}...")
|
||||
#print(f"Stagnant epochs {stag_frac}...")
|
||||
|
||||
epoch_start_time = time.time()
|
||||
train_loss_sums = []
|
||||
self.estimator.train()
|
||||
with tqdm(train_loader, unit="batch") as train_epoch:
|
||||
for i, batch_data in enumerate(train_epoch):
|
||||
est_kwargs = batch_estimator_map(batch_data, self)
|
||||
inputs = est_kwargs["inputs"]
|
||||
|
||||
# one-time logging
|
||||
if self._step == 0:
|
||||
writer.add_graph(ModelWrapper(self.estimator), est_kwargs)
|
||||
|
||||
# once-per-epoch logging
|
||||
if i == 0:
|
||||
self.estimator.epoch_write(
|
||||
writer,
|
||||
step=self._step,
|
||||
val=False,
|
||||
**est_kwargs
|
||||
self._train_epoch(
|
||||
train_loader,
|
||||
optimizers,
|
||||
max_grad_norm,
|
||||
progress_bar=progress_bar
|
||||
)
|
||||
epoch_end_time = time.time() - epoch_start_time
|
||||
self._log_event("train", "epoch_duration", epoch_end_time)
|
||||
|
||||
train_losses = self.estimator.loss(**est_kwargs)
|
||||
train_loss_items = []
|
||||
for o_idx, optimizer in enumerate(optimizers):
|
||||
optimizer.zero_grad()
|
||||
train_loss = next(train_losses)
|
||||
|
||||
if len(train_loss_sums) <= o_idx:
|
||||
train_loss_sums.append(0.0)
|
||||
|
||||
train_loss_item = train_loss.item()
|
||||
train_loss_sums[o_idx] += train_loss_item
|
||||
train_loss_items.append(train_loss_item)
|
||||
|
||||
train_loss.backward()
|
||||
|
||||
# clip gradients for optimizer's parameters
|
||||
if max_grad_norm is not None:
|
||||
opt_params = self._get_optimizer_parameters(optimizer)
|
||||
clip_grad_norm_(opt_params, max_norm=max_grad_norm)
|
||||
|
||||
optimizer.step()
|
||||
|
||||
self._step += len(inputs)
|
||||
|
||||
for train_loss_item, train_loss_sum in zip(
|
||||
train_loss_items,
|
||||
train_loss_sums,
|
||||
strict=True,
|
||||
):
|
||||
train_epoch.set_postfix(loss=f"{train_loss_sum/(i+1):8.2f}")
|
||||
self._add_summary_item("train_loss", train_loss_item)
|
||||
|
||||
estimator_metrics = self.estimator.metrics(**est_kwargs)
|
||||
for metric_name, metric_value in estimator_metrics.items():
|
||||
self._add_summary_item(f"train_{metric_name}", metric_value)
|
||||
|
||||
self.estimator.epoch_step()
|
||||
|
||||
for li, train_loss_sum in enumerate(train_loss_sums):
|
||||
self._add_summary_item(
|
||||
f"train_loss{li}_epoch", train_loss_sum / len(train_loader)
|
||||
train_loss, val_loss, *_ = self._eval_loaders(
|
||||
train_loader, val_loader, aux_loaders, progress_bar
|
||||
)
|
||||
|
||||
if val_frac > 0:
|
||||
val_loss_sums = []
|
||||
self.estimator.eval()
|
||||
with tqdm(val_loader, unit="batch") as val_epoch:
|
||||
for i, batch_data in enumerate(val_epoch):
|
||||
est_kwargs = batch_estimator_map(batch_data, self)
|
||||
inputs = est_kwargs["inputs"]
|
||||
|
||||
# once-per-epoch logging
|
||||
if i == 0:
|
||||
self.estimator.epoch_write(
|
||||
writer,
|
||||
step=self._step,
|
||||
val=True,
|
||||
**est_kwargs
|
||||
)
|
||||
|
||||
val_losses = self.estimator.loss(**est_kwargs)
|
||||
val_loss_items = []
|
||||
for o_idx in range(len(optimizers)):
|
||||
val_loss = next(val_losses)
|
||||
|
||||
if len(val_loss_sums) <= o_idx:
|
||||
val_loss_sums.append(0.0)
|
||||
|
||||
val_loss_item = val_loss.item()
|
||||
val_loss_sums[o_idx] += val_loss_item
|
||||
val_loss_items.append(val_loss_item)
|
||||
|
||||
for val_loss_item, val_loss_sum in zip(
|
||||
val_loss_items,
|
||||
val_loss_sums,
|
||||
strict=True,
|
||||
):
|
||||
val_epoch.set_postfix(loss=f"{val_loss_sum/(i+1):8.2f}")
|
||||
self._add_summary_item("val_loss", val_loss_item)
|
||||
|
||||
estimator_metrics = self.estimator.metrics(**est_kwargs)
|
||||
for metric_name, metric_value in estimator_metrics.items():
|
||||
self._add_summary_item(f"val_{metric_name}", metric_value)
|
||||
|
||||
for li, val_loss_sum in enumerate(val_loss_sums):
|
||||
self._add_summary_item(
|
||||
f"val_loss{li}_epoch", val_loss_sum / len(val_loader)
|
||||
)
|
||||
|
||||
# convergence of multiple losses may be ambiguous
|
||||
self._val_loss = sum(val_loss_sums) / len(val_loader)
|
||||
|
||||
self._add_summary_item("epoch_time_sec", time.time() - epoch_start_time)
|
||||
# determine loss to use for measuring convergence
|
||||
conv_loss = val_loss if val_loss else train_loss
|
||||
self._conv_loss = sum(conv_loss) / len(conv_loss)
|
||||
|
||||
if self._epoch % summarize_every == 0:
|
||||
self._summarize(writer, self._epoch)
|
||||
|
||||
# save checkpoint
|
||||
self._summarize()
|
||||
if self._epoch % chkpt_every == 0:
|
||||
self.save_model(
|
||||
self._epoch, self.chkpt_dir, dir_prefix
|
||||
)
|
||||
|
||||
self._epoch += 1
|
||||
self.save_model()
|
||||
|
||||
return self.estimator
|
||||
|
||||
def _converged(self, epoch: int, stop_after_epochs: int) -> bool:
|
||||
converged = False
|
||||
|
||||
if epoch == 1 or self._val_loss < self._best_val_loss:
|
||||
self._best_val_loss = self._val_loss
|
||||
if epoch == 0 or self._conv_loss < self._best_val_loss:
|
||||
self._best_val_loss = self._conv_loss
|
||||
self._stagnant_epochs = 0
|
||||
self._best_model_state_dict = deepcopy(self.estimator.state_dict())
|
||||
else:
|
||||
@@ -359,94 +511,25 @@ class Trainer[I, K: EstimatorKwargs]:
|
||||
|
||||
return converged
|
||||
|
||||
@staticmethod
|
||||
def get_dataloaders(
|
||||
dataset: BatchedDataset,
|
||||
batch_size: int,
|
||||
val_frac: float = 0.1,
|
||||
train_transform: Transform | None = None,
|
||||
val_transform: Transform | None = None,
|
||||
dataset_split_kwargs: SplitKwargs | None = None,
|
||||
dataset_balance_kwargs: BalanceKwargs | None = None,
|
||||
dataloader_kwargs: LoaderKwargs | None = None,
|
||||
) -> tuple[DataLoader, DataLoader]:
|
||||
def _summarize(self) -> None:
|
||||
"""
|
||||
Create training and validation dataloaders for the provided dataset.
|
||||
Flush the training summary to the TensorBoard summary writer and print
|
||||
metrics for the current epoch.
|
||||
"""
|
||||
|
||||
if dataset_split_kwargs is None:
|
||||
dataset_split_kwargs = {}
|
||||
|
||||
if dataset_balance_kwargs is not None:
|
||||
dataset.balance(**dataset_balance_kwargs)
|
||||
|
||||
if val_frac <= 0:
|
||||
dataset.post_transform = train_transform
|
||||
train_loader_kwargs: LoaderKwargs = {
|
||||
"batch_size": min(batch_size, len(dataset)),
|
||||
"num_workers": 0,
|
||||
"shuffle": True,
|
||||
}
|
||||
if dataloader_kwargs is not None:
|
||||
train_loader_kwargs: LoaderKwargs = {
|
||||
**train_loader_kwargs,
|
||||
**dataloader_kwargs
|
||||
}
|
||||
|
||||
return (
|
||||
DataLoader(dataset, **train_loader_kwargs),
|
||||
DataLoader(Dataset())
|
||||
print(f"==== Epoch [{self._epoch}] summary ====")
|
||||
for (group, name), epoch_map in self._summary.items():
|
||||
for epoch, values in epoch_map.items():
|
||||
# compute average over batch items recorded for the epoch
|
||||
mean = torch.tensor(values).mean().item()
|
||||
self._writer.add_scalar(f"{group}-{name}", mean, epoch)
|
||||
if epoch == self._epoch:
|
||||
print(
|
||||
f"> ({len(values)}) [{group}] {name} :: {mean:.2f}"
|
||||
)
|
||||
|
||||
train_dataset, val_dataset = dataset.split(
|
||||
[1 - val_frac, val_frac],
|
||||
**dataset_split_kwargs,
|
||||
)
|
||||
|
||||
# Dataset.split() returns light Subset objects of shallow copies of the
|
||||
# underlying dataset; can change the transform attribute of both splits
|
||||
# w/o overwriting
|
||||
train_dataset.post_transform = train_transform
|
||||
val_dataset.post_transform = val_transform
|
||||
|
||||
train_loader_kwargs: LoaderKwargs = {
|
||||
"batch_size": min(batch_size, len(train_dataset)),
|
||||
"num_workers": 0,
|
||||
"shuffle": True,
|
||||
}
|
||||
val_loader_kwargs: LoaderKwargs = {
|
||||
"batch_size": min(batch_size, len(val_dataset)),
|
||||
"num_workers": 0,
|
||||
"shuffle": True, # shuffle to prevent homogeneous val batches
|
||||
}
|
||||
|
||||
if dataloader_kwargs is not None:
|
||||
train_loader_kwargs = {**train_loader_kwargs, **dataloader_kwargs}
|
||||
val_loader_kwargs = {**val_loader_kwargs, **dataloader_kwargs}
|
||||
|
||||
train_loader = DataLoader(train_dataset, **train_loader_kwargs)
|
||||
val_loader = DataLoader(val_dataset, **val_loader_kwargs)
|
||||
|
||||
return train_loader, val_loader
|
||||
|
||||
def _summarize(self, writer: SummaryWriter, epoch: int) -> None:
|
||||
"""
|
||||
Flush the training summary to the TB summary writer.
|
||||
"""
|
||||
|
||||
summary_values = defaultdict(list)
|
||||
for name, records in self._summary.items():
|
||||
for value, step in records:
|
||||
writer.add_scalar(name, value, step)
|
||||
summary_values[name].append(value)
|
||||
|
||||
print(f"==== Epoch [{epoch}] summary ====")
|
||||
for name, values in summary_values.items():
|
||||
mean_value = torch.tensor(values).mean().item()
|
||||
print(f"> ({len(values)}) {name} :: {mean_value:.2f}")
|
||||
|
||||
writer.flush()
|
||||
self._summary = defaultdict(list)
|
||||
self._writer.flush()
|
||||
self._summary = defaultdict(lambda: defaultdict(list))
|
||||
|
||||
def _get_optimizer_parameters(
|
||||
self,
|
||||
@@ -459,15 +542,13 @@ class Trainer[I, K: EstimatorKwargs]:
|
||||
if param.grad is not None
|
||||
]
|
||||
|
||||
def _add_summary_item(self, name: str, value: float) -> None:
|
||||
self._summary[name].append((value, self._step))
|
||||
def _log_event(self, group: str, name: str, value: float) -> None:
|
||||
session, epoch = self._session_name, self._epoch
|
||||
|
||||
def save_model(
|
||||
self,
|
||||
epoch: int,
|
||||
chkpt_dir: str | Path,
|
||||
dir_prefix: str,
|
||||
) -> None:
|
||||
self._summary[group, name][epoch].append(value)
|
||||
self._event_log[session][group][name][epoch].append(value)
|
||||
|
||||
def save_model(self) -> None:
|
||||
"""
|
||||
Save a model checkpoint.
|
||||
"""
|
||||
@@ -477,25 +558,26 @@ class Trainer[I, K: EstimatorKwargs]:
|
||||
model_buff.seek(0)
|
||||
|
||||
model_class = self.estimator.__class__.__name__
|
||||
chkpt_name = f"m_{model_class}-e_{epoch}.pth"
|
||||
chkpt_name = f"m_{model_class}-e_{self._epoch}.pth"
|
||||
|
||||
chkpt_dir = Path(chkpt_dir, dir_prefix)
|
||||
chkpt_dir = Path(self.chkpt_dir, self._session_name)
|
||||
chkpt_path = Path(chkpt_dir, chkpt_name)
|
||||
|
||||
chkpt_dir.mkdir(parents=True, exist_ok=True)
|
||||
chkpt_path.write_bytes(model_buff.getvalue())
|
||||
|
||||
def load_model(
|
||||
self,
|
||||
epoch: int,
|
||||
chkpt_dir: str,
|
||||
) -> None:
|
||||
def load_model(self, chkpt_dir: str, epoch: int) -> None:
|
||||
"""
|
||||
Load a model checkpoint from a given epoch.
|
||||
|
||||
Note that this assumes the model was saved via `Trainer.save_model()`,
|
||||
and the estimator provided to this `Trainer` instance matches the
|
||||
architecture of the checkpoint model being loaded.
|
||||
Note that this assumes the model was saved via
|
||||
``Trainer.save_model()``, and the estimator provided to this
|
||||
``Trainer`` instance matches the architecture of the checkpoint model
|
||||
being loaded.
|
||||
|
||||
Parameters:
|
||||
epoch: epoch of saved model
|
||||
chkpt_dir:
|
||||
"""
|
||||
|
||||
model_class = self.estimator.__class__.__name__
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
"""
|
||||
Transform base for dataset records
|
||||
"""
|
||||
|
||||
class Transform[I]:
|
||||
"""
|
||||
Dataset transform base class.
|
||||
@@ -8,4 +12,14 @@ class Transform[I]:
|
||||
"""
|
||||
|
||||
def __call__(self, item: I) -> I:
|
||||
"""
|
||||
Apply transform to item.
|
||||
|
||||
Parameters:
|
||||
item: item object to transform
|
||||
|
||||
Returns:
|
||||
transformed item (same type ``I`` as input)
|
||||
"""
|
||||
|
||||
raise NotImplementedError
|
||||
|
||||
49
trainlib/utils/custom.mplstyle
Normal file
49
trainlib/utils/custom.mplstyle
Normal file
@@ -0,0 +1,49 @@
|
||||
text.usetex : False
|
||||
mathtext.default : regular
|
||||
|
||||
# testing to prevent component overlap/clipping
|
||||
figure.constrained_layout.use : True
|
||||
|
||||
font.family : sans-serif
|
||||
font.sans-serif : DejaVu Sans
|
||||
font.serif : DejaVu Serif
|
||||
font.cursive : DejaVu Sans
|
||||
mathtext.fontset : dejavuserif
|
||||
font.size : 9
|
||||
figure.titlesize : 9
|
||||
legend.fontsize : 9
|
||||
axes.titlesize : 9
|
||||
axes.labelsize : 9
|
||||
xtick.labelsize : 9
|
||||
ytick.labelsize : 9
|
||||
|
||||
# monobiome -d 0.45 -l 22
|
||||
axes.prop_cycle : cycler('color', ['5e8de4', 'c38141', '67a771', 'e15344', '9e9858', '41a6b0', 'a46fd7', 'c86d9a'])
|
||||
|
||||
image.interpolation : nearest
|
||||
image.resample : False
|
||||
image.composite_image : True
|
||||
|
||||
axes.spines.left : True
|
||||
axes.spines.bottom : True
|
||||
axes.spines.top : False
|
||||
axes.spines.right : False
|
||||
|
||||
axes.linewidth : 1
|
||||
xtick.major.width : 1
|
||||
xtick.minor.width : 1
|
||||
ytick.major.width : 1
|
||||
ytick.minor.width : 1
|
||||
|
||||
lines.linewidth : 1
|
||||
lines.markersize : 1
|
||||
|
||||
savefig.dpi : 300
|
||||
savefig.format : svg
|
||||
savefig.bbox : tight
|
||||
savefig.pad_inches : 0.1
|
||||
|
||||
svg.image_inline : True
|
||||
svg.fonttype : none
|
||||
|
||||
legend.frameon : False
|
||||
10
trainlib/utils/map.py
Normal file
10
trainlib/utils/map.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
def nested_defaultdict(
|
||||
depth: int,
|
||||
final: type = dict,
|
||||
) -> defaultdict:
|
||||
if depth == 1:
|
||||
return defaultdict(final)
|
||||
return defaultdict(lambda: nested_defaultdict(depth - 1, final))
|
||||
38
trainlib/utils/plot.py
Normal file
38
trainlib/utils/plot.py
Normal file
@@ -0,0 +1,38 @@
|
||||
from pathlib import Path
|
||||
|
||||
import matplotlib as mpl
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
FILE = Path(__file__).parent.absolute()
|
||||
|
||||
|
||||
class use_style:
|
||||
def __init__(
|
||||
self,
|
||||
style: list[str] | None = None,
|
||||
**kwargs: str,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
|
||||
if style is None:
|
||||
style = [str(Path(FILE, "custom.mplstyle"))]
|
||||
self.style = style + [kwargs]
|
||||
self.previous_style = {}
|
||||
|
||||
def __enter__(self) -> None:
|
||||
self.previous_style = mpl.rcParams.copy()
|
||||
if self.style is not None:
|
||||
plt.style.use(self.style)
|
||||
|
||||
def __exit__(self, *args: str, **kwargs: str) -> None:
|
||||
mpl.rcParams.update(self.previous_style)
|
||||
|
||||
|
||||
def set_style(
|
||||
style: list[str] | None = None,
|
||||
**kwargs: str,
|
||||
) -> None:
|
||||
if style is None:
|
||||
style = [str(Path(FILE, "custom.mplstyle"))]
|
||||
|
||||
plt.style.use(style + [kwargs])
|
||||
28
trainlib/utils/session.py
Normal file
28
trainlib/utils/session.py
Normal file
@@ -0,0 +1,28 @@
|
||||
import random
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
from torch import Tensor
|
||||
from torch.utils import _pytree as pytree
|
||||
|
||||
|
||||
def seed_all_backends(seed: int | Tensor | None = None) -> None:
|
||||
"""Sets all python, numpy and pytorch seeds."""
|
||||
|
||||
if seed is None:
|
||||
seed = int(torch.randint(1000000, size=(1,)))
|
||||
else:
|
||||
seed = int(seed)
|
||||
|
||||
random.seed(seed)
|
||||
np.random.seed(seed)
|
||||
torch.manual_seed(seed)
|
||||
torch.cuda.manual_seed(seed)
|
||||
torch.backends.cudnn.deterministic = True
|
||||
torch.backends.cudnn.benchmark = False
|
||||
|
||||
def ensure_same_device[T](tree: T, device: str) -> T:
|
||||
return pytree.tree_map(
|
||||
lambda x: x.to(device) if isinstance(x, torch.Tensor) else x,
|
||||
tree,
|
||||
)
|
||||
@@ -1,8 +1,14 @@
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
from colorama import Style
|
||||
|
||||
camel2snake_regex: re.Pattern[str] = re.compile(
|
||||
r"(?<!^)(?=[A-Z][a-z])|(?<=[a-z])(?=[A-Z])"
|
||||
)
|
||||
|
||||
def camel_to_snake(text: str) -> str:
|
||||
return camel2snake_regex.sub("_", text).lower()
|
||||
|
||||
def color_text(text: str, *colorama_args: Any) -> str:
|
||||
return f"{''.join(colorama_args)}{text}{Style.RESET_ALL}"
|
||||
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
from typing import Any, TypedDict
|
||||
from collections.abc import Callable, Iterable
|
||||
from collections.abc import Callable, Iterable, Sequence
|
||||
|
||||
import numpy as np
|
||||
from torch import Tensor
|
||||
from torch.utils.data.sampler import Sampler
|
||||
|
||||
from trainlib.dataset import BatchedDataset
|
||||
|
||||
# need b/c matplotlib axes are insanely stupid
|
||||
type AxesArray = np.ndarray[tuple[int, int], np.dtype[np.object_]]
|
||||
|
||||
class LoaderKwargs(TypedDict, total=False):
|
||||
batch_size: int
|
||||
batch_size: int | None
|
||||
shuffle: bool
|
||||
sampler: Sampler | Iterable | None
|
||||
batch_sampler: Sampler[list] | Iterable[list] | None
|
||||
@@ -50,3 +53,17 @@ class OptimizerKwargs(TypedDict, total=False):
|
||||
capturable: bool
|
||||
differentiable: bool
|
||||
fused: bool | None
|
||||
|
||||
|
||||
class SubplotsKwargs(TypedDict, total=False):
|
||||
sharex: bool | str
|
||||
sharey: bool | str
|
||||
squeeze: bool
|
||||
width_ratios: Sequence[float]
|
||||
height_ratios: Sequence[float]
|
||||
subplot_kw: dict[str, Any]
|
||||
gridspec_kw: dict[str, Any]
|
||||
figsize: tuple[float, float]
|
||||
dpi: float
|
||||
layout: str
|
||||
constrained_layout: bool
|
||||
|
||||
496
uv.lock
generated
496
uv.lock
generated
@@ -128,43 +128,59 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
version = "3.4.4"
|
||||
version = "3.4.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7b/60/e3bec1881450851b087e301bedc3daa9377a4d45f1c26aa90b0b235e38aa/charset_normalizer-3.4.6.tar.gz", hash = "sha256:1ae6b62897110aa7c79ea2f5dd38d1abca6db663687c0b1ad9aed6f6bae3d9d6", size = 143363, upload-time = "2026-03-15T18:53:25.478Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/1d/4fdabeef4e231153b6ed7567602f3b68265ec4e5b76d6024cf647d43d981/charset_normalizer-3.4.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:11afb56037cbc4b1555a34dd69151e8e069bee82e613a73bef6e714ce733585f", size = 294823, upload-time = "2026-03-15T18:51:15.755Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/7b/20e809b89c69d37be748d98e84dce6820bf663cf19cf6b942c951a3e8f41/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:423fb7e748a08f854a08a222b983f4df1912b1daedce51a72bd24fe8f26a1843", size = 198527, upload-time = "2026-03-15T18:51:17.177Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/a6/4f8d27527d59c039dce6f7622593cdcd3d70a8504d87d09eb11e9fdc6062/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d73beaac5e90173ac3deb9928a74763a6d230f494e4bfb422c217a0ad8e629bf", size = 218388, upload-time = "2026-03-15T18:51:18.934Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/9b/4770ccb3e491a9bacf1c46cc8b812214fe367c86a96353ccc6daf87b01ec/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d60377dce4511655582e300dc1e5a5f24ba0cb229005a1d5c8d0cb72bb758ab8", size = 214563, upload-time = "2026-03-15T18:51:20.374Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/58/a199d245894b12db0b957d627516c78e055adc3a0d978bc7f65ddaf7c399/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:530e8cebeea0d76bdcf93357aa5e41336f48c3dc709ac52da2bb167c5b8271d9", size = 206587, upload-time = "2026-03-15T18:51:21.807Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/70/3def227f1ec56f5c69dfc8392b8bd63b11a18ca8178d9211d7cc5e5e4f27/charset_normalizer-3.4.6-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:a26611d9987b230566f24a0a125f17fe0de6a6aff9f25c9f564aaa2721a5fb88", size = 194724, upload-time = "2026-03-15T18:51:23.508Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/ab/9318352e220c05efd31c2779a23b50969dc94b985a2efa643ed9077bfca5/charset_normalizer-3.4.6-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:34315ff4fc374b285ad7f4a0bf7dcbfe769e1b104230d40f49f700d4ab6bbd84", size = 202956, upload-time = "2026-03-15T18:51:25.239Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/13/f3550a3ac25b70f87ac98c40d3199a8503676c2f1620efbf8d42095cfc40/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5f8ddd609f9e1af8c7bd6e2aca279c931aefecd148a14402d4e368f3171769fd", size = 201923, upload-time = "2026-03-15T18:51:26.682Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/db/c5c643b912740b45e8eec21de1bbab8e7fc085944d37e1e709d3dcd9d72f/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:80d0a5615143c0b3225e5e3ef22c8d5d51f3f72ce0ea6fb84c943546c7b25b6c", size = 195366, upload-time = "2026-03-15T18:51:28.129Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/67/3b1c62744f9b2448443e0eb160d8b001c849ec3fef591e012eda6484787c/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:92734d4d8d187a354a556626c221cd1a892a4e0802ccb2af432a1d85ec012194", size = 219752, upload-time = "2026-03-15T18:51:29.556Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/98/32ffbaf7f0366ffb0445930b87d103f6b406bc2c271563644bde8a2b1093/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:613f19aa6e082cf96e17e3ffd89383343d0d589abda756b7764cf78361fd41dc", size = 203296, upload-time = "2026-03-15T18:51:30.921Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/12/5d308c1bbe60cabb0c5ef511574a647067e2a1f631bc8634fcafaccd8293/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2b1a63e8224e401cafe7739f77efd3f9e7f5f2026bda4aead8e59afab537784f", size = 215956, upload-time = "2026-03-15T18:51:32.399Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/e9/5f85f6c5e20669dbe56b165c67b0260547dea97dba7e187938833d791687/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6cceb5473417d28edd20c6c984ab6fee6c6267d38d906823ebfe20b03d607dc2", size = 208652, upload-time = "2026-03-15T18:51:34.214Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/11/897052ea6af56df3eef3ca94edafee410ca699ca0c7b87960ad19932c55e/charset_normalizer-3.4.6-cp313-cp313-win32.whl", hash = "sha256:d7de2637729c67d67cf87614b566626057e95c303bc0a55ffe391f5205e7003d", size = 143940, upload-time = "2026-03-15T18:51:36.15Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/5c/724b6b363603e419829f561c854b87ed7c7e31231a7908708ac086cdf3e2/charset_normalizer-3.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:572d7c822caf521f0525ba1bce1a622a0b85cf47ffbdae6c9c19e3b5ac3c4389", size = 154101, upload-time = "2026-03-15T18:51:37.876Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/a5/7abf15b4c0968e47020f9ca0935fb3274deb87cb288cd187cad92e8cdffd/charset_normalizer-3.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a4474d924a47185a06411e0064b803c68be044be2d60e50e8bddcc2649957c1f", size = 143109, upload-time = "2026-03-15T18:51:39.565Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/6f/ffe1e1259f384594063ea1869bfb6be5cdb8bc81020fc36c3636bc8302a1/charset_normalizer-3.4.6-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:9cc6e6d9e571d2f863fa77700701dae73ed5f78881efc8b3f9a4398772ff53e8", size = 294458, upload-time = "2026-03-15T18:51:41.134Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/56/60/09bb6c13a8c1016c2ed5c6a6488e4ffef506461aa5161662bd7636936fb1/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef5960d965e67165d75b7c7ffc60a83ec5abfc5c11b764ec13ea54fbef8b4421", size = 199277, upload-time = "2026-03-15T18:51:42.953Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/50/dcfbb72a5138bbefdc3332e8d81a23494bf67998b4b100703fd15fa52d81/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b3694e3f87f8ac7ce279d4355645b3c878d24d1424581b46282f24b92f5a4ae2", size = 218758, upload-time = "2026-03-15T18:51:44.339Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/b3/d79a9a191bb75f5aa81f3aaaa387ef29ce7cb7a9e5074ba8ea095cc073c2/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5d11595abf8dd942a77883a39d81433739b287b6aa71620f15164f8096221b30", size = 215299, upload-time = "2026-03-15T18:51:45.871Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/7e/bc8911719f7084f72fd545f647601ea3532363927f807d296a8c88a62c0d/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7bda6eebafd42133efdca535b04ccb338ab29467b3f7bf79569883676fc628db", size = 206811, upload-time = "2026-03-15T18:51:47.308Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/40/c430b969d41dda0c465aa36cc7c2c068afb67177bef50905ac371b28ccc7/charset_normalizer-3.4.6-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:bbc8c8650c6e51041ad1be191742b8b421d05bbd3410f43fa2a00c8db87678e8", size = 193706, upload-time = "2026-03-15T18:51:48.849Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/48/15/e35e0590af254f7df984de1323640ef375df5761f615b6225ba8deb9799a/charset_normalizer-3.4.6-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:22c6f0c2fbc31e76c3b8a86fba1a56eda6166e238c29cdd3d14befdb4a4e4815", size = 202706, upload-time = "2026-03-15T18:51:50.257Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/bd/f736f7b9cc5e93a18b794a50346bb16fbfd6b37f99e8f306f7951d27c17c/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7edbed096e4a4798710ed6bc75dcaa2a21b68b6c356553ac4823c3658d53743a", size = 202497, upload-time = "2026-03-15T18:51:52.012Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/ba/2cc9e3e7dfdf7760a6ed8da7446d22536f3d0ce114ac63dee2a5a3599e62/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:7f9019c9cb613f084481bd6a100b12e1547cf2efe362d873c2e31e4035a6fa43", size = 193511, upload-time = "2026-03-15T18:51:53.723Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/cb/5be49b5f776e5613be07298c80e1b02a2d900f7a7de807230595c85a8b2e/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:58c948d0d086229efc484fe2f30c2d382c86720f55cd9bc33591774348ad44e0", size = 220133, upload-time = "2026-03-15T18:51:55.333Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/43/99f1b5dad345accb322c80c7821071554f791a95ee50c1c90041c157ae99/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:419a9d91bd238052642a51938af8ac05da5b3343becde08d5cdeab9046df9ee1", size = 203035, upload-time = "2026-03-15T18:51:56.736Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/9a/62c2cb6a531483b55dddff1a68b3d891a8b498f3ca555fbcf2978e804d9d/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5273b9f0b5835ff0350c0828faea623c68bfa65b792720c453e22b25cc72930f", size = 216321, upload-time = "2026-03-15T18:51:58.17Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/79/94a010ff81e3aec7c293eb82c28f930918e517bc144c9906a060844462eb/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:0e901eb1049fdb80f5bd11ed5ea1e498ec423102f7a9b9e4645d5b8204ff2815", size = 208973, upload-time = "2026-03-15T18:51:59.998Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/57/4ecff6d4ec8585342f0c71bc03efaa99cb7468f7c91a57b105bcd561cea8/charset_normalizer-3.4.6-cp314-cp314-win32.whl", hash = "sha256:b4ff1d35e8c5bd078be89349b6f3a845128e685e751b6ea1169cf2160b344c4d", size = 144610, upload-time = "2026-03-15T18:52:02.213Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/94/8434a02d9d7f168c25767c64671fead8d599744a05d6a6c877144c754246/charset_normalizer-3.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:74119174722c4349af9708993118581686f343adc1c8c9c007d59be90d077f3f", size = 154962, upload-time = "2026-03-15T18:52:03.658Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/4c/48f2cdbfd923026503dfd67ccea45c94fd8fe988d9056b468579c66ed62b/charset_normalizer-3.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:e5bcc1a1ae744e0bb59641171ae53743760130600da8db48cbb6e4918e186e4e", size = 143595, upload-time = "2026-03-15T18:52:05.123Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/93/8878be7569f87b14f1d52032946131bcb6ebbd8af3e20446bc04053dc3f1/charset_normalizer-3.4.6-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ad8faf8df23f0378c6d527d8b0b15ea4a2e23c89376877c598c4870d1b2c7866", size = 314828, upload-time = "2026-03-15T18:52:06.831Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/b6/fae511ca98aac69ecc35cde828b0a3d146325dd03d99655ad38fc2cc3293/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f5ea69428fa1b49573eef0cc44a1d43bebd45ad0c611eb7d7eac760c7ae771bc", size = 208138, upload-time = "2026-03-15T18:52:08.239Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/57/64caf6e1bf07274a1e0b7c160a55ee9e8c9ec32c46846ce59b9c333f7008/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:06a7e86163334edfc5d20fe104db92fcd666e5a5df0977cb5680a506fe26cc8e", size = 224679, upload-time = "2026-03-15T18:52:10.043Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/cb/9ff5a25b9273ef160861b41f6937f86fae18b0792fe0a8e75e06acb08f1d/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e1f6e2f00a6b8edb562826e4632e26d063ac10307e80f7461f7de3ad8ef3f077", size = 223475, upload-time = "2026-03-15T18:52:11.854Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/97/440635fc093b8d7347502a377031f9605a1039c958f3cd18dcacffb37743/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95b52c68d64c1878818687a473a10547b3292e82b6f6fe483808fb1468e2f52f", size = 215230, upload-time = "2026-03-15T18:52:13.325Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/24/afff630feb571a13f07c8539fbb502d2ab494019492aaffc78ef41f1d1d0/charset_normalizer-3.4.6-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:7504e9b7dc05f99a9bbb4525c67a2c155073b44d720470a148b34166a69c054e", size = 199045, upload-time = "2026-03-15T18:52:14.752Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/17/d1399ecdaf7e0498c327433e7eefdd862b41236a7e484355b8e0e5ebd64b/charset_normalizer-3.4.6-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:172985e4ff804a7ad08eebec0a1640ece87ba5041d565fff23c8f99c1f389484", size = 211658, upload-time = "2026-03-15T18:52:16.278Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/38/16baa0affb957b3d880e5ac2144caf3f9d7de7bc4a91842e447fbb5e8b67/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4be9f4830ba8741527693848403e2c457c16e499100963ec711b1c6f2049b7c7", size = 210769, upload-time = "2026-03-15T18:52:17.782Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/34/c531bc6ac4c21da9ddfddb3107be2287188b3ea4b53b70fc58f2a77ac8d8/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:79090741d842f564b1b2827c0b82d846405b744d31e84f18d7a7b41c20e473ff", size = 201328, upload-time = "2026-03-15T18:52:19.553Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/73/a5a1e9ca5f234519c1953608a03fe109c306b97fdfb25f09182babad51a7/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:87725cfb1a4f1f8c2fc9890ae2f42094120f4b44db9360be5d99a4c6b0e03a9e", size = 225302, upload-time = "2026-03-15T18:52:21.043Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/f6/cd782923d112d296294dea4bcc7af5a7ae0f86ab79f8fefbda5526b6cfc0/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:fcce033e4021347d80ed9c66dcf1e7b1546319834b74445f561d2e2221de5659", size = 211127, upload-time = "2026-03-15T18:52:22.491Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/c5/0b6898950627af7d6103a449b22320372c24c6feda91aa24e201a478d161/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:ca0276464d148c72defa8bb4390cce01b4a0e425f3b50d1435aa6d7a18107602", size = 222840, upload-time = "2026-03-15T18:52:24.113Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/25/c4bba773bef442cbdc06111d40daa3de5050a676fa26e85090fc54dd12f0/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:197c1a244a274bb016dd8b79204850144ef77fe81c5b797dc389327adb552407", size = 216890, upload-time = "2026-03-15T18:52:25.541Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/1a/05dacadb0978da72ee287b0143097db12f2e7e8d3ffc4647da07a383b0b7/charset_normalizer-3.4.6-cp314-cp314t-win32.whl", hash = "sha256:2a24157fa36980478dd1770b585c0f30d19e18f4fb0c47c13aa568f871718579", size = 155379, upload-time = "2026-03-15T18:52:27.05Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/7a/d269d834cb3a76291651256f3b9a5945e81d0a49ab9f4a498964e83c0416/charset_normalizer-3.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:cd5e2801c89992ed8c0a3f0293ae83c159a60d9a5d685005383ef4caca77f2c4", size = 169043, upload-time = "2026-03-15T18:52:28.502Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/23/06/28b29fba521a37a8932c6a84192175c34d49f84a6d4773fa63d05f9aff22/charset_normalizer-3.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:47955475ac79cc504ef2704b192364e51d0d473ad452caedd0002605f780101c", size = 148523, upload-time = "2026-03-15T18:52:29.956Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/68/687187c7e26cb24ccbd88e5069f5ef00eba804d36dde11d99aad0838ab45/charset_normalizer-3.4.6-py3-none-any.whl", hash = "sha256:947cf925bc916d90adba35a64c82aace04fa39b46b52d4630ece166655905a69", size = 61455, upload-time = "2026-03-15T18:53:23.833Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -248,18 +264,22 @@ dependencies = [
|
||||
{ name = "cuda-pathfinder" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/05/8b/b4b2d1c7775fa403b64333e720cfcfccef8dcb9cdeb99947061ca5a77628/cuda_bindings-12.9.4-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cf8bfaedc238f3b115d957d1fd6562b7e8435ba57f6d0e2f87d0e7149ccb2da5", size = 11570071, upload-time = "2025-10-21T14:51:47.472Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/56/e465c31dc9111be3441a9ba7df1941fe98f4aa6e71e8788a3fb4534ce24d/cuda_bindings-12.9.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:32bdc5a76906be4c61eb98f546a6786c5773a881f3b166486449b5d141e4a39f", size = 11906628, upload-time = "2025-10-21T14:51:49.905Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/07/6aff13bc1e977e35aaa6b22f52b172e2890c608c6db22438cf7ed2bf43a6/cuda_bindings-12.9.4-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3adf4958dcf68ae7801a59b73fb00a8b37f8d0595060d66ceae111b1002de38d", size = 11566797, upload-time = "2025-10-21T14:51:54.581Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/84/1e6be415e37478070aeeee5884c2022713c1ecc735e6d82d744de0252eee/cuda_bindings-12.9.4-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56e0043c457a99ac473ddc926fe0dc4046694d99caef633e92601ab52cbe17eb", size = 11925991, upload-time = "2025-10-21T14:51:56.535Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/b5/96a6696e20c4ffd2b327f54c7d0fde2259bdb998d045c25d5dedbbe30290/cuda_bindings-12.9.4-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f53a7f453d4b2643d8663d036bafe29b5ba89eb904c133180f295df6dc151e5", size = 11624530, upload-time = "2025-10-21T14:52:01.539Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/af/6dfd8f2ed90b1d4719bc053ff8940e494640fe4212dc3dd72f383e4992da/cuda_bindings-12.9.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8b72ee72a9cc1b531db31eebaaee5c69a8ec3500e32c6933f2d3b15297b53686", size = 11922703, upload-time = "2025-10-21T14:52:03.585Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/73/d2fc40c043bac699c3880bf88d3cebe9d88410cd043795382826c93a89f0/cuda_bindings-12.9.4-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:20f2699d61d724de3eb3f3369d57e2b245f93085cab44fd37c3bea036cea1a6f", size = 11565056, upload-time = "2025-10-21T14:52:08.338Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/19/90ac264acc00f6df8a49378eedec9fd2db3061bf9263bf9f39fd3d8377c3/cuda_bindings-12.9.4-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d80bffc357df9988dca279734bc9674c3934a654cab10cadeed27ce17d8635ee", size = 11924658, upload-time = "2025-10-21T14:52:10.411Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cuda-pathfinder"
|
||||
version = "1.4.0"
|
||||
version = "1.4.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/60/d8f1dbfb7f06b94c662e98c95189e6f39b817da638bc8fcea0d003f89e5d/cuda_pathfinder-1.4.0-py3-none-any.whl", hash = "sha256:437079ca59e7b61ae439ecc501d69ed87b3accc34d58153ef1e54815e2c2e118", size = 38406, upload-time = "2026-02-25T22:13:00.807Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/de/8ca2b613042550dcf9ef50c596c8b1f602afda92cf9032ac28a73f6ee410/cuda_pathfinder-1.4.2-py3-none-any.whl", hash = "sha256:eb354abc20278f8609dc5b666a24648655bef5613c6dfe78a238a6fd95566754", size = 44779, upload-time = "2026-03-10T21:57:30.974Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -317,44 +337,44 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "filelock"
|
||||
version = "3.25.0"
|
||||
version = "3.25.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/77/18/a1fd2231c679dcb9726204645721b12498aeac28e1ad0601038f94b42556/filelock-3.25.0.tar.gz", hash = "sha256:8f00faf3abf9dc730a1ffe9c354ae5c04e079ab7d3a683b7c32da5dd05f26af3", size = 40158, upload-time = "2026-03-01T15:08:45.916Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/94/b8/00651a0f559862f3bb7d6f7477b192afe3f583cc5e26403b44e59a55ab34/filelock-3.25.2.tar.gz", hash = "sha256:b64ece2b38f4ca29dd3e810287aa8c48182bbecd1ae6e9ae126c9b35f1382694", size = 40480, upload-time = "2026-03-11T20:45:38.487Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/0b/de6f54d4a8bedfe8645c41497f3c18d749f0bd3218170c667bf4b81d0cdd/filelock-3.25.0-py3-none-any.whl", hash = "sha256:5ccf8069f7948f494968fc0713c10e5c182a9c9d9eef3a636307a20c2490f047", size = 26427, upload-time = "2026-03-01T15:08:44.593Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl", hash = "sha256:ca8afb0da15f229774c9ad1b455ed96e85a81373065fb10446672f64444ddf70", size = 26759, upload-time = "2026-03-11T20:45:37.437Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fonttools"
|
||||
version = "4.61.1"
|
||||
version = "4.62.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ec/ca/cf17b88a8df95691275a3d77dc0a5ad9907f328ae53acbe6795da1b2f5ed/fonttools-4.61.1.tar.gz", hash = "sha256:6675329885c44657f826ef01d9e4fb33b9158e9d93c537d84ad8399539bc6f69", size = 3565756, upload-time = "2025-12-12T17:31:24.246Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9a/08/7012b00a9a5874311b639c3920270c36ee0c445b69d9989a85e5c92ebcb0/fonttools-4.62.1.tar.gz", hash = "sha256:e54c75fd6041f1122476776880f7c3c3295ffa31962dc6ebe2543c00dca58b5d", size = 3580737, upload-time = "2026-03-13T13:54:25.52Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/cf/00ba28b0990982530addb8dc3e9e6f2fa9cb5c20df2abdda7baa755e8fe1/fonttools-4.61.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c56c488ab471628ff3bfa80964372fc13504ece601e0d97a78ee74126b2045c", size = 2846454, upload-time = "2025-12-12T17:30:24.938Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/ca/468c9a8446a2103ae645d14fee3f610567b7042aba85031c1c65e3ef7471/fonttools-4.61.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dc492779501fa723b04d0ab1f5be046797fee17d27700476edc7ee9ae535a61e", size = 2398191, upload-time = "2025-12-12T17:30:27.343Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/4b/d67eedaed19def5967fade3297fed8161b25ba94699efc124b14fb68cdbc/fonttools-4.61.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:64102ca87e84261419c3747a0d20f396eb024bdbeb04c2bfb37e2891f5fadcb5", size = 4928410, upload-time = "2025-12-12T17:30:29.771Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/8d/6fb3494dfe61a46258cd93d979cf4725ded4eb46c2a4ca35e4490d84daea/fonttools-4.61.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c1b526c8d3f615a7b1867f38a9410849c8f4aef078535742198e942fba0e9bd", size = 4984460, upload-time = "2025-12-12T17:30:32.073Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/f1/a47f1d30b3dc00d75e7af762652d4cbc3dff5c2697a0dbd5203c81afd9c3/fonttools-4.61.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:41ed4b5ec103bd306bb68f81dc166e77409e5209443e5773cb4ed837bcc9b0d3", size = 4925800, upload-time = "2025-12-12T17:30:34.339Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/01/e6ae64a0981076e8a66906fab01539799546181e32a37a0257b77e4aa88b/fonttools-4.61.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b501c862d4901792adaec7c25b1ecc749e2662543f68bb194c42ba18d6eec98d", size = 5067859, upload-time = "2025-12-12T17:30:36.593Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/aa/28e40b8d6809a9b5075350a86779163f074d2b617c15d22343fce81918db/fonttools-4.61.1-cp313-cp313-win32.whl", hash = "sha256:4d7092bb38c53bbc78e9255a59158b150bcdc115a1e3b3ce0b5f267dc35dd63c", size = 2267821, upload-time = "2025-12-12T17:30:38.478Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/59/453c06d1d83dc0951b69ef692d6b9f1846680342927df54e9a1ca91c6f90/fonttools-4.61.1-cp313-cp313-win_amd64.whl", hash = "sha256:21e7c8d76f62ab13c9472ccf74515ca5b9a761d1bde3265152a6dc58700d895b", size = 2318169, upload-time = "2025-12-12T17:30:40.951Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/8f/4e7bf82c0cbb738d3c2206c920ca34ca74ef9dabde779030145d28665104/fonttools-4.61.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fff4f534200a04b4a36e7ae3cb74493afe807b517a09e99cb4faa89a34ed6ecd", size = 2846094, upload-time = "2025-12-12T17:30:43.511Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/09/d44e45d0a4f3a651f23a1e9d42de43bc643cce2971b19e784cc67d823676/fonttools-4.61.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d9203500f7c63545b4ce3799319fe4d9feb1a1b89b28d3cb5abd11b9dd64147e", size = 2396589, upload-time = "2025-12-12T17:30:45.681Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/18/58c64cafcf8eb677a99ef593121f719e6dcbdb7d1c594ae5a10d4997ca8a/fonttools-4.61.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa646ecec9528bef693415c79a86e733c70a4965dd938e9a226b0fc64c9d2e6c", size = 4877892, upload-time = "2025-12-12T17:30:47.709Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/ec/9e6b38c7ba1e09eb51db849d5450f4c05b7e78481f662c3b79dbde6f3d04/fonttools-4.61.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11f35ad7805edba3aac1a3710d104592df59f4b957e30108ae0ba6c10b11dd75", size = 4972884, upload-time = "2025-12-12T17:30:49.656Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/87/b5339da8e0256734ba0dbbf5b6cdebb1dd79b01dc8c270989b7bcd465541/fonttools-4.61.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b931ae8f62db78861b0ff1ac017851764602288575d65b8e8ff1963fed419063", size = 4924405, upload-time = "2025-12-12T17:30:51.735Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/47/e3409f1e1e69c073a3a6fd8cb886eb18c0bae0ee13db2c8d5e7f8495e8b7/fonttools-4.61.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b148b56f5de675ee16d45e769e69f87623a4944f7443850bf9a9376e628a89d2", size = 5035553, upload-time = "2025-12-12T17:30:54.823Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/b6/1f6600161b1073a984294c6c031e1a56ebf95b6164249eecf30012bb2e38/fonttools-4.61.1-cp314-cp314-win32.whl", hash = "sha256:9b666a475a65f4e839d3d10473fad6d47e0a9db14a2f4a224029c5bfde58ad2c", size = 2271915, upload-time = "2025-12-12T17:30:57.913Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/7b/91e7b01e37cc8eb0e1f770d08305b3655e4f002fc160fb82b3390eabacf5/fonttools-4.61.1-cp314-cp314-win_amd64.whl", hash = "sha256:4f5686e1fe5fce75d82d93c47a438a25bf0d1319d2843a926f741140b2b16e0c", size = 2323487, upload-time = "2025-12-12T17:30:59.804Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/5c/908ad78e46c61c3e3ed70c3b58ff82ab48437faf84ec84f109592cabbd9f/fonttools-4.61.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:e76ce097e3c57c4bcb67c5aa24a0ecdbd9f74ea9219997a707a4061fbe2707aa", size = 2929571, upload-time = "2025-12-12T17:31:02.574Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/41/975804132c6dea64cdbfbaa59f3518a21c137a10cccf962805b301ac6ab2/fonttools-4.61.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9cfef3ab326780c04d6646f68d4b4742aae222e8b8ea1d627c74e38afcbc9d91", size = 2435317, upload-time = "2025-12-12T17:31:04.974Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/5a/aef2a0a8daf1ebaae4cfd83f84186d4a72ee08fd6a8451289fcd03ffa8a4/fonttools-4.61.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a75c301f96db737e1c5ed5fd7d77d9c34466de16095a266509e13da09751bd19", size = 4882124, upload-time = "2025-12-12T17:31:07.456Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/33/d6db3485b645b81cea538c9d1c9219d5805f0877fda18777add4671c5240/fonttools-4.61.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:91669ccac46bbc1d09e9273546181919064e8df73488ea087dcac3e2968df9ba", size = 5100391, upload-time = "2025-12-12T17:31:09.732Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/d6/675ba631454043c75fcf76f0ca5463eac8eb0666ea1d7badae5fea001155/fonttools-4.61.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c33ab3ca9d3ccd581d58e989d67554e42d8d4ded94ab3ade3508455fe70e65f7", size = 4978800, upload-time = "2025-12-12T17:31:11.681Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/33/d3ec753d547a8d2bdaedd390d4a814e8d5b45a093d558f025c6b990b554c/fonttools-4.61.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:664c5a68ec406f6b1547946683008576ef8b38275608e1cee6c061828171c118", size = 5006426, upload-time = "2025-12-12T17:31:13.764Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/40/cc11f378b561a67bea850ab50063366a0d1dd3f6d0a30ce0f874b0ad5664/fonttools-4.61.1-cp314-cp314t-win32.whl", hash = "sha256:aed04cabe26f30c1647ef0e8fbb207516fd40fe9472e9439695f5c6998e60ac5", size = 2335377, upload-time = "2025-12-12T17:31:16.49Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/ff/c9a2b66b39f8628531ea58b320d66d951267c98c6a38684daa8f50fb02f8/fonttools-4.61.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2180f14c141d2f0f3da43f3a81bc8aa4684860f6b0e6f9e165a4831f24e6a23b", size = 2400613, upload-time = "2025-12-12T17:31:18.769Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/4e/ce75a57ff3aebf6fc1f4e9d508b8e5810618a33d900ad6c19eb30b290b97/fonttools-4.61.1-py3-none-any.whl", hash = "sha256:17d2bf5d541add43822bcf0c43d7d847b160c9bb01d15d5007d84e2217aaa371", size = 1148996, upload-time = "2025-12-12T17:31:21.03Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/56/6f389de21c49555553d6a5aeed5ac9767631497ac836c4f076273d15bd72/fonttools-4.62.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c22b1014017111c401469e3acc5433e6acf6ebcc6aa9efb538a533c800971c79", size = 2865155, upload-time = "2026-03-13T13:53:16.132Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/c5/0e3966edd5ec668d41dfe418787726752bc07e2f5fd8c8f208615e61fa89/fonttools-4.62.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:68959f5fc58ed4599b44aad161c2837477d7f35f5f79402d97439974faebfebe", size = 2412802, upload-time = "2026-03-13T13:53:18.878Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/94/e6ac4b44026de7786fe46e3bfa0c87e51d5d70a841054065d49cd62bb909/fonttools-4.62.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef46db46c9447103b8f3ff91e8ba009d5fe181b1920a83757a5762551e32bb68", size = 5013926, upload-time = "2026-03-13T13:53:21.379Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/98/8b1e801939839d405f1f122e7d175cebe9aeb4e114f95bfc45e3152af9a7/fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6706d1cb1d5e6251a97ad3c1b9347505c5615c112e66047abbef0f8545fa30d1", size = 4964575, upload-time = "2026-03-13T13:53:23.857Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/76/7d051671e938b1881670528fec69cc4044315edd71a229c7fd712eaa5119/fonttools-4.62.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2e7abd2b1e11736f58c1de27819e1955a53267c21732e78243fa2fa2e5c1e069", size = 4953693, upload-time = "2026-03-13T13:53:26.569Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/ae/b41f8628ec0be3c1b934fc12b84f4576a5c646119db4d3bdd76a217c90b5/fonttools-4.62.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:403d28ce06ebfc547fbcb0cb8b7f7cc2f7a2d3e1a67ba9a34b14632df9e080f9", size = 5094920, upload-time = "2026-03-13T13:53:29.329Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/f6/53a1e9469331a23dcc400970a27a4caa3d9f6edbf5baab0260285238b884/fonttools-4.62.1-cp313-cp313-win32.whl", hash = "sha256:93c316e0f5301b2adbe6a5f658634307c096fd5aae60a5b3412e4f3e1728ab24", size = 2279928, upload-time = "2026-03-13T13:53:32.352Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/60/35186529de1db3c01f5ad625bde07c1f576305eab6d86bbda4c58445f721/fonttools-4.62.1-cp313-cp313-win_amd64.whl", hash = "sha256:7aa21ff53e28a9c2157acbc44e5b401149d3c9178107130e82d74ceb500e5056", size = 2330514, upload-time = "2026-03-13T13:53:34.991Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/f0/2888cdac391807d68d90dcb16ef858ddc1b5309bfc6966195a459dd326e2/fonttools-4.62.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fa1d16210b6b10a826d71bed68dd9ec24a9e218d5a5e2797f37c573e7ec215ca", size = 2864442, upload-time = "2026-03-13T13:53:37.509Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/b2/e521803081f8dc35990816b82da6360fa668a21b44da4b53fc9e77efcd62/fonttools-4.62.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:aa69d10ed420d8121118e628ad47d86e4caa79ba37f968597b958f6cceab7eca", size = 2410901, upload-time = "2026-03-13T13:53:40.55Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/a4/8c3511ff06e53110039358dbbdc1a65d72157a054638387aa2ada300a8b8/fonttools-4.62.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd13b7999d59c5eb1c2b442eb2d0c427cb517a0b7a1f5798fc5c9e003f5ff782", size = 4999608, upload-time = "2026-03-13T13:53:42.798Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/28/63/cd0c3b26afe60995a5295f37c246a93d454023726c3261cfbb3559969bb9/fonttools-4.62.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8d337fdd49a79b0d51c4da87bc38169d21c3abbf0c1aa9367eff5c6656fb6dae", size = 4912726, upload-time = "2026-03-13T13:53:45.405Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/b9/ac677cb07c24c685cf34f64e140617d58789d67a3dd524164b63648c6114/fonttools-4.62.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d241cdc4a67b5431c6d7f115fdf63335222414995e3a1df1a41e1182acd4bcc7", size = 4951422, upload-time = "2026-03-13T13:53:48.326Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/10/11c08419a14b85b7ca9a9faca321accccc8842dd9e0b1c8a72908de05945/fonttools-4.62.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c05557a78f8fa514da0f869556eeda40887a8abc77c76ee3f74cf241778afd5a", size = 5060979, upload-time = "2026-03-13T13:53:51.366Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/3c/12eea4a4cf054e7ab058ed5ceada43b46809fce2bf319017c4d63ae55bb4/fonttools-4.62.1-cp314-cp314-win32.whl", hash = "sha256:49a445d2f544ce4a69338694cad575ba97b9a75fff02720da0882d1a73f12800", size = 2283733, upload-time = "2026-03-13T13:53:53.606Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/67/74b070029043186b5dd13462c958cb7c7f811be0d2e634309d9a1ffb1505/fonttools-4.62.1-cp314-cp314-win_amd64.whl", hash = "sha256:1eecc128c86c552fb963fe846ca4e011b1be053728f798185a1687502f6d398e", size = 2335663, upload-time = "2026-03-13T13:53:56.23Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/c5/4d2ed3ca6e33617fc5624467da353337f06e7f637707478903c785bd8e20/fonttools-4.62.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1596aeaddf7f78e21e68293c011316a25267b3effdaccaf4d59bc9159d681b82", size = 2947288, upload-time = "2026-03-13T13:53:59.397Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/e9/7ab11ddfda48ed0f89b13380e5595ba572619c27077be0b2c447a63ff351/fonttools-4.62.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8f8fca95d3bb3208f59626a4b0ea6e526ee51f5a8ad5d91821c165903e8d9260", size = 2449023, upload-time = "2026-03-13T13:54:01.642Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/10/a800fa090b5e8819942e54e19b55fc7c21fe14a08757c3aa3ca8db358939/fonttools-4.62.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee91628c08e76f77b533d65feb3fbe6d9dad699f95be51cf0d022db94089cdc4", size = 5137599, upload-time = "2026-03-13T13:54:04.495Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/dc/8ccd45033fffd74deb6912fa1ca524643f584b94c87a16036855b498a1ed/fonttools-4.62.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5f37df1cac61d906e7b836abe356bc2f34c99d4477467755c216b72aa3dc748b", size = 4920933, upload-time = "2026-03-13T13:54:07.557Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/eb/e618adefb839598d25ac8136cd577925d6c513dc0d931d93b8af956210f0/fonttools-4.62.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:92bb00a947e666169c99b43753c4305fc95a890a60ef3aeb2a6963e07902cc87", size = 5016232, upload-time = "2026-03-13T13:54:10.611Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/5f/9b5c9bfaa8ec82def8d8168c4f13615990d6ce5996fe52bd49bfb5e05134/fonttools-4.62.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:bdfe592802ef939a0e33106ea4a318eeb17822c7ee168c290273cbd5fabd746c", size = 5042987, upload-time = "2026-03-13T13:54:13.569Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/aa/dfbbe24c6a6afc5c203d90cc0343e24bcbb09e76d67c4d6eef8c2558d7ba/fonttools-4.62.1-cp314-cp314t-win32.whl", hash = "sha256:b820fcb92d4655513d8402d5b219f94481c4443d825b4372c75a2072aa4b357a", size = 2348021, upload-time = "2026-03-13T13:54:16.98Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/6f/ae9c4e4dd417948407b680855c2c7790efb52add6009aaecff1e3bc50e8e/fonttools-4.62.1-cp314-cp314t-win_amd64.whl", hash = "sha256:59b372b4f0e113d3746b88985f1c796e7bf830dd54b28374cd85c2b8acd7583e", size = 2414147, upload-time = "2026-03-13T13:54:19.416Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/ba/56147c165442cc5ba7e82ecf301c9a68353cede498185869e6e02b4c264f/fonttools-4.62.1-py3-none-any.whl", hash = "sha256:7487782e2113861f4ddcc07c3436450659e3caa5e470b27dc2177cade2d8e7fd", size = 1152647, upload-time = "2026-03-13T13:54:22.735Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -466,7 +486,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "ipython"
|
||||
version = "9.10.0"
|
||||
version = "9.11.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
@@ -480,9 +500,9 @@ dependencies = [
|
||||
{ name = "stack-data" },
|
||||
{ name = "traitlets" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a6/60/2111715ea11f39b1535bed6024b7dec7918b71e5e5d30855a5b503056b50/ipython-9.10.0.tar.gz", hash = "sha256:cd9e656be97618a0676d058134cd44e6dc7012c0e5cb36a9ce96a8c904adaf77", size = 4426526, upload-time = "2026-02-02T10:00:33.594Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/86/28/a4698eda5a8928a45d6b693578b135b753e14fa1c2b36ee9441e69a45576/ipython-9.11.0.tar.gz", hash = "sha256:2a94bc4406b22ecc7e4cb95b98450f3ea493a76bec8896cda11b78d7752a6667", size = 4427354, upload-time = "2026-03-05T08:57:30.549Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/aa/898dec789a05731cd5a9f50605b7b44a72bd198fd0d4528e11fc610177cc/ipython-9.10.0-py3-none-any.whl", hash = "sha256:c6ab68cc23bba8c7e18e9b932797014cc61ea7fd6f19de180ab9ba73e65ee58d", size = 622774, upload-time = "2026-02-02T10:00:31.503Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/90/45c72becc57158facc6a6404f663b77bbcea2519ca57f760e2879ae1315d/ipython-9.11.0-py3-none-any.whl", hash = "sha256:6922d5bcf944c6e525a76a0a304451b60a2b6f875e86656d8bc2dfda5d710e19", size = 624222, upload-time = "2026-03-05T08:57:28.94Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -552,61 +572,69 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "kiwisolver"
|
||||
version = "1.4.9"
|
||||
version = "1.5.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5c/3c/85844f1b0feb11ee581ac23fe5fce65cd049a200c1446708cc1b7f922875/kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d", size = 97564, upload-time = "2025-08-10T21:27:49.279Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d0/67/9c61eccb13f0bdca9307614e782fec49ffdde0f7a2314935d489fa93cd9c/kiwisolver-1.5.0.tar.gz", hash = "sha256:d4193f3d9dc3f6f79aaed0e5637f45d98850ebf01f7ca20e69457f3e8946b66a", size = 103482, upload-time = "2026-03-09T13:15:53.382Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/31/c1/c2686cda909742ab66c7388e9a1a8521a59eb89f8bcfbee28fc980d07e24/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5d0432ccf1c7ab14f9949eec60c5d1f924f17c037e9f8b33352fa05799359b8", size = 123681, upload-time = "2025-08-10T21:26:26.725Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/f0/f44f50c9f5b1a1860261092e3bc91ecdc9acda848a8b8c6abfda4a24dd5c/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efb3a45b35622bb6c16dbfab491a8f5a391fe0e9d45ef32f4df85658232ca0e2", size = 66464, upload-time = "2025-08-10T21:26:27.733Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/7a/9d90a151f558e29c3936b8a47ac770235f436f2120aca41a6d5f3d62ae8d/kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a12cf6398e8a0a001a059747a1cbf24705e18fe413bc22de7b3d15c67cffe3f", size = 64961, upload-time = "2025-08-10T21:26:28.729Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/e9/f218a2cb3a9ffbe324ca29a9e399fa2d2866d7f348ec3a88df87fc248fc5/kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b67e6efbf68e077dd71d1a6b37e43e1a99d0bff1a3d51867d45ee8908b931098", size = 1474607, upload-time = "2025-08-10T21:26:29.798Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/28/aac26d4c882f14de59041636292bc838db8961373825df23b8eeb807e198/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5656aa670507437af0207645273ccdfee4f14bacd7f7c67a4306d0dcaeaf6eed", size = 1276546, upload-time = "2025-08-10T21:26:31.401Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/ad/8bfc1c93d4cc565e5069162f610ba2f48ff39b7de4b5b8d93f69f30c4bed/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bfc08add558155345129c7803b3671cf195e6a56e7a12f3dde7c57d9b417f525", size = 1294482, upload-time = "2025-08-10T21:26:32.721Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/f1/6aca55ff798901d8ce403206d00e033191f63d82dd708a186e0ed2067e9c/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:40092754720b174e6ccf9e845d0d8c7d8e12c3d71e7fc35f55f3813e96376f78", size = 1343720, upload-time = "2025-08-10T21:26:34.032Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/91/eed031876c595c81d90d0f6fc681ece250e14bf6998c3d7c419466b523b7/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:497d05f29a1300d14e02e6441cf0f5ee81c1ff5a304b0d9fb77423974684e08b", size = 2224907, upload-time = "2025-08-10T21:26:35.824Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/ec/4d1925f2e49617b9cca9c34bfa11adefad49d00db038e692a559454dfb2e/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdd1a81a1860476eb41ac4bc1e07b3f07259e6d55bbf739b79c8aaedcf512799", size = 2321334, upload-time = "2025-08-10T21:26:37.534Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/cb/450cd4499356f68802750c6ddc18647b8ea01ffa28f50d20598e0befe6e9/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e6b93f13371d341afee3be9f7c5964e3fe61d5fa30f6a30eb49856935dfe4fc3", size = 2488313, upload-time = "2025-08-10T21:26:39.191Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/67/fc76242bd99f885651128a5d4fa6083e5524694b7c88b489b1b55fdc491d/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d75aa530ccfaa593da12834b86a0724f58bff12706659baa9227c2ccaa06264c", size = 2291970, upload-time = "2025-08-10T21:26:40.828Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/bd/f1a5d894000941739f2ae1b65a32892349423ad49c2e6d0771d0bad3fae4/kiwisolver-1.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:dd0a578400839256df88c16abddf9ba14813ec5f21362e1fe65022e00c883d4d", size = 73894, upload-time = "2025-08-10T21:26:42.33Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/95/38/dce480814d25b99a391abbddadc78f7c117c6da34be68ca8b02d5848b424/kiwisolver-1.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:d4188e73af84ca82468f09cadc5ac4db578109e52acb4518d8154698d3a87ca2", size = 64995, upload-time = "2025-08-10T21:26:43.889Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/37/7d218ce5d92dadc5ebdd9070d903e0c7cf7edfe03f179433ac4d13ce659c/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5a0f2724dfd4e3b3ac5a82436a8e6fd16baa7d507117e4279b660fe8ca38a3a1", size = 126510, upload-time = "2025-08-10T21:26:44.915Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/23/b0/e85a2b48233daef4b648fb657ebbb6f8367696a2d9548a00b4ee0eb67803/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b11d6a633e4ed84fc0ddafd4ebfd8ea49b3f25082c04ad12b8315c11d504dc1", size = 67903, upload-time = "2025-08-10T21:26:45.934Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/98/f2425bc0113ad7de24da6bb4dae1343476e95e1d738be7c04d31a5d037fd/kiwisolver-1.4.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61874cdb0a36016354853593cffc38e56fc9ca5aa97d2c05d3dcf6922cd55a11", size = 66402, upload-time = "2025-08-10T21:26:47.101Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/d8/594657886df9f34c4177cc353cc28ca7e6e5eb562d37ccc233bff43bbe2a/kiwisolver-1.4.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:60c439763a969a6af93b4881db0eed8fadf93ee98e18cbc35bc8da868d0c4f0c", size = 1582135, upload-time = "2025-08-10T21:26:48.665Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/c6/38a115b7170f8b306fc929e166340c24958347308ea3012c2b44e7e295db/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92a2f997387a1b79a75e7803aa7ded2cfbe2823852ccf1ba3bcf613b62ae3197", size = 1389409, upload-time = "2025-08-10T21:26:50.335Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/3b/e04883dace81f24a568bcee6eb3001da4ba05114afa622ec9b6fafdc1f5e/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31d512c812daea6d8b3be3b2bfcbeb091dbb09177706569bcfc6240dcf8b41c", size = 1401763, upload-time = "2025-08-10T21:26:51.867Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/80/20ace48e33408947af49d7d15c341eaee69e4e0304aab4b7660e234d6288/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:52a15b0f35dad39862d376df10c5230155243a2c1a436e39eb55623ccbd68185", size = 1453643, upload-time = "2025-08-10T21:26:53.592Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/31/6ce4380a4cd1f515bdda976a1e90e547ccd47b67a1546d63884463c92ca9/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a30fd6fdef1430fd9e1ba7b3398b5ee4e2887783917a687d86ba69985fb08748", size = 2330818, upload-time = "2025-08-10T21:26:55.051Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/e9/3f3fcba3bcc7432c795b82646306e822f3fd74df0ee81f0fa067a1f95668/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cc9617b46837c6468197b5945e196ee9ca43057bb7d9d1ae688101e4e1dddf64", size = 2419963, upload-time = "2025-08-10T21:26:56.421Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/43/7320c50e4133575c66e9f7dadead35ab22d7c012a3b09bb35647792b2a6d/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:0ab74e19f6a2b027ea4f845a78827969af45ce790e6cb3e1ebab71bdf9f215ff", size = 2594639, upload-time = "2025-08-10T21:26:57.882Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/65/d6/17ae4a270d4a987ef8a385b906d2bdfc9fce502d6dc0d3aea865b47f548c/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dba5ee5d3981160c28d5490f0d1b7ed730c22470ff7f6cc26cfcfaacb9896a07", size = 2391741, upload-time = "2025-08-10T21:26:59.237Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/8f/8f6f491d595a9e5912971f3f863d81baddccc8a4d0c3749d6a0dd9ffc9df/kiwisolver-1.4.9-cp313-cp313t-win_arm64.whl", hash = "sha256:0749fd8f4218ad2e851e11cc4dc05c7cbc0cbc4267bdfdb31782e65aace4ee9c", size = 68646, upload-time = "2025-08-10T21:27:00.52Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/32/6cc0fbc9c54d06c2969faa9c1d29f5751a2e51809dd55c69055e62d9b426/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:9928fe1eb816d11ae170885a74d074f57af3a0d65777ca47e9aeb854a1fba386", size = 123806, upload-time = "2025-08-10T21:27:01.537Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/dd/2bfb1d4a4823d92e8cbb420fe024b8d2167f72079b3bb941207c42570bdf/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d0005b053977e7b43388ddec89fa567f43d4f6d5c2c0affe57de5ebf290dc552", size = 66605, upload-time = "2025-08-10T21:27:03.335Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/69/00aafdb4e4509c2ca6064646cba9cd4b37933898f426756adb2cb92ebbed/kiwisolver-1.4.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2635d352d67458b66fd0667c14cb1d4145e9560d503219034a18a87e971ce4f3", size = 64925, upload-time = "2025-08-10T21:27:04.339Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/dc/51acc6791aa14e5cb6d8a2e28cefb0dc2886d8862795449d021334c0df20/kiwisolver-1.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:767c23ad1c58c9e827b649a9ab7809fd5fd9db266a9cf02b0e926ddc2c680d58", size = 1472414, upload-time = "2025-08-10T21:27:05.437Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/bb/93fa64a81db304ac8a246f834d5094fae4b13baf53c839d6bb6e81177129/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72d0eb9fba308b8311685c2268cf7d0a0639a6cd027d8128659f72bdd8a024b4", size = 1281272, upload-time = "2025-08-10T21:27:07.063Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/e6/6df102916960fb8d05069d4bd92d6d9a8202d5a3e2444494e7cd50f65b7a/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f68e4f3eeca8fb22cc3d731f9715a13b652795ef657a13df1ad0c7dc0e9731df", size = 1298578, upload-time = "2025-08-10T21:27:08.452Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/47/e142aaa612f5343736b087864dbaebc53ea8831453fb47e7521fa8658f30/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d84cd4061ae292d8ac367b2c3fa3aad11cb8625a95d135fe93f286f914f3f5a6", size = 1345607, upload-time = "2025-08-10T21:27:10.125Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/89/d641a746194a0f4d1a3670fb900d0dbaa786fb98341056814bc3f058fa52/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a60ea74330b91bd22a29638940d115df9dc00af5035a9a2a6ad9399ffb4ceca5", size = 2230150, upload-time = "2025-08-10T21:27:11.484Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/6b/5ee1207198febdf16ac11f78c5ae40861b809cbe0e6d2a8d5b0b3044b199/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ce6a3a4e106cf35c2d9c4fa17c05ce0b180db622736845d4315519397a77beaf", size = 2325979, upload-time = "2025-08-10T21:27:12.917Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/ff/b269eefd90f4ae14dcc74973d5a0f6d28d3b9bb1afd8c0340513afe6b39a/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:77937e5e2a38a7b48eef0585114fe7930346993a88060d0bf886086d2aa49ef5", size = 2491456, upload-time = "2025-08-10T21:27:14.353Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/d4/10303190bd4d30de547534601e259a4fbf014eed94aae3e5521129215086/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:24c175051354f4a28c5d6a31c93906dc653e2bf234e8a4bbfb964892078898ce", size = 2294621, upload-time = "2025-08-10T21:27:15.808Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/28/e0/a9a90416fce5c0be25742729c2ea52105d62eda6c4be4d803c2a7be1fa50/kiwisolver-1.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:0763515d4df10edf6d06a3c19734e2566368980d21ebec439f33f9eb936c07b7", size = 75417, upload-time = "2025-08-10T21:27:17.436Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/10/6949958215b7a9a264299a7db195564e87900f709db9245e4ebdd3c70779/kiwisolver-1.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:0e4e2bf29574a6a7b7f6cb5fa69293b9f96c928949ac4a53ba3f525dffb87f9c", size = 66582, upload-time = "2025-08-10T21:27:18.436Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/79/60e53067903d3bc5469b369fe0dfc6b3482e2133e85dae9daa9527535991/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d976bbb382b202f71c67f77b0ac11244021cfa3f7dfd9e562eefcea2df711548", size = 126514, upload-time = "2025-08-10T21:27:19.465Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/d1/4843d3e8d46b072c12a38c97c57fab4608d36e13fe47d47ee96b4d61ba6f/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2489e4e5d7ef9a1c300a5e0196e43d9c739f066ef23270607d45aba368b91f2d", size = 67905, upload-time = "2025-08-10T21:27:20.51Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/ae/29ffcbd239aea8b93108de1278271ae764dfc0d803a5693914975f200596/kiwisolver-1.4.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e2ea9f7ab7fbf18fffb1b5434ce7c69a07582f7acc7717720f1d69f3e806f90c", size = 66399, upload-time = "2025-08-10T21:27:21.496Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/ae/d7ba902aa604152c2ceba5d352d7b62106bedbccc8e95c3934d94472bfa3/kiwisolver-1.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b34e51affded8faee0dfdb705416153819d8ea9250bbbf7ea1b249bdeb5f1122", size = 1582197, upload-time = "2025-08-10T21:27:22.604Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/41/27c70d427eddb8bc7e4f16420a20fefc6f480312122a59a959fdfe0445ad/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8aacd3d4b33b772542b2e01beb50187536967b514b00003bdda7589722d2a64", size = 1390125, upload-time = "2025-08-10T21:27:24.036Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/42/b3799a12bafc76d962ad69083f8b43b12bf4fe78b097b12e105d75c9b8f1/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7cf974dd4e35fa315563ac99d6287a1024e4dc2077b8a7d7cd3d2fb65d283134", size = 1402612, upload-time = "2025-08-10T21:27:25.773Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/b5/a210ea073ea1cfaca1bb5c55a62307d8252f531beb364e18aa1e0888b5a0/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85bd218b5ecfbee8c8a82e121802dcb519a86044c9c3b2e4aef02fa05c6da370", size = 1453990, upload-time = "2025-08-10T21:27:27.089Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/ce/a829eb8c033e977d7ea03ed32fb3c1781b4fa0433fbadfff29e39c676f32/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0856e241c2d3df4efef7c04a1e46b1936b6120c9bcf36dd216e3acd84bc4fb21", size = 2331601, upload-time = "2025-08-10T21:27:29.343Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/4b/b5e97eb142eb9cd0072dacfcdcd31b1c66dc7352b0f7c7255d339c0edf00/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9af39d6551f97d31a4deebeac6f45b156f9755ddc59c07b402c148f5dbb6482a", size = 2422041, upload-time = "2025-08-10T21:27:30.754Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/be/8eb4cd53e1b85ba4edc3a9321666f12b83113a178845593307a3e7891f44/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:bb4ae2b57fc1d8cbd1cf7b1d9913803681ffa903e7488012be5b76dedf49297f", size = 2594897, upload-time = "2025-08-10T21:27:32.803Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/dd/841e9a66c4715477ea0abc78da039832fbb09dac5c35c58dc4c41a407b8a/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:aedff62918805fb62d43a4aa2ecd4482c380dc76cd31bd7c8878588a61bd0369", size = 2391835, upload-time = "2025-08-10T21:27:34.23Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/28/4b2e5c47a0da96896fdfdb006340ade064afa1e63675d01ea5ac222b6d52/kiwisolver-1.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:1fa333e8b2ce4d9660f2cda9c0e1b6bafcfb2457a9d259faa82289e73ec24891", size = 79988, upload-time = "2025-08-10T21:27:35.587Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/be/3578e8afd18c88cdf9cb4cffde75a96d2be38c5a903f1ed0ceec061bd09e/kiwisolver-1.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:4a48a2ce79d65d363597ef7b567ce3d14d68783d2b2263d98db3d9477805ba32", size = 70260, upload-time = "2025-08-10T21:27:36.606Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/69/024d6711d5ba575aa65d5538042e99964104e97fa153a9f10bc369182bc2/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:fd40bb9cd0891c4c3cb1ddf83f8bbfa15731a248fdc8162669405451e2724b09", size = 123166, upload-time = "2026-03-09T13:13:48.032Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/48/adbb40df306f587054a348831220812b9b1d787aff714cfbc8556e38fccd/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c0e1403fd7c26d77c1f03e096dc58a5c726503fa0db0456678b8668f76f521e3", size = 66395, upload-time = "2026-03-09T13:13:49.365Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/3a/d0a972b34e1c63e2409413104216cd1caa02c5a37cb668d1687d466c1c45/kiwisolver-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dda366d548e89a90d88a86c692377d18d8bd64b39c1fb2b92cb31370e2896bbd", size = 64065, upload-time = "2026-03-09T13:13:50.562Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/0a/7b98e1e119878a27ba8618ca1e18b14f992ff1eda40f47bccccf4de44121/kiwisolver-1.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:332b4f0145c30b5f5ad9374881133e5aa64320428a57c2c2b61e9d891a51c2f3", size = 1477903, upload-time = "2026-03-09T13:13:52.084Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/d8/55638d89ffd27799d5cc3d8aa28e12f4ce7a64d67b285114dbedc8ea4136/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c50b89ffd3e1a911c69a1dd3de7173c0cd10b130f56222e57898683841e4f96", size = 1278751, upload-time = "2026-03-09T13:13:54.673Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/97/b4c8d0d18421ecceba20ad8701358453b88e32414e6f6950b5a4bad54e65/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4db576bb8c3ef9365f8b40fe0f671644de6736ae2c27a2c62d7d8a1b4329f099", size = 1296793, upload-time = "2026-03-09T13:13:56.287Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/10/f862f94b6389d8957448ec9df59450b81bec4abb318805375c401a1e6892/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0b85aad90cea8ac6797a53b5d5f2e967334fa4d1149f031c4537569972596cb8", size = 1346041, upload-time = "2026-03-09T13:13:58.269Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/6a/f1650af35821eaf09de398ec0bc2aefc8f211f0cda50204c9f1673741ba9/kiwisolver-1.5.0-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:d36ca54cb4c6c4686f7cbb7b817f66f5911c12ddb519450bbe86707155028f87", size = 987292, upload-time = "2026-03-09T13:13:59.871Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/19/d7fb82984b9238115fe629c915007be608ebd23dc8629703d917dbfaffd4/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:38f4a703656f493b0ad185211ccfca7f0386120f022066b018eb5296d8613e23", size = 2227865, upload-time = "2026-03-09T13:14:01.401Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/b9/46b7f386589fd222dac9e9de9c956ce5bcefe2ee73b4e79891381dda8654/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3ac2360e93cb41be81121755c6462cff3beaa9967188c866e5fce5cf13170859", size = 2324369, upload-time = "2026-03-09T13:14:02.972Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/8b/95e237cf3d9c642960153c769ddcbe278f182c8affb20cecc1cc983e7cc5/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c95cab08d1965db3d84a121f1c7ce7479bdd4072c9b3dafd8fecce48a2e6b902", size = 1977989, upload-time = "2026-03-09T13:14:04.503Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/95/980c9df53501892784997820136c01f62bc1865e31b82b9560f980c0e649/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fc20894c3d21194d8041a28b65622d5b86db786da6e3cfe73f0c762951a61167", size = 2491645, upload-time = "2026-03-09T13:14:06.106Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/32/900647fd0840abebe1561792c6b31e6a7c0e278fc3973d30572a965ca14c/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7a32f72973f0f950c1920475d5c5ea3d971b81b6f0ec53b8d0a956cc965f22e0", size = 2295237, upload-time = "2026-03-09T13:14:08.891Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/8a/be60e3bbcf513cc5a50f4a3e88e1dcecebb79c1ad607a7222877becaa101/kiwisolver-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bf3acf1419fa93064a4c2189ac0b58e3be7872bf6ee6177b0d4c63dc4cea276", size = 73573, upload-time = "2026-03-09T13:14:12.327Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/d2/64be2e429eb4fca7f7e1c52a91b12663aeaf25de3895e5cca0f47ef2a8d0/kiwisolver-1.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:fa8eb9ecdb7efb0b226acec134e0d709e87a909fa4971a54c0c4f6e88635484c", size = 64998, upload-time = "2026-03-09T13:14:13.469Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/69/ce68dd0c85755ae2de490bf015b62f2cea5f6b14ff00a463f9d0774449ff/kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:db485b3847d182b908b483b2ed133c66d88d49cacf98fd278fadafe11b4478d1", size = 125700, upload-time = "2026-03-09T13:14:14.636Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/aa/937aac021cf9d4349990d47eb319309a51355ed1dbdc9c077cdc9224cb11/kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:be12f931839a3bdfe28b584db0e640a65a8bcbc24560ae3fdb025a449b3d754e", size = 67537, upload-time = "2026-03-09T13:14:15.808Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/20/3a87fbece2c40ad0f6f0aefa93542559159c5f99831d596050e8afae7a9f/kiwisolver-1.5.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:16b85d37c2cbb3253226d26e64663f755d88a03439a9c47df6246b35defbdfb7", size = 65514, upload-time = "2026-03-09T13:14:18.035Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/7f/f943879cda9007c45e1f7dba216d705c3a18d6b35830e488b6c6a4e7cdf0/kiwisolver-1.5.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4432b835675f0ea7414aab3d37d119f7226d24869b7a829caeab49ebda407b0c", size = 1584848, upload-time = "2026-03-09T13:14:19.745Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/f8/4d4f85cc1870c127c88d950913370dd76138482161cd07eabbc450deff01/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b0feb50971481a2cc44d94e88bdb02cdd497618252ae226b8eb1201b957e368", size = 1391542, upload-time = "2026-03-09T13:14:21.54Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/0b/65dd2916c84d252b244bd405303220f729e7c17c9d7d33dca6feeff9ffc4/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56fa888f10d0f367155e76ce849fa1166fc9730d13bd2d65a2aa13b6f5424489", size = 1404447, upload-time = "2026-03-09T13:14:23.205Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/5c/2606a373247babce9b1d056c03a04b65f3cf5290a8eac5d7bdead0a17e21/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:940dda65d5e764406b9fb92761cbf462e4e63f712ab60ed98f70552e496f3bf1", size = 1455918, upload-time = "2026-03-09T13:14:24.74Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/d1/c6078b5756670658e9192a2ef11e939c92918833d2745f85cd14a6004bdf/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_39_riscv64.whl", hash = "sha256:89fc958c702ee9a745e4700378f5d23fddbc46ff89e8fdbf5395c24d5c1452a3", size = 1072856, upload-time = "2026-03-09T13:14:26.597Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/c8/7def6ddf16eb2b3741d8b172bdaa9af882b03c78e9b0772975408801fa63/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9027d773c4ff81487181a925945743413f6069634d0b122d0b37684ccf4f1e18", size = 2333580, upload-time = "2026-03-09T13:14:28.237Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/87/2ac1fce0eb1e616fcd3c35caa23e665e9b1948bb984f4764790924594128/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:5b233ea3e165e43e35dba1d2b8ecc21cf070b45b65ae17dd2747d2713d942021", size = 2423018, upload-time = "2026-03-09T13:14:30.018Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/13/c6700ccc6cc218716bfcda4935e4b2997039869b4ad8a94f364c5a3b8e63/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ce9bf03dad3b46408c08649c6fbd6ca28a9fce0eb32fdfffa6775a13103b5310", size = 2062804, upload-time = "2026-03-09T13:14:32.888Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/bd/877056304626943ff0f1f44c08f584300c199b887cb3176cd7e34f1515f1/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:fc4d3f1fb9ca0ae9f97b095963bc6326f1dbfd3779d6679a1e016b9baaa153d3", size = 2597482, upload-time = "2026-03-09T13:14:34.971Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/19/c60626c47bf0f8ac5dcf72c6c98e266d714f2fbbfd50cf6dab5ede3aaa50/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f443b4825c50a51ee68585522ab4a1d1257fac65896f282b4c6763337ac9f5d2", size = 2394328, upload-time = "2026-03-09T13:14:36.816Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/84/6a6d5e5bb8273756c27b7d810d47f7ef2f1f9b9fd23c9ee9a3f8c75c9cef/kiwisolver-1.5.0-cp313-cp313t-win_arm64.whl", hash = "sha256:893ff3a711d1b515ba9da14ee090519bad4610ed1962fbe298a434e8c5f8db53", size = 68410, upload-time = "2026-03-09T13:14:38.695Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/d7/060f45052f2a01ad5762c8fdecd6d7a752b43400dc29ff75cd47225a40fd/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8df31fe574b8b3993cc61764f40941111b25c2d9fea13d3ce24a49907cd2d615", size = 123231, upload-time = "2026-03-09T13:14:41.323Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/a7/78da680eadd06ff35edef6ef68a1ad273bad3e2a0936c9a885103230aece/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1d49a49ac4cbfb7c1375301cd1ec90169dfeae55ff84710d782260ce77a75a02", size = 66489, upload-time = "2026-03-09T13:14:42.534Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/b2/97980f3ad4fae37dd7fe31626e2bf75fbf8bdf5d303950ec1fab39a12da8/kiwisolver-1.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0cbe94b69b819209a62cb27bdfa5dc2a8977d8de2f89dfd97ba4f53ed3af754e", size = 64063, upload-time = "2026-03-09T13:14:44.759Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/f9/b06c934a6aa8bc91f566bd2a214fd04c30506c2d9e2b6b171953216a65b6/kiwisolver-1.5.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:80aa065ffd378ff784822a6d7c3212f2d5f5e9c3589614b5c228b311fd3063ac", size = 1475913, upload-time = "2026-03-09T13:14:46.247Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/f0/f768ae564a710135630672981231320bc403cf9152b5596ec5289de0f106/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e7f886f47ab881692f278ae901039a234e4025a68e6dfab514263a0b1c4ae05", size = 1282782, upload-time = "2026-03-09T13:14:48.458Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/9f/1de7aad00697325f05238a5f2eafbd487fb637cc27a558b5367a5f37fb7f/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5060731cc3ed12ca3a8b57acd4aeca5bbc2f49216dd0bec1650a1acd89486bcd", size = 1300815, upload-time = "2026-03-09T13:14:50.721Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/c2/297f25141d2e468e0ce7f7a7b92e0cf8918143a0cbd3422c1ad627e85a06/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a4aa69609f40fce3cbc3f87b2061f042eee32f94b8f11db707b66a26461591a", size = 1347925, upload-time = "2026-03-09T13:14:52.304Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/d3/f4c73a02eb41520c47610207b21afa8cdd18fdbf64ffd94674ae21c4812d/kiwisolver-1.5.0-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:d168fda2dbff7b9b5f38e693182d792a938c31db4dac3a80a4888de603c99554", size = 991322, upload-time = "2026-03-09T13:14:54.637Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/46/d3f2efef7732fcda98d22bf4ad5d3d71d545167a852ca710a494f4c15343/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:413b820229730d358efd838ecbab79902fe97094565fdc80ddb6b0a18c18a581", size = 2232857, upload-time = "2026-03-09T13:14:56.471Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/ec/2d9756bf2b6d26ae4349b8d3662fb3993f16d80c1f971c179ce862b9dbae/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5124d1ea754509b09e53738ec185584cc609aae4a3b510aaf4ed6aa047ef9303", size = 2329376, upload-time = "2026-03-09T13:14:58.072Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/9f/876a0a0f2260f1bde92e002b3019a5fabc35e0939c7d945e0fa66185eb20/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e4415a8db000bf49a6dd1c478bf70062eaacff0f462b92b0ba68791a905861f9", size = 1982549, upload-time = "2026-03-09T13:14:59.668Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/4f/ba3624dfac23a64d54ac4179832860cb537c1b0af06024936e82ca4154a0/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d618fd27420381a4f6044faa71f46d8bfd911bd077c555f7138ed88729bfbe79", size = 2494680, upload-time = "2026-03-09T13:15:01.364Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/b7/97716b190ab98911b20d10bf92eca469121ec483b8ce0edd314f51bc85af/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5092eb5b1172947f57d6ea7d89b2f29650414e4293c47707eb499ec07a0ac796", size = 2297905, upload-time = "2026-03-09T13:15:03.925Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/36/4e551e8aa55c9188bca9abb5096805edbf7431072b76e2298e34fd3a3008/kiwisolver-1.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:d76e2d8c75051d58177e762164d2e9ab92886534e3a12e795f103524f221dd8e", size = 75086, upload-time = "2026-03-09T13:15:07.775Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/15/9b90f7df0e31a003c71649cf66ef61c3c1b862f48c81007fa2383c8bd8d7/kiwisolver-1.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:fa6248cd194edff41d7ea9425ced8ca3a6f838bfb295f6f1d6e6bb694a8518df", size = 66577, upload-time = "2026-03-09T13:15:09.139Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/01/7dc8c5443ff42b38e72731643ed7cf1ed9bf01691ae5cdca98501999ed83/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d1ffeb80b5676463d7a7d56acbe8e37a20ce725570e09549fe738e02ca6b7e1e", size = 125794, upload-time = "2026-03-09T13:15:10.525Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/8a/b4ebe46ebaac6a303417fab10c2e165c557ddaff558f9699d302b256bc53/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc4d8e252f532ab46a1de9349e2d27b91fce46736a9eedaa37beaca66f574ed4", size = 67646, upload-time = "2026-03-09T13:15:12.016Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/35/10a844afc5f19d6f567359bf4789e26661755a2f36200d5d1ed8ad0126e5/kiwisolver-1.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6783e069732715ad0c3ce96dbf21dbc2235ab0593f2baf6338101f70371f4028", size = 65511, upload-time = "2026-03-09T13:15:13.311Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/8a/685b297052dd041dcebce8e8787b58923b6e78acc6115a0dc9189011c44b/kiwisolver-1.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e7c4c09a490dc4d4a7f8cbee56c606a320f9dc28cf92a7157a39d1ce7676a657", size = 1584858, upload-time = "2026-03-09T13:15:15.103Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/80/04865e3d4638ac5bddec28908916df4a3075b8c6cc101786a96803188b96/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a075bd7bd19c70cf67c8badfa36cf7c5d8de3c9ddb8420c51e10d9c50e94920", size = 1392539, upload-time = "2026-03-09T13:15:16.661Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/01/77a19cacc0893fa13fafa46d1bba06fb4dc2360b3292baf4b56d8e067b24/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bdd3e53429ff02aa319ba59dfe4ceeec345bf46cf180ec2cf6fd5b942e7975e9", size = 1405310, upload-time = "2026-03-09T13:15:18.229Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/39/bcaf5d0cca50e604cfa9b4e3ae1d64b50ca1ae5b754122396084599ef903/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cdcb35dc9d807259c981a85531048ede628eabcffb3239adf3d17463518992d", size = 1456244, upload-time = "2026-03-09T13:15:20.444Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/7a/72c187abc6975f6978c3e39b7cf67aeb8b3c0a8f9790aa7fd412855e9e1f/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:70d593af6a6ca332d1df73d519fddb5148edb15cd90d5f0155e3746a6d4fcc65", size = 1073154, upload-time = "2026-03-09T13:15:22.039Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/ca/cf5b25783ebbd59143b4371ed0c8428a278abe68d6d0104b01865b1bbd0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:377815a8616074cabbf3f53354e1d040c35815a134e01d7614b7692e4bf8acfa", size = 2334377, upload-time = "2026-03-09T13:15:23.741Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4a/e5/b1f492adc516796e88751282276745340e2a72dcd0d36cf7173e0daf3210/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0255a027391d52944eae1dbb5d4cc5903f57092f3674e8e544cdd2622826b3f0", size = 2425288, upload-time = "2026-03-09T13:15:25.789Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/e5/9b21fbe91a61b8f409d74a26498706e97a48008bfcd1864373d32a6ba31c/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:012b1eb16e28718fa782b5e61dc6f2da1f0792ca73bd05d54de6cb9561665fc9", size = 2063158, upload-time = "2026-03-09T13:15:27.63Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/02/83f47986138310f95ea95531f851b2a62227c11cbc3e690ae1374fe49f0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0e3aafb33aed7479377e5e9a82e9d4bf87063741fc99fc7ae48b0f16e32bdd6f", size = 2597260, upload-time = "2026-03-09T13:15:29.421Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/18/43a5f24608d8c313dd189cf838c8e68d75b115567c6279de7796197cfb6a/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e7a116ae737f0000343218c4edf5bd45893bfeaff0993c0b215d7124c9f77646", size = 2394403, upload-time = "2026-03-09T13:15:31.517Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/b5/98222136d839b8afabcaa943b09bd05888c2d36355b7e448550211d1fca4/kiwisolver-1.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1dd9b0b119a350976a6d781e7278ec7aca0b201e1a9e2d23d9804afecb6ca681", size = 79687, upload-time = "2026-03-09T13:15:33.204Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/a2/ca7dc962848040befed12732dff6acae7fb3c4f6fc4272b3f6c9a30b8713/kiwisolver-1.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:58f812017cd2985c21fbffb4864d59174d4903dd66fa23815e74bbc7a0e2dd57", size = 70032, upload-time = "2026-03-09T13:15:34.411Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -808,52 +836,52 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "numpy"
|
||||
version = "2.4.2"
|
||||
version = "2.4.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/57/fd/0005efbd0af48e55eb3c7208af93f2862d4b1a56cd78e84309a2d959208d/numpy-2.4.2.tar.gz", hash = "sha256:659a6107e31a83c4e33f763942275fd278b21d095094044eb35569e86a21ddae", size = 20723651, upload-time = "2026-01-31T23:13:10.135Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/10/8b/c265f4823726ab832de836cdd184d0986dcf94480f81e8739692a7ac7af2/numpy-2.4.3.tar.gz", hash = "sha256:483a201202b73495f00dbc83796c6ae63137a9bdade074f7648b3e32613412dd", size = 20727743, upload-time = "2026-03-09T07:58:53.426Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/22/815b9fe25d1d7ae7d492152adbc7226d3eff731dffc38fe970589fcaaa38/numpy-2.4.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25f2059807faea4b077a2b6837391b5d830864b3543627f381821c646f31a63c", size = 16663696, upload-time = "2026-01-31T23:11:17.516Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/f0/817d03a03f93ba9c6c8993de509277d84e69f9453601915e4a69554102a1/numpy-2.4.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bd3a7a9f5847d2fb8c2c6d1c862fa109c31a9abeca1a3c2bd5a64572955b2979", size = 14688322, upload-time = "2026-01-31T23:11:19.883Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/b4/f805ab79293c728b9a99438775ce51885fd4f31b76178767cfc718701a39/numpy-2.4.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8e4549f8a3c6d13d55041925e912bfd834285ef1dd64d6bc7d542583355e2e98", size = 5198157, upload-time = "2026-01-31T23:11:22.375Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/09/826e4289844eccdcd64aac27d13b0fd3f32039915dd5b9ba01baae1f436c/numpy-2.4.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:aea4f66ff44dfddf8c2cffd66ba6538c5ec67d389285292fe428cb2c738c8aef", size = 6546330, upload-time = "2026-01-31T23:11:23.958Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/fb/cbfdbfa3057a10aea5422c558ac57538e6acc87ec1669e666d32ac198da7/numpy-2.4.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3cd545784805de05aafe1dde61752ea49a359ccba9760c1e5d1c88a93bbf2b7", size = 15660968, upload-time = "2026-01-31T23:11:25.713Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/dc/46066ce18d01645541f0186877377b9371b8fa8017fa8262002b4ef22612/numpy-2.4.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0d9b7c93578baafcbc5f0b83eaf17b79d345c6f36917ba0c67f45226911d499", size = 16607311, upload-time = "2026-01-31T23:11:28.117Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/14/d9/4b5adfc39a43fa6bf918c6d544bc60c05236cc2f6339847fc5b35e6cb5b0/numpy-2.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f74f0f7779cc7ae07d1810aab8ac6b1464c3eafb9e283a40da7309d5e6e48fbb", size = 17012850, upload-time = "2026-01-31T23:11:30.888Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/20/adb6e6adde6d0130046e6fdfb7675cc62bc2f6b7b02239a09eb58435753d/numpy-2.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7ac672d699bf36275c035e16b65539931347d68b70667d28984c9fb34e07fa7", size = 18334210, upload-time = "2026-01-31T23:11:33.214Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/0e/0a73b3dff26803a8c02baa76398015ea2a5434d9b8265a7898a6028c1591/numpy-2.4.2-cp313-cp313-win32.whl", hash = "sha256:8e9afaeb0beff068b4d9cd20d322ba0ee1cecfb0b08db145e4ab4dd44a6b5110", size = 5958199, upload-time = "2026-01-31T23:11:35.385Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/bc/6352f343522fcb2c04dbaf94cb30cca6fd32c1a750c06ad6231b4293708c/numpy-2.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:7df2de1e4fba69a51c06c28f5a3de36731eb9639feb8e1cf7e4a7b0daf4cf622", size = 12310848, upload-time = "2026-01-31T23:11:38.001Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/8d/6da186483e308da5da1cc6918ce913dcfe14ffde98e710bfeff2a6158d4e/numpy-2.4.2-cp313-cp313-win_arm64.whl", hash = "sha256:0fece1d1f0a89c16b03442eae5c56dc0be0c7883b5d388e0c03f53019a4bfd71", size = 10221082, upload-time = "2026-01-31T23:11:40.392Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/a1/9510aa43555b44781968935c7548a8926274f815de42ad3997e9e83680dd/numpy-2.4.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5633c0da313330fd20c484c78cdd3f9b175b55e1a766c4a174230c6b70ad8262", size = 14815866, upload-time = "2026-01-31T23:11:42.495Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/30/6bbb5e76631a5ae46e7923dd16ca9d3f1c93cfa8d4ed79a129814a9d8db3/numpy-2.4.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d9f64d786b3b1dd742c946c42d15b07497ed14af1a1f3ce840cce27daa0ce913", size = 5325631, upload-time = "2026-01-31T23:11:44.7Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/00/3a490938800c1923b567b3a15cd17896e68052e2145d8662aaf3e1ffc58f/numpy-2.4.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:b21041e8cb6a1eb5312dd1d2f80a94d91efffb7a06b70597d44f1bd2dfc315ab", size = 6646254, upload-time = "2026-01-31T23:11:46.341Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/e9/fac0890149898a9b609caa5af7455a948b544746e4b8fe7c212c8edd71f8/numpy-2.4.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:00ab83c56211a1d7c07c25e3217ea6695e50a3e2f255053686b081dc0b091a82", size = 15720138, upload-time = "2026-01-31T23:11:48.082Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/5c/08887c54e68e1e28df53709f1893ce92932cc6f01f7c3d4dc952f61ffd4e/numpy-2.4.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fb882da679409066b4603579619341c6d6898fc83a8995199d5249f986e8e8f", size = 16655398, upload-time = "2026-01-31T23:11:50.293Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/89/253db0fa0e66e9129c745e4ef25631dc37d5f1314dad2b53e907b8538e6d/numpy-2.4.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:66cb9422236317f9d44b67b4d18f44efe6e9c7f8794ac0462978513359461554", size = 17079064, upload-time = "2026-01-31T23:11:52.927Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/d5/cbade46ce97c59c6c3da525e8d95b7abe8a42974a1dc5c1d489c10433e88/numpy-2.4.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0f01dcf33e73d80bd8dc0f20a71303abbafa26a19e23f6b68d1aa9990af90257", size = 18379680, upload-time = "2026-01-31T23:11:55.22Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/62/48f99ae172a4b63d981babe683685030e8a3df4f246c893ea5c6ef99f018/numpy-2.4.2-cp313-cp313t-win32.whl", hash = "sha256:52b913ec40ff7ae845687b0b34d8d93b60cb66dcee06996dd5c99f2fc9328657", size = 6082433, upload-time = "2026-01-31T23:11:58.096Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/38/e054a61cfe48ad9f1ed0d188e78b7e26859d0b60ef21cd9de4897cdb5326/numpy-2.4.2-cp313-cp313t-win_amd64.whl", hash = "sha256:5eea80d908b2c1f91486eb95b3fb6fab187e569ec9752ab7d9333d2e66bf2d6b", size = 12451181, upload-time = "2026-01-31T23:11:59.782Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/a4/a05c3a6418575e185dd84d0b9680b6bb2e2dc3e4202f036b7b4e22d6e9dc/numpy-2.4.2-cp313-cp313t-win_arm64.whl", hash = "sha256:fd49860271d52127d61197bb50b64f58454e9f578cb4b2c001a6de8b1f50b0b1", size = 10290756, upload-time = "2026-01-31T23:12:02.438Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/88/b7df6050bf18fdcfb7046286c6535cabbdd2064a3440fca3f069d319c16e/numpy-2.4.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:444be170853f1f9d528428eceb55f12918e4fda5d8805480f36a002f1415e09b", size = 16663092, upload-time = "2026-01-31T23:12:04.521Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/7a/1fee4329abc705a469a4afe6e69b1ef7e915117747886327104a8493a955/numpy-2.4.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d1240d50adff70c2a88217698ca844723068533f3f5c5fa6ee2e3220e3bdb000", size = 14698770, upload-time = "2026-01-31T23:12:06.96Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/0b/f9e49ba6c923678ad5bc38181c08ac5e53b7a5754dbca8e581aa1a56b1ff/numpy-2.4.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:7cdde6de52fb6664b00b056341265441192d1291c130e99183ec0d4b110ff8b1", size = 5208562, upload-time = "2026-01-31T23:12:09.632Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/12/d7de8f6f53f9bb76997e5e4c069eda2051e3fe134e9181671c4391677bb2/numpy-2.4.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:cda077c2e5b780200b6b3e09d0b42205a3d1c68f30c6dceb90401c13bff8fe74", size = 6543710, upload-time = "2026-01-31T23:12:11.969Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/63/c66418c2e0268a31a4cf8a8b512685748200f8e8e8ec6c507ce14e773529/numpy-2.4.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d30291931c915b2ab5717c2974bb95ee891a1cf22ebc16a8006bd59cd210d40a", size = 15677205, upload-time = "2026-01-31T23:12:14.33Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/6c/7f237821c9642fb2a04d2f1e88b4295677144ca93285fd76eff3bcba858d/numpy-2.4.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bba37bc29d4d85761deed3954a1bc62be7cf462b9510b51d367b769a8c8df325", size = 16611738, upload-time = "2026-01-31T23:12:16.525Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/a7/39c4cdda9f019b609b5c473899d87abff092fc908cfe4d1ecb2fcff453b0/numpy-2.4.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b2f0073ed0868db1dcd86e052d37279eef185b9c8db5bf61f30f46adac63c909", size = 17028888, upload-time = "2026-01-31T23:12:19.306Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/b3/e84bb64bdfea967cc10950d71090ec2d84b49bc691df0025dddb7c26e8e3/numpy-2.4.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7f54844851cdb630ceb623dcec4db3240d1ac13d4990532446761baede94996a", size = 18339556, upload-time = "2026-01-31T23:12:21.816Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/88/f5/954a291bc1192a27081706862ac62bb5920fbecfbaa302f64682aa90beed/numpy-2.4.2-cp314-cp314-win32.whl", hash = "sha256:12e26134a0331d8dbd9351620f037ec470b7c75929cb8a1537f6bfe411152a1a", size = 6006899, upload-time = "2026-01-31T23:12:24.14Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/cb/eff72a91b2efdd1bc98b3b8759f6a1654aa87612fc86e3d87d6fe4f948c4/numpy-2.4.2-cp314-cp314-win_amd64.whl", hash = "sha256:068cdb2d0d644cdb45670810894f6a0600797a69c05f1ac478e8d31670b8ee75", size = 12443072, upload-time = "2026-01-31T23:12:26.33Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/75/62726948db36a56428fce4ba80a115716dc4fad6a3a4352487f8bb950966/numpy-2.4.2-cp314-cp314-win_arm64.whl", hash = "sha256:6ed0be1ee58eef41231a5c943d7d1375f093142702d5723ca2eb07db9b934b05", size = 10494886, upload-time = "2026-01-31T23:12:28.488Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/2f/ee93744f1e0661dc267e4b21940870cabfae187c092e1433b77b09b50ac4/numpy-2.4.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:98f16a80e917003a12c0580f97b5f875853ebc33e2eaa4bccfc8201ac6869308", size = 14818567, upload-time = "2026-01-31T23:12:30.709Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/24/6535212add7d76ff938d8bdc654f53f88d35cddedf807a599e180dcb8e66/numpy-2.4.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:20abd069b9cda45874498b245c8015b18ace6de8546bf50dfa8cea1696ed06ef", size = 5328372, upload-time = "2026-01-31T23:12:32.962Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/9d/c48f0a035725f925634bf6b8994253b43f2047f6778a54147d7e213bc5a7/numpy-2.4.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:e98c97502435b53741540a5717a6749ac2ada901056c7db951d33e11c885cc7d", size = 6649306, upload-time = "2026-01-31T23:12:34.797Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/81/05/7c73a9574cd4a53a25907bad38b59ac83919c0ddc8234ec157f344d57d9a/numpy-2.4.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:da6cad4e82cb893db4b69105c604d805e0c3ce11501a55b5e9f9083b47d2ffe8", size = 15722394, upload-time = "2026-01-31T23:12:36.565Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/fa/4de10089f21fc7d18442c4a767ab156b25c2a6eaf187c0db6d9ecdaeb43f/numpy-2.4.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e4424677ce4b47fe73c8b5556d876571f7c6945d264201180db2dc34f676ab5", size = 16653343, upload-time = "2026-01-31T23:12:39.188Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/f9/d33e4ffc857f3763a57aa85650f2e82486832d7492280ac21ba9efda80da/numpy-2.4.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2b8f157c8a6f20eb657e240f8985cc135598b2b46985c5bccbde7616dc9c6b1e", size = 17078045, upload-time = "2026-01-31T23:12:42.041Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/b8/54bdb43b6225badbea6389fa038c4ef868c44f5890f95dd530a218706da3/numpy-2.4.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5daf6f3914a733336dab21a05cdec343144600e964d2fcdabaac0c0269874b2a", size = 18380024, upload-time = "2026-01-31T23:12:44.331Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/55/6e1a61ded7af8df04016d81b5b02daa59f2ea9252ee0397cb9f631efe9e5/numpy-2.4.2-cp314-cp314t-win32.whl", hash = "sha256:8c50dd1fc8826f5b26a5ee4d77ca55d88a895f4e4819c7ecc2a9f5905047a443", size = 6153937, upload-time = "2026-01-31T23:12:47.229Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/aa/fa6118d1ed6d776b0983f3ceac9b1a5558e80df9365b1c3aa6d42bf9eee4/numpy-2.4.2-cp314-cp314t-win_amd64.whl", hash = "sha256:fcf92bee92742edd401ba41135185866f7026c502617f422eb432cfeca4fe236", size = 12631844, upload-time = "2026-01-31T23:12:48.997Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/0a/2ec5deea6dcd158f254a7b372fb09cfba5719419c8d66343bab35237b3fb/numpy-2.4.2-cp314-cp314t-win_arm64.whl", hash = "sha256:1f92f53998a17265194018d1cc321b2e96e900ca52d54c7c77837b71b9465181", size = 10565379, upload-time = "2026-01-31T23:12:51.345Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/d0/1fe47a98ce0df229238b77611340aff92d52691bcbc10583303181abf7fc/numpy-2.4.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b346845443716c8e542d54112966383b448f4a3ba5c66409771b8c0889485dd3", size = 16665297, upload-time = "2026-03-09T07:56:52.296Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/d9/4e7c3f0e68dfa91f21c6fb6cf839bc829ec920688b1ce7ec722b1a6202fb/numpy-2.4.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2629289168f4897a3c4e23dc98d6f1731f0fc0fe52fb9db19f974041e4cc12b9", size = 14691853, upload-time = "2026-03-09T07:56:54.992Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/66/bd096b13a87549683812b53ab211e6d413497f84e794fb3c39191948da97/numpy-2.4.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:bb2e3cf95854233799013779216c57e153c1ee67a0bf92138acca0e429aefaee", size = 5198435, upload-time = "2026-03-09T07:56:57.184Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/2f/687722910b5a5601de2135c891108f51dfc873d8e43c8ed9f4ebb440b4a2/numpy-2.4.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:7f3408ff897f8ab07a07fbe2823d7aee6ff644c097cc1f90382511fe982f647f", size = 6546347, upload-time = "2026-03-09T07:56:59.531Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/ec/7971c4e98d86c564750393fab8d7d83d0a9432a9d78bb8a163a6dc59967a/numpy-2.4.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:decb0eb8a53c3b009b0962378065589685d66b23467ef5dac16cbe818afde27f", size = 15664626, upload-time = "2026-03-09T07:57:01.385Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/eb/7daecbea84ec935b7fc732e18f532073064a3816f0932a40a17f3349185f/numpy-2.4.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5f51900414fc9204a0e0da158ba2ac52b75656e7dce7e77fb9f84bfa343b4cc", size = 16608916, upload-time = "2026-03-09T07:57:04.008Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/58/2a2b4a817ffd7472dca4421d9f0776898b364154e30c95f42195041dc03b/numpy-2.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6bd06731541f89cdc01b261ba2c9e037f1543df7472517836b78dfb15bd6e476", size = 17015824, upload-time = "2026-03-09T07:57:06.347Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4a/ca/627a828d44e78a418c55f82dd4caea8ea4a8ef24e5144d9e71016e52fb40/numpy-2.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:22654fe6be0e5206f553a9250762c653d3698e46686eee53b399ab90da59bd92", size = 18334581, upload-time = "2026-03-09T07:57:09.114Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/c0/76f93962fc79955fcba30a429b62304332345f22d4daec1cb33653425643/numpy-2.4.3-cp313-cp313-win32.whl", hash = "sha256:d71e379452a2f670ccb689ec801b1218cd3983e253105d6e83780967e899d687", size = 5958618, upload-time = "2026-03-09T07:57:11.432Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/3c/88af0040119209b9b5cb59485fa48b76f372c73068dbf9254784b975ac53/numpy-2.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:0a60e17a14d640f49146cb38e3f105f571318db7826d9b6fef7e4dce758faecd", size = 12312824, upload-time = "2026-03-09T07:57:13.586Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/ce/3d07743aced3d173f877c3ef6a454c2174ba42b584ab0b7e6d99374f51ed/numpy-2.4.3-cp313-cp313-win_arm64.whl", hash = "sha256:c9619741e9da2059cd9c3f206110b97583c7152c1dc9f8aafd4beb450ac1c89d", size = 10221218, upload-time = "2026-03-09T07:57:16.183Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/09/d96b02a91d09e9d97862f4fc8bfebf5400f567d8eb1fe4b0cc4795679c15/numpy-2.4.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7aa4e54f6469300ebca1d9eb80acd5253cdfa36f2c03d79a35883687da430875", size = 14819570, upload-time = "2026-03-09T07:57:18.564Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/ca/0b1aba3905fdfa3373d523b2b15b19029f4f3031c87f4066bd9d20ef6c6b/numpy-2.4.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d1b90d840b25874cf5cd20c219af10bac3667db3876d9a495609273ebe679070", size = 5326113, upload-time = "2026-03-09T07:57:21.052Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/63/406e0fd32fcaeb94180fd6a4c41e55736d676c54346b7efbce548b94a914/numpy-2.4.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a749547700de0a20a6718293396ec237bb38218049cfce788e08fcb716e8cf73", size = 6646370, upload-time = "2026-03-09T07:57:22.804Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/d0/10f7dc157d4b37af92720a196be6f54f889e90dcd30dce9dc657ed92c257/numpy-2.4.3-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94f3c4a151a2e529adf49c1d54f0f57ff8f9b233ee4d44af623a81553ab86368", size = 15723499, upload-time = "2026-03-09T07:57:24.693Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/f1/d1c2bf1161396629701bc284d958dc1efa3a5a542aab83cf11ee6eb4cba5/numpy-2.4.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22c31dc07025123aedf7f2db9e91783df13f1776dc52c6b22c620870dc0fab22", size = 16657164, upload-time = "2026-03-09T07:57:27.676Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/be/cca19230b740af199ac47331a21c71e7a3d0ba59661350483c1600d28c37/numpy-2.4.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:148d59127ac95979d6f07e4d460f934ebdd6eed641db9c0db6c73026f2b2101a", size = 17081544, upload-time = "2026-03-09T07:57:30.664Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/c5/9602b0cbb703a0936fb40f8a95407e8171935b15846de2f0776e08af04c7/numpy-2.4.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a97cbf7e905c435865c2d939af3d93f99d18eaaa3cabe4256f4304fb51604349", size = 18380290, upload-time = "2026-03-09T07:57:33.763Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/81/9f24708953cd30be9ee36ec4778f4b112b45165812f2ada4cc5ea1c1f254/numpy-2.4.3-cp313-cp313t-win32.whl", hash = "sha256:be3b8487d725a77acccc9924f65fd8bce9af7fac8c9820df1049424a2115af6c", size = 6082814, upload-time = "2026-03-09T07:57:36.491Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/9e/52f6eaa13e1a799f0ab79066c17f7016a4a8ae0c1aefa58c82b4dab690b4/numpy-2.4.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1ec84fd7c8e652b0f4aaaf2e6e9cc8eaa9b1b80a537e06b2e3a2fb176eedcb26", size = 12452673, upload-time = "2026-03-09T07:57:38.281Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/04/b8cece6ead0b30c9fbd99bb835ad7ea0112ac5f39f069788c5558e3b1ab2/numpy-2.4.3-cp313-cp313t-win_arm64.whl", hash = "sha256:120df8c0a81ebbf5b9020c91439fccd85f5e018a927a39f624845be194a2be02", size = 10290907, upload-time = "2026-03-09T07:57:40.747Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/ae/3936f79adebf8caf81bd7a599b90a561334a658be4dcc7b6329ebf4ee8de/numpy-2.4.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:5884ce5c7acfae1e4e1b6fde43797d10aa506074d25b531b4f54bde33c0c31d4", size = 16664563, upload-time = "2026-03-09T07:57:43.817Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/62/760f2b55866b496bb1fa7da2a6db076bef908110e568b02fcfc1422e2a3a/numpy-2.4.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:297837823f5bc572c5f9379b0c9f3a3365f08492cbdc33bcc3af174372ebb168", size = 14702161, upload-time = "2026-03-09T07:57:46.169Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/af/a7a39464e2c0a21526fb4fb76e346fb172ebc92f6d1c7a07c2c139cc17b1/numpy-2.4.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:a111698b4a3f8dcbe54c64a7708f049355abd603e619013c346553c1fd4ca90b", size = 5208738, upload-time = "2026-03-09T07:57:48.506Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/8c/2a0cf86a59558fa078d83805589c2de490f29ed4fb336c14313a161d358a/numpy-2.4.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:4bd4741a6a676770e0e97fe9ab2e51de01183df3dcbcec591d26d331a40de950", size = 6543618, upload-time = "2026-03-09T07:57:50.591Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/b8/612ce010c0728b1c363fa4ea3aa4c22fe1c5da1de008486f8c2f5cb92fae/numpy-2.4.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:54f29b877279d51e210e0c80709ee14ccbbad647810e8f3d375561c45ef613dd", size = 15680676, upload-time = "2026-03-09T07:57:52.34Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/7e/4f120ecc54ba26ddf3dc348eeb9eb063f421de65c05fc961941798feea18/numpy-2.4.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:679f2a834bae9020f81534671c56fd0cc76dd7e5182f57131478e23d0dc59e24", size = 16613492, upload-time = "2026-03-09T07:57:54.91Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/86/1b6020db73be330c4b45d5c6ee4295d59cfeef0e3ea323959d053e5a6909/numpy-2.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d84f0f881cb2225c2dfd7f78a10a5645d487a496c6668d6cc39f0f114164f3d0", size = 17031789, upload-time = "2026-03-09T07:57:57.641Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/3a/3b90463bf41ebc21d1b7e06079f03070334374208c0f9a1f05e4ae8455e7/numpy-2.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d213c7e6e8d211888cc359bab7199670a00f5b82c0978b9d1c75baf1eddbeac0", size = 18339941, upload-time = "2026-03-09T07:58:00.577Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/74/6d736c4cd962259fd8bae9be27363eb4883a2f9069763747347544c2a487/numpy-2.4.3-cp314-cp314-win32.whl", hash = "sha256:52077feedeff7c76ed7c9f1a0428558e50825347b7545bbb8523da2cd55c547a", size = 6007503, upload-time = "2026-03-09T07:58:03.331Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/48/39/c56ef87af669364356bb011922ef0734fc49dad51964568634c72a009488/numpy-2.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:0448e7f9caefb34b4b7dd2b77f21e8906e5d6f0365ad525f9f4f530b13df2afc", size = 12444915, upload-time = "2026-03-09T07:58:06.353Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/1f/ab8528e38d295fd349310807496fabb7cf9fe2e1f70b97bc20a483ea9d4a/numpy-2.4.3-cp314-cp314-win_arm64.whl", hash = "sha256:b44fd60341c4d9783039598efadd03617fa28d041fc37d22b62d08f2027fa0e7", size = 10494875, upload-time = "2026-03-09T07:58:08.734Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/ef/b7c35e4d5ef141b836658ab21a66d1a573e15b335b1d111d31f26c8ef80f/numpy-2.4.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0a195f4216be9305a73c0e91c9b026a35f2161237cf1c6de9b681637772ea657", size = 14822225, upload-time = "2026-03-09T07:58:11.034Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/8d/7730fa9278cf6648639946cc816e7cc89f0d891602584697923375f801ed/numpy-2.4.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:cd32fbacb9fd1bf041bf8e89e4576b6f00b895f06d00914820ae06a616bdfef7", size = 5328769, upload-time = "2026-03-09T07:58:13.67Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/01/d2a137317c958b074d338807c1b6a383406cdf8b8e53b075d804cc3d211d/numpy-2.4.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:2e03c05abaee1f672e9d67bc858f300b5ccba1c21397211e8d77d98350972093", size = 6649461, upload-time = "2026-03-09T07:58:15.912Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/34/812ce12bc0f00272a4b0ec0d713cd237cb390666eb6206323d1cc9cedbb2/numpy-2.4.3-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d1ce23cce91fcea443320a9d0ece9b9305d4368875bab09538f7a5b4131938a", size = 15725809, upload-time = "2026-03-09T07:58:17.787Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/c0/2aed473a4823e905e765fee3dc2cbf504bd3e68ccb1150fbdabd5c39f527/numpy-2.4.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c59020932feb24ed49ffd03704fbab89f22aa9c0d4b180ff45542fe8918f5611", size = 16655242, upload-time = "2026-03-09T07:58:20.476Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/c8/7e052b2fc87aa0e86de23f20e2c42bd261c624748aa8efd2c78f7bb8d8c6/numpy-2.4.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9684823a78a6cd6ad7511fc5e25b07947d1d5b5e2812c93fe99d7d4195130720", size = 17080660, upload-time = "2026-03-09T07:58:23.067Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/3d/0876746044db2adcb11549f214d104f2e1be00f07a67edbb4e2812094847/numpy-2.4.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0200b25c687033316fb39f0ff4e3e690e8957a2c3c8d22499891ec58c37a3eb5", size = 18380384, upload-time = "2026-03-09T07:58:25.839Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/12/8160bea39da3335737b10308df4f484235fd297f556745f13092aa039d3b/numpy-2.4.3-cp314-cp314t-win32.whl", hash = "sha256:5e10da9e93247e554bb1d22f8edc51847ddd7dde52d85ce31024c1b4312bfba0", size = 6154547, upload-time = "2026-03-09T07:58:28.289Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/f3/76534f61f80d74cc9cdf2e570d3d4eeb92c2280a27c39b0aaf471eda7b48/numpy-2.4.3-cp314-cp314t-win_amd64.whl", hash = "sha256:45f003dbdffb997a03da2d1d0cb41fbd24a87507fb41605c0420a3db5bd4667b", size = 12633645, upload-time = "2026-03-09T07:58:30.384Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/b6/7c0d4334c15983cec7f92a69e8ce9b1e6f31857e5ee3a413ac424e6bd63d/numpy-2.4.3-cp314-cp314t-win_arm64.whl", hash = "sha256:4d382735cecd7bcf090172489a525cd7d4087bc331f7df9f60ddc9a296cf208e", size = 10565454, upload-time = "2026-03-09T07:58:33.031Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -861,6 +889,7 @@ name = "nvidia-cublas-cu12"
|
||||
version = "12.8.4.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/29/99/db44d685f0e257ff0e213ade1964fc459b4a690a73293220e98feb3307cf/nvidia_cublas_cu12-12.8.4.1-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:b86f6dd8935884615a0683b663891d43781b819ac4f2ba2b0c9604676af346d0", size = 590537124, upload-time = "2025-03-07T01:43:53.556Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/61/e24b560ab2e2eaeb3c839129175fb330dfcfc29e5203196e5541a4c44682/nvidia_cublas_cu12-12.8.4.1-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:8ac4e771d5a348c551b2a426eda6193c19aa630236b418086020df5ba9667142", size = 594346921, upload-time = "2025-03-07T01:44:31.254Z" },
|
||||
]
|
||||
|
||||
@@ -869,6 +898,7 @@ name = "nvidia-cuda-cupti-cu12"
|
||||
version = "12.8.90"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/1f/b3bd73445e5cb342727fd24fe1f7b748f690b460acadc27ea22f904502c8/nvidia_cuda_cupti_cu12-12.8.90-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4412396548808ddfed3f17a467b104ba7751e6b58678a4b840675c56d21cf7ed", size = 9533318, upload-time = "2025-03-07T01:40:10.421Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/02/2adcaa145158bf1a8295d83591d22e4103dbfd821bcaf6f3f53151ca4ffa/nvidia_cuda_cupti_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ea0cb07ebda26bb9b29ba82cda34849e73c166c18162d3913575b0c9db9a6182", size = 10248621, upload-time = "2025-03-07T01:40:21.213Z" },
|
||||
]
|
||||
|
||||
@@ -878,6 +908,7 @@ version = "12.8.93"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/05/6b/32f747947df2da6994e999492ab306a903659555dddc0fbdeb9d71f75e52/nvidia_cuda_nvrtc_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:a7756528852ef889772a84c6cd89d41dfa74667e24cca16bb31f8f061e3e9994", size = 88040029, upload-time = "2025-03-07T01:42:13.562Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/d1/e50d0acaab360482034b84b6e27ee83c6738f7d32182b987f9c7a4e32962/nvidia_cuda_nvrtc_cu12-12.8.93-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fc1fec1e1637854b4c0a65fb9a8346b51dd9ee69e61ebaccc82058441f15bce8", size = 43106076, upload-time = "2025-03-07T01:41:59.817Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -885,6 +916,7 @@ name = "nvidia-cuda-runtime-cu12"
|
||||
version = "12.8.90"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/75/f865a3b236e4647605ea34cc450900854ba123834a5f1598e160b9530c3a/nvidia_cuda_runtime_cu12-12.8.90-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:52bf7bbee900262ffefe5e9d5a2a69a30d97e2bc5bb6cc866688caa976966e3d", size = 965265, upload-time = "2025-03-07T01:39:43.533Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/9b/a997b638fcd068ad6e4d53b8551a7d30fe8b404d6f1804abf1df69838932/nvidia_cuda_runtime_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:adade8dcbd0edf427b7204d480d6066d33902cab2a4707dcfc48a2d0fd44ab90", size = 954765, upload-time = "2025-03-07T01:40:01.615Z" },
|
||||
]
|
||||
|
||||
@@ -896,6 +928,7 @@ dependencies = [
|
||||
{ name = "nvidia-cublas-cu12" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/41/e79269ce215c857c935fd86bcfe91a451a584dfc27f1e068f568b9ad1ab7/nvidia_cudnn_cu12-9.10.2.21-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:c9132cc3f8958447b4910a1720036d9eff5928cc3179b0a51fb6d167c6cc87d8", size = 705026878, upload-time = "2025-06-06T21:52:51.348Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/51/e123d997aa098c61d029f76663dedbfb9bc8dcf8c60cbd6adbe42f76d049/nvidia_cudnn_cu12-9.10.2.21-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:949452be657fa16687d0930933f032835951ef0892b37d2d53824d1a84dc97a8", size = 706758467, upload-time = "2025-06-06T21:54:08.597Z" },
|
||||
]
|
||||
|
||||
@@ -907,6 +940,7 @@ dependencies = [
|
||||
{ name = "nvidia-nvjitlink-cu12" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/60/bc/7771846d3a0272026c416fbb7e5f4c1f146d6d80704534d0b187dd6f4800/nvidia_cufft_cu12-11.3.3.83-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:848ef7224d6305cdb2a4df928759dca7b1201874787083b6e7550dd6765ce69a", size = 193109211, upload-time = "2025-03-07T01:44:56.873Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/13/ee4e00f30e676b66ae65b4f08cb5bcbb8392c03f54f2d5413ea99a5d1c80/nvidia_cufft_cu12-11.3.3.83-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d2dd21ec0b88cf61b62e6b43564355e5222e4a3fb394cac0db101f2dd0d4f74", size = 193118695, upload-time = "2025-03-07T01:45:27.821Z" },
|
||||
]
|
||||
|
||||
@@ -916,6 +950,7 @@ version = "1.13.1.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/fe/1bcba1dfbfb8d01be8d93f07bfc502c93fa23afa6fd5ab3fc7c1df71038a/nvidia_cufile_cu12-1.13.1.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1d069003be650e131b21c932ec3d8969c1715379251f8d23a1860554b1cb24fc", size = 1197834, upload-time = "2025-03-07T01:45:50.723Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/f5/5607710447a6fe9fd9b3283956fceeee8a06cda1d2f56ce31371f595db2a/nvidia_cufile_cu12-1.13.1.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:4beb6d4cce47c1a0f1013d72e02b0994730359e17801d395bdcbf20cfb3bb00a", size = 1120705, upload-time = "2025-03-07T01:45:41.434Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -923,6 +958,7 @@ name = "nvidia-curand-cu12"
|
||||
version = "10.3.9.90"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/45/5e/92aa15eca622a388b80fbf8375d4760738df6285b1e92c43d37390a33a9a/nvidia_curand_cu12-10.3.9.90-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:dfab99248034673b779bc6decafdc3404a8a6f502462201f2f31f11354204acd", size = 63625754, upload-time = "2025-03-07T01:46:10.735Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/aa/6584b56dc84ebe9cf93226a5cde4d99080c8e90ab40f0c27bda7a0f29aa1/nvidia_curand_cu12-10.3.9.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:b32331d4f4df5d6eefa0554c565b626c7216f87a06a4f56fab27c3b68a830ec9", size = 63619976, upload-time = "2025-03-07T01:46:23.323Z" },
|
||||
]
|
||||
|
||||
@@ -936,6 +972,7 @@ dependencies = [
|
||||
{ name = "nvidia-nvjitlink-cu12" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/32/f7cd6ce8a7690544d084ea21c26e910a97e077c9b7f07bf5de623ee19981/nvidia_cusolver_cu12-11.7.3.90-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:db9ed69dbef9715071232caa9b69c52ac7de3a95773c2db65bdba85916e4e5c0", size = 267229841, upload-time = "2025-03-07T01:46:54.356Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/85/48/9a13d2975803e8cf2777d5ed57b87a0b6ca2cc795f9a4f59796a910bfb80/nvidia_cusolver_cu12-11.7.3.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:4376c11ad263152bd50ea295c05370360776f8c3427b30991df774f9fb26c450", size = 267506905, upload-time = "2025-03-07T01:47:16.273Z" },
|
||||
]
|
||||
|
||||
@@ -947,6 +984,7 @@ dependencies = [
|
||||
{ name = "nvidia-nvjitlink-cu12" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/f7/cd777c4109681367721b00a106f491e0d0d15cfa1fd59672ce580ce42a97/nvidia_cusparse_cu12-12.5.8.93-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9b6c161cb130be1a07a27ea6923df8141f3c295852f4b260c65f18f3e0a091dc", size = 288117129, upload-time = "2025-03-07T01:47:40.407Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/f5/e1854cb2f2bcd4280c44736c93550cc300ff4b8c95ebe370d0aa7d2b473d/nvidia_cusparse_cu12-12.5.8.93-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ec05d76bbbd8b61b06a80e1eaf8cf4959c3d4ce8e711b65ebd0443bb0ebb13b", size = 288216466, upload-time = "2025-03-07T01:48:13.779Z" },
|
||||
]
|
||||
|
||||
@@ -955,6 +993,7 @@ name = "nvidia-cusparselt-cu12"
|
||||
version = "0.7.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/73/b9/598f6ff36faaece4b3c50d26f50e38661499ff34346f00e057760b35cc9d/nvidia_cusparselt_cu12-0.7.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8878dce784d0fac90131b6817b607e803c36e629ba34dc5b433471382196b6a5", size = 283835557, upload-time = "2025-02-26T00:16:54.265Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/56/79/12978b96bd44274fe38b5dde5cfb660b1d114f70a65ef962bcbbed99b549/nvidia_cusparselt_cu12-0.7.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f1bb701d6b930d5a7cea44c19ceb973311500847f81b634d802b7b539dc55623", size = 287193691, upload-time = "2025-02-26T00:15:44.104Z" },
|
||||
]
|
||||
|
||||
@@ -963,6 +1002,7 @@ name = "nvidia-nccl-cu12"
|
||||
version = "2.27.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/1c/857979db0ef194ca5e21478a0612bcdbbe59458d7694361882279947b349/nvidia_nccl_cu12-2.27.5-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:31432ad4d1fb1004eb0c56203dc9bc2178a1ba69d1d9e02d64a6938ab5e40e7a", size = 322400625, upload-time = "2025-06-26T04:11:04.496Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/89/f7a07dc961b60645dbbf42e80f2bc85ade7feb9a491b11a1e973aa00071f/nvidia_nccl_cu12-2.27.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ad730cf15cb5d25fe849c6e6ca9eb5b76db16a80f13f425ac68d8e2e55624457", size = 322348229, upload-time = "2025-06-26T04:11:28.385Z" },
|
||||
]
|
||||
|
||||
@@ -972,6 +1012,7 @@ version = "12.8.93"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/74/86a07f1d0f42998ca31312f998bd3b9a7eff7f52378f4f270c8679c77fb9/nvidia_nvjitlink_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:81ff63371a7ebd6e6451970684f916be2eab07321b73c9d244dc2b4da7f73b88", size = 39254836, upload-time = "2025-03-07T01:49:55.661Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/a2/8cee5da30d13430e87bf99bb33455d2724d0a4a9cb5d7926d80ccb96d008/nvidia_nvjitlink_cu12-12.8.93-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:adccd7161ace7261e01bb91e44e88da350895c270d23f744f0820c818b7229e7", size = 38386204, upload-time = "2025-03-07T01:49:43.612Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -979,6 +1020,7 @@ name = "nvidia-nvshmem-cu12"
|
||||
version = "3.4.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/6a/03aa43cc9bd3ad91553a88b5f6fb25ed6a3752ae86ce2180221962bc2aa5/nvidia_nvshmem_cu12-3.4.5-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0b48363fc6964dede448029434c6abed6c5e37f823cb43c3bcde7ecfc0457e15", size = 138936938, upload-time = "2025-09-06T00:32:05.589Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/09/6ea3ea725f82e1e76684f0708bbedd871fc96da89945adeba65c3835a64c/nvidia_nvshmem_cu12-3.4.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:042f2500f24c021db8a06c5eec2539027d57460e1c1a762055a6554f72c369bd", size = 139103095, upload-time = "2025-09-06T00:32:31.266Z" },
|
||||
]
|
||||
|
||||
@@ -987,6 +1029,7 @@ name = "nvidia-nvtx-cu12"
|
||||
version = "12.8.90"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/10/c0/1b303feea90d296f6176f32a2a70b5ef230f9bdeb3a72bddb0dc922dc137/nvidia_nvtx_cu12-12.8.90-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d7ad891da111ebafbf7e015d34879f7112832fc239ff0d7d776b6cb685274615", size = 91161, upload-time = "2025-03-07T01:42:23.922Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/eb/86626c1bbc2edb86323022371c39aa48df6fd8b0a1647bc274577f72e90b/nvidia_nvtx_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5b17e2001cc0d751a5bc2c6ec6d26ad95913324a4adb86788c944f8ce9ba441f", size = 89954, upload-time = "2025-03-07T01:42:44.131Z" },
|
||||
]
|
||||
|
||||
@@ -1080,11 +1123,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "4.9.2"
|
||||
version = "4.9.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1b/04/fea538adf7dbbd6d186f551d595961e564a3b6715bdf276b477460858672/platformdirs-4.9.2.tar.gz", hash = "sha256:9a33809944b9db043ad67ca0db94b14bf452cc6aeaac46a88ea55b26e2e9d291", size = 28394, upload-time = "2026-02-16T03:56:10.574Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/19/56/8d4c30c8a1d07013911a8fdbd8f89440ef9f08d07a1b50ab8ca8be5a20f9/platformdirs-4.9.4.tar.gz", hash = "sha256:1ec356301b7dc906d83f371c8f487070e99d3ccf9e501686456394622a01a934", size = 28737, upload-time = "2026-03-05T18:34:13.271Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/48/31/05e764397056194206169869b50cf2fee4dbbbc71b344705b9c0d878d4d8/platformdirs-4.9.2-py3-none-any.whl", hash = "sha256:9170634f126f8efdae22fb58ae8a0eaa86f38365bc57897a6c4f781d1f5875bd", size = 21168, upload-time = "2026-02-16T03:56:08.891Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl", hash = "sha256:68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868", size = 21216, upload-time = "2026-03-05T18:34:12.172Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1329,11 +1372,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "setuptools"
|
||||
version = "82.0.0"
|
||||
version = "81.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/82/f3/748f4d6f65d1756b9ae577f329c951cda23fb900e4de9f70900ced962085/setuptools-82.0.0.tar.gz", hash = "sha256:22e0a2d69474c6ae4feb01951cb69d515ed23728cf96d05513d36e42b62b37cb", size = 1144893, upload-time = "2026-02-08T15:08:40.206Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0d/1c/73e719955c59b8e424d015ab450f51c0af856ae46ea2da83eba51cc88de1/setuptools-81.0.0.tar.gz", hash = "sha256:487b53915f52501f0a79ccfd0c02c165ffe06631443a886740b91af4b7a5845a", size = 1198299, upload-time = "2026-02-06T21:10:39.601Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/c6/76dc613121b793286a3f91621d7b75a2b493e0390ddca50f11993eadf192/setuptools-82.0.0-py3-none-any.whl", hash = "sha256:70b18734b607bd1da571d097d236cfcfacaf01de45717d59e6e04b96877532e0", size = 1003468, upload-time = "2026-02-08T15:08:38.723Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/e3/c164c88b2e5ce7b24d667b9bd83589cf4f3520d97cad01534cd3c4f55fdb/setuptools-81.0.0-py3-none-any.whl", hash = "sha256:fdd925d5c5d9f62e4b74b30d6dd7828ce236fd6ed998a08d81de62ce5a6310d6", size = 1062021, upload-time = "2026-02-06T21:10:37.175Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1393,14 +1436,14 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "sphinx-autodoc-typehints"
|
||||
version = "3.9.5"
|
||||
version = "3.9.8"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "sphinx" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/58/ec/21bd9babcfeb9930a73011257002d5cfa5fd30667b8de6d76dbaf8275dfb/sphinx_autodoc_typehints-3.9.5.tar.gz", hash = "sha256:60e646efb7c352a0e98f34dd7fdcde4527fbbdbdf30371ff8321b6b3eb1fd37d", size = 63249, upload-time = "2026-03-02T19:58:07.974Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/74/4d/02216afa475c838c123b41f30e3a2875ad27c14fae3f5bbed4ba4e7fd894/sphinx_autodoc_typehints-3.9.8.tar.gz", hash = "sha256:1e36b31ee593b7e838988045918b7fa965f5062abbd6800af96d5e2c3f17130e", size = 68763, upload-time = "2026-03-09T15:40:01.02Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/cb/80c250f47a0ca5ac67d82f14811b4068a551a12b4790b085ffdb900de427/sphinx_autodoc_typehints-3.9.5-py3-none-any.whl", hash = "sha256:c94f88a90b6c61a7a6686cb77b410e46e077712838387e6cf22d69e85cfd06a5", size = 34763, upload-time = "2026-03-02T19:58:06.028Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/6c/f275f59095b2fec6627c3ce2caba4e18f55a3925718cf0547cde04821a37/sphinx_autodoc_typehints-3.9.8-py3-none-any.whl", hash = "sha256:df123ec82479934fed27e31d4ccdcf382901c5d9481450fc224054496e574466", size = 36685, upload-time = "2026-03-09T15:39:59.567Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1542,71 +1585,64 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "torch"
|
||||
version = "2.10.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
version = "2.10.0+cu128"
|
||||
source = { registry = "https://download.pytorch.org/whl/cu128" }
|
||||
dependencies = [
|
||||
{ name = "cuda-bindings", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
||||
{ name = "cuda-bindings", marker = "sys_platform == 'linux'" },
|
||||
{ name = "filelock" },
|
||||
{ name = "fsspec" },
|
||||
{ name = "jinja2" },
|
||||
{ name = "networkx" },
|
||||
{ name = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
||||
{ name = "nvidia-cuda-cupti-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
||||
{ name = "nvidia-cuda-nvrtc-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
||||
{ name = "nvidia-cuda-runtime-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
||||
{ name = "nvidia-cudnn-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
||||
{ name = "nvidia-cufft-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
||||
{ name = "nvidia-cufile-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
||||
{ name = "nvidia-curand-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
||||
{ name = "nvidia-cusolver-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
||||
{ name = "nvidia-cusparse-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
||||
{ name = "nvidia-cusparselt-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
||||
{ name = "nvidia-nccl-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
||||
{ name = "nvidia-nvjitlink-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
||||
{ name = "nvidia-nvshmem-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
||||
{ name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
||||
{ name = "nvidia-cublas-cu12", marker = "sys_platform == 'linux'" },
|
||||
{ name = "nvidia-cuda-cupti-cu12", marker = "sys_platform == 'linux'" },
|
||||
{ name = "nvidia-cuda-nvrtc-cu12", marker = "sys_platform == 'linux'" },
|
||||
{ name = "nvidia-cuda-runtime-cu12", marker = "sys_platform == 'linux'" },
|
||||
{ name = "nvidia-cudnn-cu12", marker = "sys_platform == 'linux'" },
|
||||
{ name = "nvidia-cufft-cu12", marker = "sys_platform == 'linux'" },
|
||||
{ name = "nvidia-cufile-cu12", marker = "sys_platform == 'linux'" },
|
||||
{ name = "nvidia-curand-cu12", marker = "sys_platform == 'linux'" },
|
||||
{ name = "nvidia-cusolver-cu12", marker = "sys_platform == 'linux'" },
|
||||
{ name = "nvidia-cusparse-cu12", marker = "sys_platform == 'linux'" },
|
||||
{ name = "nvidia-cusparselt-cu12", marker = "sys_platform == 'linux'" },
|
||||
{ name = "nvidia-nccl-cu12", marker = "sys_platform == 'linux'" },
|
||||
{ name = "nvidia-nvjitlink-cu12", marker = "sys_platform == 'linux'" },
|
||||
{ name = "nvidia-nvshmem-cu12", marker = "sys_platform == 'linux'" },
|
||||
{ name = "nvidia-nvtx-cu12", marker = "sys_platform == 'linux'" },
|
||||
{ name = "setuptools" },
|
||||
{ name = "sympy" },
|
||||
{ name = "triton", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
||||
{ name = "triton", marker = "sys_platform == 'linux'" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/23/2c9fe0c9c27f7f6cb865abcea8a4568f29f00acaeadfc6a37f6801f84cb4/torch-2.10.0-2-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:e521c9f030a3774ed770a9c011751fb47c4d12029a3d6522116e48431f2ff89e", size = 79498254, upload-time = "2026-02-10T21:44:44.095Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/6f/f2e91e34e3fcba2e3fc8d8f74e7d6c22e74e480bbd1db7bc8900fdf3e95c/torch-2.10.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:5c4d217b14741e40776dd7074d9006fd28b8a97ef5654db959d8635b2fe5f29b", size = 146004247, upload-time = "2026-01-21T16:24:29.335Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/fb/5160261aeb5e1ee12ee95fe599d0541f7c976c3701d607d8fc29e623229f/torch-2.10.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:6b71486353fce0f9714ca0c9ef1c850a2ae766b409808acd58e9678a3edb7738", size = 915716445, upload-time = "2026-01-21T16:22:45.353Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/16/502fb1b41e6d868e8deb5b0e3ae926bbb36dab8ceb0d1b769b266ad7b0c3/torch-2.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:c2ee399c644dc92ef7bc0d4f7e74b5360c37cdbe7c5ba11318dda49ffac2bc57", size = 113757050, upload-time = "2026-01-21T16:24:19.204Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/0b/39929b148f4824bc3ad6f9f72a29d4ad865bcf7ebfc2fa67584773e083d2/torch-2.10.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:3202429f58309b9fa96a614885eace4b7995729f44beb54d3e4a47773649d382", size = 79851305, upload-time = "2026-01-21T16:24:09.209Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/14/21fbce63bc452381ba5f74a2c0a959fdf5ad5803ccc0c654e752e0dbe91a/torch-2.10.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:aae1b29cd68e50a9397f5ee897b9c24742e9e306f88a807a27d617f07adb3bd8", size = 146005472, upload-time = "2026-01-21T16:22:29.022Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/fd/b207d1c525cb570ef47f3e9f836b154685011fce11a2f444ba8a4084d042/torch-2.10.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:6021db85958db2f07ec94e1bc77212721ba4920c12a18dc552d2ae36a3eb163f", size = 915612644, upload-time = "2026-01-21T16:21:47.019Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/53/0197f868c75f1050b199fe58f9bf3bf3aecac9b4e85cc9c964383d745403/torch-2.10.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff43db38af76fda183156153983c9a096fc4c78d0cd1e07b14a2314c7f01c2c8", size = 113997015, upload-time = "2026-01-21T16:23:00.767Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/13/e76b4d9c160e89fff48bf16b449ea324bda84745d2ab30294c37c2434c0d/torch-2.10.0-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:cdf2a523d699b70d613243211ecaac14fe9c5df8a0b0a9c02add60fb2a413e0f", size = 79498248, upload-time = "2026-01-21T16:23:09.315Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/93/716b5ac0155f1be70ed81bacc21269c3ece8dba0c249b9994094110bfc51/torch-2.10.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:bf0d9ff448b0218e0433aeb198805192346c4fd659c852370d5cc245f602a06a", size = 79464992, upload-time = "2026-01-21T16:23:05.162Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/69/2b/51e663ff190c9d16d4a8271203b71bc73a16aa7619b9f271a69b9d4a936b/torch-2.10.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:233aed0659a2503b831d8a67e9da66a62c996204c0bba4f4c442ccc0c68a3f60", size = 146018567, upload-time = "2026-01-21T16:22:23.393Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/cd/4b95ef7f293b927c283db0b136c42be91c8ec6845c44de0238c8c23bdc80/torch-2.10.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:682497e16bdfa6efeec8cde66531bc8d1fbbbb4d8788ec6173c089ed3cc2bfe5", size = 915721646, upload-time = "2026-01-21T16:21:16.983Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/56/97/078a007208f8056d88ae43198833469e61a0a355abc0b070edd2c085eb9a/torch-2.10.0-cp314-cp314-win_amd64.whl", hash = "sha256:6528f13d2a8593a1a412ea07a99812495bec07e9224c28b2a25c0a30c7da025c", size = 113752373, upload-time = "2026-01-21T16:22:13.471Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/94/71994e7d0d5238393df9732fdab607e37e2b56d26a746cb59fdb415f8966/torch-2.10.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:f5ab4ba32383061be0fb74bda772d470140a12c1c3b58a0cfbf3dae94d164c28", size = 79850324, upload-time = "2026-01-21T16:22:09.494Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/65/1a05346b418ea8ccd10360eef4b3e0ce688fba544e76edec26913a8d0ee0/torch-2.10.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:716b01a176c2a5659c98f6b01bf868244abdd896526f1c692712ab36dbaf9b63", size = 146006482, upload-time = "2026-01-21T16:22:18.42Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/b9/5f6f9d9e859fc3235f60578fa64f52c9c6e9b4327f0fe0defb6de5c0de31/torch-2.10.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:d8f5912ba938233f86361e891789595ff35ca4b4e2ac8fe3670895e5976731d6", size = 915613050, upload-time = "2026-01-21T16:20:49.035Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/4d/35352043ee0eaffdeff154fad67cd4a31dbed7ff8e3be1cc4549717d6d51/torch-2.10.0-cp314-cp314t-win_amd64.whl", hash = "sha256:71283a373f0ee2c89e0f0d5f446039bdabe8dbc3c9ccf35f0f784908b0acd185", size = 113995816, upload-time = "2026-01-21T16:22:05.312Z" },
|
||||
{ url = "https://download.pytorch.org/whl/cu128/torch-2.10.0%2Bcu128-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:bdbcc703382f948e951c063448c9406bf38ce66c41dd698d9e2733fcf96c037a" },
|
||||
{ url = "https://download.pytorch.org/whl/cu128/torch-2.10.0%2Bcu128-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:7b4bd23ed63de97456fcc81c26fea9f02ee02ce1112111c4dac0d8cfe574b23e" },
|
||||
{ url = "https://download.pytorch.org/whl/cu128/torch-2.10.0%2Bcu128-cp313-cp313-win_amd64.whl", hash = "sha256:4d1b0b49c54223c7c04050b49eac141d77b6edbc34aea1dfc74a6fdb661baa8c" },
|
||||
{ url = "https://download.pytorch.org/whl/cu128/torch-2.10.0%2Bcu128-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:f1f8b840c64b645a4bc61a393db48effb9c92b2dc26c8373873911f0750d1ea7" },
|
||||
{ url = "https://download.pytorch.org/whl/cu128/torch-2.10.0%2Bcu128-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:23f58258012bcf1c349cb22af387e33aadca7f83ea617b080e774eb41e4fe8ff" },
|
||||
{ url = "https://download.pytorch.org/whl/cu128/torch-2.10.0%2Bcu128-cp313-cp313t-win_amd64.whl", hash = "sha256:01b216e097b17a5277cfb47c383cdcacf06abeadcb0daca0c76b59e72854c3b6" },
|
||||
{ url = "https://download.pytorch.org/whl/cu128/torch-2.10.0%2Bcu128-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:c42377bc2607e3e1c60da71b792fb507c3938c87fd6edab8b21c59c91473c36d" },
|
||||
{ url = "https://download.pytorch.org/whl/cu128/torch-2.10.0%2Bcu128-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:37d71feea068776855686a1512058df3f19f6f040a151f055aa746601678744f" },
|
||||
{ url = "https://download.pytorch.org/whl/cu128/torch-2.10.0%2Bcu128-cp314-cp314-win_amd64.whl", hash = "sha256:c57017ca29e62271e362fdeee7d20070e254755a5148b30b553d8a10fc83c7ef" },
|
||||
{ url = "https://download.pytorch.org/whl/cu128/torch-2.10.0%2Bcu128-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:777461f50b2daf77e4bdd8e2ad34bdfc5a993bf1bdf2ab9ef39f5edfe4e9c12b" },
|
||||
{ url = "https://download.pytorch.org/whl/cu128/torch-2.10.0%2Bcu128-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:7bcba6a7c5f0987a13298b1ca843155dcceceac758fa3c7ccd5c7af4059a1080" },
|
||||
{ url = "https://download.pytorch.org/whl/cu128/torch-2.10.0%2Bcu128-cp314-cp314t-win_amd64.whl", hash = "sha256:70d89143c956389d4806cb4e5fe0b1129fe0db280e1073288d17fa76c101cba4" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tornado"
|
||||
version = "6.5.4"
|
||||
version = "6.5.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/37/1d/0a336abf618272d53f62ebe274f712e213f5a03c0b2339575430b8362ef2/tornado-6.5.4.tar.gz", hash = "sha256:a22fa9047405d03260b483980635f0b041989d8bcc9a313f8fe18b411d84b1d7", size = 513632, upload-time = "2025-12-15T19:21:03.836Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f8/f1/3173dfa4a18db4a9b03e5d55325559dab51ee653763bb8745a75af491286/tornado-6.5.5.tar.gz", hash = "sha256:192b8f3ea91bd7f1f50c06955416ed76c6b72f96779b962f07f911b91e8d30e9", size = 516006, upload-time = "2026-03-10T21:31:02.067Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/a9/e94a9d5224107d7ce3cc1fab8d5dc97f5ea351ccc6322ee4fb661da94e35/tornado-6.5.4-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d6241c1a16b1c9e4cc28148b1cda97dd1c6cb4fb7068ac1bedc610768dff0ba9", size = 443909, upload-time = "2025-12-15T19:20:48.382Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/7e/f7b8d8c4453f305a51f80dbb49014257bb7d28ccb4bbb8dd328ea995ecad/tornado-6.5.4-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2d50f63dda1d2cac3ae1fa23d254e16b5e38153758470e9956cbc3d813d40843", size = 442163, upload-time = "2025-12-15T19:20:49.791Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/b5/206f82d51e1bfa940ba366a8d2f83904b15942c45a78dd978b599870ab44/tornado-6.5.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1cf66105dc6acb5af613c054955b8137e34a03698aa53272dbda4afe252be17", size = 445746, upload-time = "2025-12-15T19:20:51.491Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/9d/1a3338e0bd30ada6ad4356c13a0a6c35fbc859063fa7eddb309183364ac1/tornado-6.5.4-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50ff0a58b0dc97939d29da29cd624da010e7f804746621c78d14b80238669335", size = 445083, upload-time = "2025-12-15T19:20:52.778Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/d4/e51d52047e7eb9a582da59f32125d17c0482d065afd5d3bc435ff2120dc5/tornado-6.5.4-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5fb5e04efa54cf0baabdd10061eb4148e0be137166146fff835745f59ab9f7f", size = 445315, upload-time = "2025-12-15T19:20:53.996Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/07/2273972f69ca63dbc139694a3fc4684edec3ea3f9efabf77ed32483b875c/tornado-6.5.4-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9c86b1643b33a4cd415f8d0fe53045f913bf07b4a3ef646b735a6a86047dda84", size = 446003, upload-time = "2025-12-15T19:20:56.101Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/83/41c52e47502bf7260044413b6770d1a48dda2f0246f95ee1384a3cd9c44a/tornado-6.5.4-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:6eb82872335a53dd063a4f10917b3efd28270b56a33db69009606a0312660a6f", size = 445412, upload-time = "2025-12-15T19:20:57.398Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/c7/bc96917f06cbee182d44735d4ecde9c432e25b84f4c2086143013e7b9e52/tornado-6.5.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6076d5dda368c9328ff41ab5d9dd3608e695e8225d1cd0fd1e006f05da3635a8", size = 445392, upload-time = "2025-12-15T19:20:58.692Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/1a/d7592328d037d36f2d2462f4bc1fbb383eec9278bc786c1b111cbbd44cfa/tornado-6.5.4-cp39-abi3-win32.whl", hash = "sha256:1768110f2411d5cd281bac0a090f707223ce77fd110424361092859e089b38d1", size = 446481, upload-time = "2025-12-15T19:21:00.008Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/6d/c69be695a0a64fd37a97db12355a035a6d90f79067a3cf936ec2b1dc38cd/tornado-6.5.4-cp39-abi3-win_amd64.whl", hash = "sha256:fa07d31e0cd85c60713f2b995da613588aa03e1303d75705dca6af8babc18ddc", size = 446886, upload-time = "2025-12-15T19:21:01.287Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/49/8dc3fd90902f70084bd2cd059d576ddb4f8bb44c2c7c0e33a11422acb17e/tornado-6.5.4-cp39-abi3-win_arm64.whl", hash = "sha256:053e6e16701eb6cbe641f308f4c1a9541f91b6261991160391bfc342e8a551a1", size = 445910, upload-time = "2025-12-15T19:21:02.571Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/8c/77f5097695f4dd8255ecbd08b2a1ed8ba8b953d337804dd7080f199e12bf/tornado-6.5.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:487dc9cc380e29f58c7ab88f9e27cdeef04b2140862e5076a66fb6bb68bb1bfa", size = 445983, upload-time = "2026-03-10T21:30:44.28Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/5e/7625b76cd10f98f1516c36ce0346de62061156352353ef2da44e5c21523c/tornado-6.5.5-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:65a7f1d46d4bb41df1ac99f5fcb685fb25c7e61613742d5108b010975a9a6521", size = 444246, upload-time = "2026-03-10T21:30:46.571Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/04/7b5705d5b3c0fab088f434f9c83edac1573830ca49ccf29fb83bf7178eec/tornado-6.5.5-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e74c92e8e65086b338fd56333fb9a68b9f6f2fe7ad532645a290a464bcf46be5", size = 447229, upload-time = "2026-03-10T21:30:48.273Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/34/01/74e034a30ef59afb4097ef8659515e96a39d910b712a89af76f5e4e1f93c/tornado-6.5.5-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:435319e9e340276428bbdb4e7fa732c2d399386d1de5686cb331ec8eee754f07", size = 448192, upload-time = "2026-03-10T21:30:51.22Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/00/fe9e02c5a96429fce1a1d15a517f5d8444f9c412e0bb9eadfbe3b0fc55bf/tornado-6.5.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3f54aa540bdbfee7b9eb268ead60e7d199de5021facd276819c193c0fb28ea4e", size = 448039, upload-time = "2026-03-10T21:30:53.52Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/9e/656ee4cec0398b1d18d0f1eb6372c41c6b889722641d84948351ae19556d/tornado-6.5.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:36abed1754faeb80fbd6e64db2758091e1320f6bba74a4cf8c09cd18ccce8aca", size = 447445, upload-time = "2026-03-10T21:30:55.541Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/76/4921c00511f88af86a33de770d64141170f1cfd9c00311aea689949e274e/tornado-6.5.5-cp39-abi3-win32.whl", hash = "sha256:dd3eafaaeec1c7f2f8fdcd5f964e8907ad788fe8a5a32c4426fbbdda621223b7", size = 448582, upload-time = "2026-03-10T21:30:57.142Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/23/f6c6112a04d28eed765e374435fb1a9198f73e1ec4b4024184f21faeb1ad/tornado-6.5.5-cp39-abi3-win_amd64.whl", hash = "sha256:6443a794ba961a9f619b1ae926a2e900ac20c34483eea67be4ed8f1e58d3ef7b", size = 448990, upload-time = "2026-03-10T21:30:58.857Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/c8/876602cbc96469911f0939f703453c1157b0c826ecb05bdd32e023397d4e/tornado-6.5.5-cp39-abi3-win_arm64.whl", hash = "sha256:2c9a876e094109333f888539ddb2de4361743e5d21eece20688e3e351e4990a6", size = 448016, upload-time = "2026-03-10T21:31:00.43Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1623,12 +1659,13 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "trainlib"
|
||||
version = "0.1.0"
|
||||
version = "0.2.1"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "colorama" },
|
||||
{ name = "matplotlib" },
|
||||
{ name = "numpy" },
|
||||
{ name = "setuptools" },
|
||||
{ name = "tensorboard" },
|
||||
{ name = "torch" },
|
||||
{ name = "tqdm" },
|
||||
@@ -1658,11 +1695,12 @@ requires-dist = [
|
||||
{ name = "myst-parser", marker = "extra == 'doc'" },
|
||||
{ name = "numpy", specifier = ">=2.4.1" },
|
||||
{ name = "pytest", marker = "extra == 'test'" },
|
||||
{ name = "setuptools", specifier = "<=81.0.0" },
|
||||
{ name = "sphinx", marker = "extra == 'doc'" },
|
||||
{ name = "sphinx-autodoc-typehints", marker = "extra == 'doc'" },
|
||||
{ name = "sphinx-togglebutton", marker = "extra == 'doc'" },
|
||||
{ name = "tensorboard", specifier = ">=2.20.0" },
|
||||
{ name = "torch", specifier = ">=2.5.1" },
|
||||
{ name = "torch", index = "https://download.pytorch.org/whl/cu128" },
|
||||
{ name = "tqdm", specifier = ">=4.67.1" },
|
||||
]
|
||||
provides-extras = ["dev", "doc", "test"]
|
||||
@@ -1681,9 +1719,13 @@ name = "triton"
|
||||
version = "3.6.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/12/34d71b350e89a204c2c7777a9bba0dcf2f19a5bfdd70b57c4dbc5ffd7154/triton-3.6.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:448e02fe6dc898e9e5aa89cf0ee5c371e99df5aa5e8ad976a80b93334f3494fd", size = 176133521, upload-time = "2026-01-20T16:16:13.321Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/0b/37d991d8c130ce81a8728ae3c25b6e60935838e9be1b58791f5997b24a54/triton-3.6.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10c7f76c6e72d2ef08df639e3d0d30729112f47a56b0c81672edc05ee5116ac9", size = 188289450, upload-time = "2026-01-20T16:00:49.136Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/4e/41b0c8033b503fd3cfcd12392cdd256945026a91ff02452bef40ec34bee7/triton-3.6.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1722e172d34e32abc3eb7711d0025bb69d7959ebea84e3b7f7a341cd7ed694d6", size = 176276087, upload-time = "2026-01-20T16:16:18.989Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/f8/9c66bfc55361ec6d0e4040a0337fb5924ceb23de4648b8a81ae9d33b2b38/triton-3.6.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d002e07d7180fd65e622134fbd980c9a3d4211fb85224b56a0a0efbd422ab72f", size = 188400296, upload-time = "2026-01-20T16:00:56.042Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/55/5ecf0dcaa0f2fbbd4420f7ef227ee3cb172e91e5fede9d0ecaddc43363b4/triton-3.6.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef5523241e7d1abca00f1d240949eebdd7c673b005edbbce0aca95b8191f1d43", size = 176138577, upload-time = "2026-01-20T16:16:25.426Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/3d/9e7eee57b37c80cec63322c0231bb6da3cfe535a91d7a4d64896fcb89357/triton-3.6.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a17a5d5985f0ac494ed8a8e54568f092f7057ef60e1b0fa09d3fd1512064e803", size = 188273063, upload-time = "2026-01-20T16:01:07.278Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/48/db/56ee649cab5eaff4757541325aca81f52d02d4a7cd3506776cad2451e060/triton-3.6.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b3a97e8ed304dfa9bd23bb41ca04cdf6b2e617d5e782a8653d616037a5d537d", size = 176274804, upload-time = "2026-01-20T16:16:31.528Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/56/6113c23ff46c00aae423333eb58b3e60bdfe9179d542781955a5e1514cb3/triton-3.6.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:46bd1c1af4b6704e554cad2eeb3b0a6513a980d470ccfa63189737340c7746a7", size = 188397994, upload-time = "2026-01-20T16:01:14.236Z" },
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user