diff --git a/.gitattributes b/.gitattributes index a6344aac8c09253b3b630fb776ae94478aa0275b..0fb748c041a7406c09ea86334dfdfee6155d5bd8 100644 --- a/.gitattributes +++ b/.gitattributes @@ -33,3 +33,18 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text *.zip filter=lfs diff=lfs merge=lfs -text *.zst filter=lfs diff=lfs merge=lfs -text *tfevents* filter=lfs diff=lfs merge=lfs -text +Discrete-Diffusion-Forcing/Discrete[[:space:]]Diffusion[[:space:]]Forcing.pdf filter=lfs diff=lfs merge=lfs -text +Discrete-Diffusion-Forcing/docs/assets/img/d2f/fig1_main_result.png filter=lfs diff=lfs merge=lfs -text +Discrete-Diffusion-Forcing/docs/assets/img/d2f/fig2_tradeoff.png filter=lfs diff=lfs merge=lfs -text +Discrete-Diffusion-Forcing/docs/assets/img/d2f/fig3_overview.png filter=lfs diff=lfs merge=lfs -text +Discrete-Diffusion-Forcing/docs/assets/img/d2f/fig4_pipeline.png filter=lfs diff=lfs merge=lfs -text +Discrete-Diffusion-Forcing/docs/assets/img/d2f/logo_lr.png filter=lfs diff=lfs merge=lfs -text +Discrete-Diffusion-Forcing/docs/assets/img/d2f/logo_ud.png filter=lfs diff=lfs merge=lfs -text +Discrete-Diffusion-Forcing/docs/assets/img/d2f/table1_llada_results.png filter=lfs diff=lfs merge=lfs -text +Discrete-Diffusion-Forcing/docs/assets/img/d2f/table2_dream_results.png filter=lfs diff=lfs merge=lfs -text +Discrete-Diffusion-Forcing/docs/assets/img/d2f/wechat.png filter=lfs diff=lfs merge=lfs -text +Discrete-Diffusion-Forcing/docs/assets/video/block_demo.mp4 filter=lfs diff=lfs merge=lfs -text +Discrete-Diffusion-Forcing/docs/assets/video/block_demo_small.mp4 filter=lfs diff=lfs merge=lfs -text +Discrete-Diffusion-Forcing/docs/assets/video/d2f_vs_ar_demo.mp4 filter=lfs diff=lfs merge=lfs -text +Discrete-Diffusion-Forcing/docs/assets/video/d2f_vs_ar_demo_small.mp4 filter=lfs diff=lfs merge=lfs -text +wandb/run-20251017_233420-gok04idh/run-gok04idh.wandb filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..f2fb8fbac3b5981a6da03f4105941c8c7440b41f --- /dev/null +++ b/.gitignore @@ -0,0 +1,228 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[codz] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py.cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +# Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +# poetry.lock +# poetry.toml + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. +# https://pdm-project.org/en/latest/usage/project/#working-with-version-control +# pdm.lock +# pdm.toml +.pdm-python +.pdm-build/ + +# pixi +# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. +# pixi.lock +# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one +# in the .venv directory. It is recommended not to include this directory in version control. +.pixi + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# Redis +*.rdb +*.aof +*.pid + +# RabbitMQ +mnesia/ +rabbitmq/ +rabbitmq-data/ + +# ActiveMQ +activemq-data/ + +# SageMath parsed files +*.sage.py + +# Environments +.env +.envrc +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +# .idea/ + +# Abstra +# Abstra is an AI-powered process automation framework. +# Ignore directories containing user credentials, local state, and settings. +# Learn more at https://abstra.io/docs +.abstra/ + +# Visual Studio Code +# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore +# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore +# and can be added to the global gitignore or merged into this file. However, if you prefer, +# you could uncomment the following to ignore the entire vscode folder +# .vscode/ + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc + +# Marimo +marimo/_static/ +marimo/_lsp/ +__marimo__/ + +# Streamlit +.streamlit/secrets.toml + + +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets +!*.code-workspace + +# Built Visual Studio Code Extensions +*.vsix diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000000000000000000000000000000000..7fef07cef093bb130a5c1499dffb3b9768f84fd0 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "FlexMDM"] + path = FlexMDM + url = git@github.com:brianlck/FlexMDM.git +[submodule "Discrete-Diffusion-Forcing"] + path = Discrete-Diffusion-Forcing + url = git@github.com:zhijie-group/Discrete-Diffusion-Forcing.git diff --git a/.gradio/certificate.pem b/.gradio/certificate.pem new file mode 100644 index 0000000000000000000000000000000000000000..b85c8037f6b60976b2546fdbae88312c5246d9a3 --- /dev/null +++ b/.gradio/certificate.pem @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- diff --git a/Discrete-Diffusion-Forcing/.gitignore b/Discrete-Diffusion-Forcing/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..c0ce246b8969ed6afc7373e8f450f3393e926e2e --- /dev/null +++ b/Discrete-Diffusion-Forcing/.gitignore @@ -0,0 +1,3 @@ +d2f_vllm +evals_* +*.pyc \ No newline at end of file diff --git a/Discrete-Diffusion-Forcing/.python-version b/Discrete-Diffusion-Forcing/.python-version new file mode 100644 index 0000000000000000000000000000000000000000..e4fba2183587225f216eeada4c78dfab6b2e65f5 --- /dev/null +++ b/Discrete-Diffusion-Forcing/.python-version @@ -0,0 +1 @@ +3.12 diff --git a/Discrete-Diffusion-Forcing/.vscode/launch.json b/Discrete-Diffusion-Forcing/.vscode/launch.json new file mode 100644 index 0000000000000000000000000000000000000000..1718d4352956368aec1bdf213e11d258d1f147d1 --- /dev/null +++ b/Discrete-Diffusion-Forcing/.vscode/launch.json @@ -0,0 +1,37 @@ +{ + "configurations": [ + + { + "name": "PyDbg: `Dream` Accelerate Launch Debug", + "type": "debugpy", + "request": "launch", + "module": "accelerate.commands.launch", + "args": [ + "--main_process_port", + "29520", + "--num_processes", + "1", + "D2F-eval/eval_dream_d2f_vllm.py", + "--model", + "dream_lora", + "--model_args", + "pretrained=/data1/ckpts/Dream-org/Dream-v0-Base-7B,lora_path=/data1/xck/ckpt/wx_dream_base/Decoder-ddt_test-20k,max_new_tokens=256,diffusion_steps=256,temperature=0,add_bos_token=true,escape_until=true,block_size=32,block_add_threshold=0.9,skip_threshold=0.95,decoded_token_threshold=0.9,dtype=bfloat16,sampling_strategy=default,save_dir=evals_dream_single/Decoder-ddt_test-20k/humaneval-ns0-len256-temp0-limit10000-diffsteps256-block32-thresh0.9-decodethresh0.7-skip0.7-toppnone-dtypebfloat16-samplingdefault", + "--tasks", + "humaneval", + "--num_fewshot", + "0", + "--batch_size", + "1", + "--output_path", + "evals_dream_single/Decoder-ddt_test-20k/humaneval-ns0-len256-temp0-limit10000-diffsteps256-block32-thresh0.9-decodethresh0.9-skip0.95-toppnone-dtypebfloat16-samplingdefault", + "--log_samples", + "--confirm_run_unsafe_code" + ], + "env": { + "HF_ALLOW_CODE_EVAL": "1", + "CUDA_VISIBLE_DEVICES": "0" + }, + "cwd": "${workspaceFolder}" + }, + ] +} \ No newline at end of file diff --git a/Discrete-Diffusion-Forcing/D2F-eval/eval_dream.py b/Discrete-Diffusion-Forcing/D2F-eval/eval_dream.py new file mode 100644 index 0000000000000000000000000000000000000000..b7dc86238671419afbcc8ef8269edcd71363d7cc --- /dev/null +++ b/Discrete-Diffusion-Forcing/D2F-eval/eval_dream.py @@ -0,0 +1,1155 @@ +import logging +import gc +import time +import json +from datetime import timedelta +from typing import List, Optional, Tuple, Type, TypeVar, Union +import torch +import torch.nn.functional as F +import torch.distributions as dists +import transformers +from accelerate import ( + Accelerator, + InitProcessGroupKwargs, +) +from datasets import Dataset +from packaging import version +from tqdm import tqdm +from peft import PeftConfig, PeftModel +import numpy as np + +from lm_eval import utils +from lm_eval.api.instance import Instance +from lm_eval.api.model import LM +from lm_eval.api.registry import register_model +from lm_eval.models.utils import get_dtype +from lm_eval.__main__ import cli_evaluate + +eval_logger = logging.getLogger(__name__) +T = TypeVar("T", bound="LM") +import random +def set_seed(seed): + torch.manual_seed(seed) + random.seed(seed) + np.random.seed(seed) + + torch.backends.cudnn.deterministic = True + torch.backends.cudnn.benchmark = False + +def shift_logits(logits): + shifted_logits = torch.zeros_like(logits) + shifted_logits[:, 1:, :] = logits[:, :-1, :] + shifted_logits[:, 0, :] = 1.0 + return shifted_logits + +def create_full_block_attention_mask(prompt_length, max_length, block_size, device=None, dtype=None): + """ + Creates a complete attention mask for the entire sequence with block-based causal attention. + + Args: + prompt_length: Length of the prompt (first irregular block) + max_length: Maximum total sequence length + block_size: Size of each regular block + device: Device to create tensor on + dtype: Data type for the attention mask + + Returns: + attention_mask: Tensor of shape [1, 1, max_length, max_length] + """ + # Use the provided dtype or default to bfloat16 + if dtype is None: + dtype = torch.bfloat16 + + # Initialize mask with -inf (no attention) + attention_mask = torch.full((1, 1, max_length, max_length), -torch.inf, device=device, dtype=dtype) + + # Block 0: Prompt (can see itself) + attention_mask[:, :, :prompt_length, :prompt_length] = 0 + + # Calculate the number of regular blocks after prompt + remaining_length = max_length - prompt_length + num_blocks = (remaining_length + block_size - 1) // block_size + + # Process each regular block + for b in range(num_blocks): + block_start = prompt_length + b * block_size + block_end = min(prompt_length + (b + 1) * block_size, max_length) + + # Current block can see the prompt + attention_mask[:, :, block_start:block_end, :prompt_length] = 0 + + # Current block can see all previous regular blocks + for prev_b in range(b): + prev_start = prompt_length + prev_b * block_size + prev_end = min(prompt_length + (prev_b + 1) * block_size, max_length) + attention_mask[:, :, block_start:block_end, prev_start:prev_end] = 0 + + # Current block can see itself (full attention within block) + attention_mask[:, :, block_start:block_end, block_start:block_end] = 0 + + return attention_mask + +def extract_attention_mask(full_mask, start_pos, input_length, cache_length): + """ + Extract the relevant portion of attention mask for current forward pass. + + Args: + full_mask: Complete attention mask [1, 1, max_length, max_length] + start_pos: Starting position in the full sequence + input_length: Length of current input sequence + cache_length: Length of cached sequence + + Returns: + attention_mask: Extracted mask [1, 1, input_length, cache_length + input_length] + """ + end_pos = start_pos + input_length + total_length = cache_length + input_length + + # Extract the relevant rows (current input positions) + # and columns (cache + current input positions) + extracted_mask = torch.full((1, 1, input_length, total_length), -torch.inf, + device=full_mask.device, dtype=full_mask.dtype) + + # Copy cache columns (0 to cache_length in the extracted mask corresponds to 0 to cache_length in full mask) + extracted_mask[:, :, :, :cache_length] = full_mask[:, :, start_pos:end_pos, :cache_length] + + # Copy current input columns + extracted_mask[:, :, :, cache_length:] = full_mask[:, :, start_pos:end_pos, start_pos:end_pos] + + return extracted_mask + +def build_custom_float_attention_mask(input_ids, prompt_length, block_size, device=None, dtype=None): + B, seq_len = input_ids.shape + # Use the provided dtype or default to float32 + if dtype is None: + dtype = torch.float32 + # Initialize to all -inf + attn_mask = torch.full((B, 1, seq_len, seq_len), float('-inf'), dtype=dtype, device=device) + # 1. Prompt part: each token can attend to the entire prompt + for i in range(B): + attn_mask[i, :, :, :prompt_length[i]] = 0.0 # Allow all tokens to see the prompt + + # 2. Block division: divide into blocks starting from prompt_length + num_blocks = (seq_len - prompt_length[i] + block_size - 1) // block_size + + for b in range(num_blocks): + block_start = prompt_length[i] + b * block_size + block_end = min(block_start + block_size, seq_len) + + # Full attention within the block + attn_mask[i, :, block_start:block_end, block_start:block_end] = 0.0 + + # Causal attention between blocks (can only see previous blocks) + for prev_b in range(b): + prev_start = prompt_length[i] + prev_b * block_size + prev_end = min(prev_start + block_size, seq_len) + + # Current block can see previous blocks + attn_mask[i, :, block_start:block_end, prev_start:prev_end] = 0.0 + + return attn_mask + +def top_p_logits(logits, top_p=None): + sorted_logits, sorted_indices = torch.sort(logits, descending=True) + cumulative_probs = torch.cumsum(F.softmax(sorted_logits, dim=-1), dim=-1) + sorted_indices_to_remove = cumulative_probs > top_p + # Shift the indices to the right to keep the first token above the threshold + sorted_indices_to_remove[..., 1:] = sorted_indices_to_remove[..., :-1].clone() + sorted_indices_to_remove[..., 0] = 0 + + mask = torch.zeros_like(logits, dtype=torch.bool, device=logits.device) + mask = mask.scatter_(-1, sorted_indices, sorted_indices_to_remove) + logits = logits.masked_fill(mask, torch.finfo(logits.dtype).min) + return logits + +def top_k_logits(logits, top_k=None): + top_k = min(top_k, logits.size(-1)) # Safety check + # Remove all tokens with a probability less than the last token of the top-k + indices_to_remove = logits < torch.topk(logits, top_k)[0][..., -1, None] + logits = logits.masked_fill(indices_to_remove, torch.finfo(logits.dtype).min) + return logits + +def sample_tokens(logits, temperature=0.0, top_p=None, top_k=None, margin_confidence=False, neg_entropy=False): + if temperature > 0: + logits = logits / temperature + if top_p is not None and top_p < 1: + logits = top_p_logits(logits, top_p) + if top_k is not None: + logits = top_k_logits(logits, top_k) + probs = torch.softmax(logits, dim=-1) + + if temperature > 0: + try: + x0 = dists.Categorical(probs=probs).sample() + initial_confidence = torch.gather(probs, -1, x0.unsqueeze(-1)).squeeze(-1) + except: + initial_confidence, x0 = probs.max(dim=-1) + else: + initial_confidence, x0 = probs.max(dim=-1) + + # Save initial confidence + confidence = initial_confidence.clone() + + if margin_confidence: + sorted_probs, _ = torch.sort(probs, dim=-1, descending=True) + # Extract top1 and top2 probabilities + top1_probs = sorted_probs[:, 0] + top2_probs = sorted_probs[:, 1] + # Calculate confidence as top1 - top2 + confidence = top1_probs - top2_probs + + if neg_entropy: + epsilon = 1e-10 + log_probs = torch.log(probs + epsilon) + confidence = torch.sum(probs * log_probs, dim=-1) + + return confidence, x0, initial_confidence + +@register_model("dream_lora") +class DreamLoRA(LM): + def __init__( + self, + pretrained: Union[str, transformers.PreTrainedModel], + lora_path: str, + batch_size: Optional[Union[int, str]] = 1, + device: Optional[str] = "cuda", + dtype: Optional[Union[str, torch.dtype]] = "auto", + max_new_tokens: Optional[int] = 128, + max_length: Optional[int] = 2048, # Updated to match example code + add_bos_token: Optional[bool] = False, + nll_type: Optional[str] = "mc", + log_type: Optional[str] = "ftb", + mc_num: Optional[int] = 128, + classifier_free_guidance: Optional[float] = 1.0, + sampling_eps: Optional[float] = 1e-3, + diffusion_steps: Optional[int] = 128, + trust_remote_code: Optional[bool] = True, + parallelize: Optional[bool] = False, + autogptq: Optional[Union[bool, str]] = False, + temperature: Optional[float] = 0.2, # Updated default + top_p: Optional[float] = None, # Updated default + top_k: Optional[float] = None, + alg: Optional[str] = "entropy", + alg_temp: Optional[float] = 0.0, + escape_until: Optional[bool] = False, + block_size: Optional[int] = 4, # Updated to match example code + mask_token_id: Optional[int] = 151666, # Added mask_token_id parameter + block_add_threshold: Optional[float] = 0.5, # Added block_add_threshold parameter + decoded_token_threshold: Optional[int] = 0.9, # Added decoded_token_threshold parameter + skip_threshold: Optional[float] = 1.0, # Added skip_threshold parameter + sampling_strategy: Optional[str] = "default", # Added sampling_strategy parameter + save_dir: Optional[str] = None, + **kwargs, + ) -> None: + super().__init__() + + # prepare for parallelism + assert isinstance(device, str) + assert isinstance(pretrained, str) + assert isinstance(batch_size, (int, str)) + + gpus = torch.cuda.device_count() + accelerator_kwargs = InitProcessGroupKwargs(timeout=timedelta(weeks=52)) + accelerator = Accelerator(kwargs_handlers=[accelerator_kwargs]) + if accelerator.num_processes > 1: + self.accelerator = accelerator + + if "npu" in accelerator.device.type: + gpus = torch.npu.device_count() + + # using one process with no model parallelism + if not (parallelize or accelerator.num_processes > 1): + # use user-passed device + device_list = set( + ["cuda", "cpu"] + + [f"cuda:{i}" for i in range(gpus)] + + ["mps", "mps:0"] + + [f"npu:{i}" for i in range(gpus)] + ) + if device and device in device_list: + self._device = torch.device(device) + eval_logger.info(f"Using device '{device}'") + if device in ("mps", "mps:0") and version.parse( + torch.__version__ + ) < version.parse("2.1"): + raise RuntimeError( + f"mps requires torch >= 2.1. You have {torch.__version__}" + ) + else: + eval_logger.info("Device not specified") + eval_logger.info(f"Cuda Available? {torch.cuda.is_available()}") + self._device = ( + torch.device("cuda") + if torch.cuda.is_available() + else torch.device("cpu") + ) + else: # Parallelism managed by accelerate + if device != "cuda": + eval_logger.info( + f"Using `accelerate launch` or `parallelize=True`, device '{device}' will be overridden when placing model." + ) + # TODO: include in warning that `load_in_8bit` etc. affect this too + self._device = ( + self.accelerator.device + if hasattr(self, "accelerator") + else torch.device(device) + ) + + self.batch_size_per_gpu = batch_size + if isinstance(batch_size, str): + self.batch_size_per_gpu = int(batch_size) + + # Save LoRA path and block_size + self.lora_path = lora_path + self.block_size = block_size + self.block_add_threshold = block_add_threshold # New block_add_threshold attribute + self.skip_threshold = skip_threshold # New skip_threshold attribute + self.sampling_strategy = sampling_strategy # Save sampling strategy parameter + self.decoded_token_threshold = decoded_token_threshold # New decoded_token_threshold attribute + self.save_dir = save_dir + + # Add metric tracking + self.total_forward_passes = 0 + self.total_generated_tokens = 0 + self.total_prompts = 0 + # Add time and token statistics + self.total_generation_time = 0.0 + self.total_block_tokens = 0 # Number of blocks * block_size + self.total_actual_tokens = 0 # Actual generated tokens (excluding EOS) + self.total_non_eos_tokens = 0 # Total non-EOS tokens in the entire sequence + self.all_generation_times = [] + self.all_block_tokens = [] + self.all_actual_tokens = [] + self.all_non_eos_tokens = [] + + # Save target_dtype for later use + self.target_dtype = get_dtype(dtype) + + self._create_model_and_tokenizer(pretrained, dtype, trust_remote_code) + + if isinstance(pretrained, str): + if gpus >= 1 or str(self.device) == "mps": + # TODO: can remove this whole snippet except in the mps case, perhaps? + if not (parallelize or autogptq or hasattr(self, "accelerator")): + # place model onto device requested manually, + # if not using HF Accelerate or device_map + # or any other option that preloads model onto device + try: + self.model.to(self.device) + except ValueError: + eval_logger.debug( + "Failed to place model onto specified device. This may be because the model is quantized via `bitsandbytes` or `device_map` is provided. If the desired GPU is being used, this message is safe to ignore." + ) + # multigpu data-parallel support when launched with accelerate + if gpus > 1: + if accelerator.num_processes > 1: + if parallelize: + eval_logger.warning( + "You are both using a HF Accelerate `device_map` (`--model_args parallelize=True`) and launching via `accelerate launch`. This will attempt to do model and data parallelism depending on the resources available." + ) + elif gpus > accelerator.num_processes: + eval_logger.warning( + "WARNING: The number of total system GPUs does not match the number of spawned processes. " + "If you would like to use data parallelism, please launch the script " + "with 'accelerate launch *script*'. " + f"Current run will proceed with {accelerator.num_processes} devices." + ) + if self.accelerator.is_local_main_process: + eval_logger.info( + f"Using {gpus} devices with data parallelism" + ) + + self._device = torch.device(f"{accelerator.device}") + self.accelerator = accelerator + + self._rank = self.accelerator.local_process_index + self._world_size = self.accelerator.num_processes + else: + # if we aren't launching via accelerate, ditch + self._rank = 0 + self._world_size = 1 + else: + # if a PreTrainedModel was passed into HFLM, we forgo distributed setup. + eval_logger.warning( + "Passed an already-initialized model through `pretrained`, assuming single-process call to evaluate() or custom distributed integration" + ) + self._rank = 0 + self._world_size = 1 + + self.max_length = max_length + self.add_bos_token = add_bos_token + # generation params + self.max_new_tokens = max_new_tokens + self.diffusion_steps = diffusion_steps + self.temperature = temperature + self.top_p = top_p + self.top_k = top_k + self.alg = alg + self.alg_temp = alg_temp + self.escape_until = escape_until + self.block_size = block_size + self.mask_token_id = mask_token_id + + # loglikelihood params + self.nll_type = nll_type + self.log_type = log_type + self.mc_num = mc_num + self.classifier_free_guidance = classifier_free_guidance + self.sampling_eps = sampling_eps + + @property + def batch_size(self): + return self.batch_size_per_gpu + + @property + def device(self): + return self._device + + @property + def rank(self): + return self._rank + + @property + def world_size(self): + return self._world_size + + def _create_model_and_tokenizer(self, pretrained, dtype, trust_remote_code): + # Get correct data type + from model_cache.dream.model_dream import DreamModel + from model_cache.dream.configuration_dream import DreamConfig + target_dtype = get_dtype(dtype) + + # Load base model, using DreamModel and DreamConfig + model_config = DreamConfig.from_pretrained(pretrained) + self.model = DreamModel.from_pretrained( + pretrained, + config=model_config, + torch_dtype=target_dtype, + trust_remote_code=False, + ).eval() + + # Load LoRA config and model + config = PeftConfig.from_pretrained(self.lora_path) + self.model = PeftModel.from_pretrained(self.model, self.lora_path) + + # Only convert data type if target_dtype is not None and not "auto" + if target_dtype is not None and target_dtype != "auto": + self.model = self.model.to(target_dtype) + + # Move to specified device + self.model = self.model.to(self.device) + + self.tokenizer = transformers.AutoTokenizer.from_pretrained( + pretrained, trust_remote_code=trust_remote_code + ) + + def tok_decode(self, tokens, skip_special_tokens=True): + return self.tokenizer.decode(tokens, skip_special_tokens=skip_special_tokens) + + def tok_encode(self, text, add_special_tokens=True): + return self.tokenizer( + text, return_tensors="pt", add_special_tokens=add_special_tokens + ).input_ids + + @classmethod + def create_from_arg_string( + cls: Type[T], arg_string: str, additional_config: Optional[dict] = None + ) -> T: + """ + Creates an instance of the LM class using the given argument string and additional config. + + Parameters: + - arg_string: A string containing arguments in the format key1=value1,key2=value2. + - additional_config: Optional dictionary containing additional configuration parameters. + + Returns: + - Instance of the LM class. + """ + additional_config = {} if additional_config is None else additional_config + args = utils.simple_parse_args_string(arg_string) + args2 = {k: v for k, v in additional_config.items() if v is not None} + return cls(**args, **args2) + + def apply_chat_template( + self, chat_history, add_generation_prompt: bool = True + ) -> str: + """ + Method to apply a chat template to a list of chat history between user and model. + """ + chat_templated = self.tokenizer.apply_chat_template( + chat_history, + tokenize=False, + add_generation_prompt=add_generation_prompt, + continue_final_message=not add_generation_prompt, + ) + + return chat_templated + + @property + def tokenizer_name(self) -> str: + return self.tokenizer.name_or_path.replace("/", "__") + + def _count_non_eos_tokens_before_truncation(self, generated_sequence, prompt_length): + """ + Unified token counting function: counts non-EOS tokens in the generated sequence (before truncation). + """ + # Get the generated part (excluding the prompt) + generated_tokens = generated_sequence[prompt_length:] + # Count non-EOS tokens + eos_token_id = self.tokenizer.eos_token_id + if eos_token_id is not None: + # If it's a tensor, convert to list for counting + if hasattr(generated_tokens, 'tolist'): + generated_tokens_list = generated_tokens.tolist() + else: + generated_tokens_list = generated_tokens + non_eos_count = sum(1 for token in generated_tokens_list if token != eos_token_id) + else: + non_eos_count = len(generated_tokens) + return non_eos_count + + def _generate_batch(self, prompts: List[str]) -> List[str]: + if self.add_bos_token: + prompts = [self.tokenizer.bos_token + p for p in prompts] + + responses = [] + + # Generate for each prompt individually (block generation usually processes one by one) + for i, prompt in enumerate(prompts): + # tokenize + prompt_ids = self.tokenizer.encode(prompt) + prompt_tensor = torch.tensor([prompt_ids], device=self.device, dtype=torch.long) + + if len(prompt_ids) > self.max_length - self.max_new_tokens: + eval_logger.warning(f"Prompt length {len(prompt_ids)} is larger than {self.max_length-self.max_new_tokens}, cutoff on the left side") + prompt_tensor = prompt_tensor[:, -(self.max_length-self.max_new_tokens):] + + # Use generate_block_single method to generate, returns EOS-truncated response text + response = self._generate_block_single(prompt_tensor) + responses.append(response) + + return responses + + def _generate_block_single(self, prompt): + """ + Generates a response for a single prompt using parallel block generation, based on KV cache, + and using pre-generated attention masks. + Returns: EOS-truncated response text. + """ + self.model.eval() + + mask_id = self.mask_token_id + block_size = self.block_size + block_add_threshold = self.block_add_threshold + skip_threshold = self.skip_threshold + decoded_token_threshold = self.decoded_token_threshold + + # Pre-generate full attention mask, using model's data type + prompt_length = prompt.shape[1] + full_attention_mask = create_full_block_attention_mask( + prompt_length=prompt_length, + max_length=self.max_length, + block_size=block_size, + device=self.device, + dtype=self.target_dtype if self.target_dtype is not None and self.target_dtype != "auto" else torch.bfloat16 + ) + + with torch.inference_mode(): + # Initialization + x_t = prompt.to(self.device) + + # Track block states - state can be: 'active', 'to_cache', 'in_cache' + # Added 'is_complete' field to indicate whether it's a complete state (True) or incomplete (False) + block_states = { + 0: { + 'start_pos': 0, + 'end_pos': prompt.shape[1], + 'mask_count': 0, + 'total_masks': prompt.shape[1], + 'state': 'to_cache', # prompt ready for caching immediately + 'is_complete': True, # prompt is always in a complete state + }, + } + + # Initialize cache + past_key_values = None + last_logits = None + + current_blocks = 0 # Number of active blocks + step = 0 + eos_detected = False # EOS detection flag + + while current_blocks >= 0: + step += 1 + + # Check if a new block needs to be added + if len(block_states)-1 < (self.max_new_tokens // block_size) and not eos_detected: + last_block_id = len(block_states) - 1 + current_progress = (block_states[last_block_id]['total_masks'] - + block_states[last_block_id]['mask_count']) / block_states[last_block_id]['total_masks'] + if current_progress >= block_add_threshold: + # Add new block - defaults to incomplete state + new_block_id = len(block_states) + new_start_pos = x_t.shape[1] + x_t = torch.cat([x_t, torch.tensor([[mask_id] * block_size]).to(self.device)], dim=1) + + block_states[new_block_id] = { + 'start_pos': new_start_pos, + 'end_pos': new_start_pos + block_size, + 'mask_count': block_size, + 'total_masks': block_size, + 'state': 'active', + 'is_complete': False, # New block defaults to incomplete state + } + current_blocks += 1 + + # At the beginning of each loop, update block completion states + self._update_block_completion_states(block_states, decoded_token_threshold) + # Check if there are still mask tokens + mask_index = (x_t == mask_id) + if mask_index.sum() == 0 and current_blocks == 0: + break + + # Determine which blocks need to be added to cache + blocks_to_cache = [bid for bid, state in block_states.items() + if state['state'] == 'to_cache'] + + # Determine the part to process + cache_length = 0 if past_key_values is None else past_key_values.get_seq_length() + + # Determine content to add to cache + update_kvcache = 0 + if blocks_to_cache: + # Find the earliest block that needs to be cached + earliest_block_id = min(blocks_to_cache) + earliest_pos = block_states[earliest_block_id]['start_pos'] + + # Find the latest block that needs to be cached + latest_block_id = max(blocks_to_cache) + latest_pos = block_states[latest_block_id]['end_pos'] + + # Update cache for all blocks within this range + update_kvcache = latest_pos - earliest_pos + + # Create input sequence for forward pass + process_start_pos = cache_length + + if update_kvcache > 0: + # Need to update cache - use completed blocks + earliest_block_to_cache = min(blocks_to_cache) + input_seq = x_t[:, block_states[earliest_block_to_cache]['start_pos']:] + process_start_pos = block_states[earliest_block_to_cache]['start_pos'] + else: + # Only process active blocks + active_blocks = [bid for bid in block_states.keys() if block_states[bid]['state'] == 'active'] + if active_blocks: + # Get all active blocks after the cache + earliest_active_after_cache = float('inf') + for bid in active_blocks: + if block_states[bid]['start_pos'] >= cache_length: + earliest_active_after_cache = min(earliest_active_after_cache, block_states[bid]['start_pos']) + + if earliest_active_after_cache < float('inf'): + input_seq = x_t[:, earliest_active_after_cache:] + process_start_pos = earliest_active_after_cache + else: + # No active blocks after cache, this shouldn't happen + input_seq = x_t[:, cache_length:] + # If cache length is already equal to or exceeds sequence length, exit + if cache_length >= x_t.shape[1]: + print(f"Cache length ({cache_length}) >= sequence length ({x_t.shape[1]}) at step {step}. Exiting generation loop.") + raise Exception("Cache length >= sequence length") + else: + # No active blocks, but might have blocks to cache in next iteration + break + + # Check if input_seq is empty + if input_seq.shape[1] == 0: + print(f"Warning: input_seq is empty at step {step}. Breaking generation loop.") + raise Exception("input_seq is empty") + + # Extract attention mask for current input from the pre-generated full mask + input_length = input_seq.shape[1] + attention_mask = extract_attention_mask( + full_mask=full_attention_mask, + start_pos=process_start_pos, + input_length=input_length, + cache_length=cache_length + ) + + # Forward pass + outputs = self.model( + input_seq, + attention_mask=attention_mask, + past_key_values=past_key_values, + use_cache=True, + update_kvcache=update_kvcache, + ) + + # If needed, update cache + if update_kvcache > 0: + # Store logits of the last position for next token prediction + cache_end_idx = update_kvcache - 1 + last_logits = outputs.logits[:, cache_end_idx, :].unsqueeze(1) + + # Update cache + past_key_values = outputs.past_key_values + + # Mark blocks as cached + for block_id in blocks_to_cache: + block_states[block_id]['state'] = 'in_cache' + + # Get correctly shifted logits for prediction + logits = self._shift_logits(outputs.logits, last_logit=last_logits) + + # Process mask tokens for each active block + blocks_to_deactivate = [] + + for block_id in sorted(block_states.keys()): + if block_states[block_id]['state'] != 'active': + continue + + # Get mask positions for this block + block_start = block_states[block_id]['start_pos'] + block_end = block_states[block_id]['end_pos'] + block_mask_index = mask_index.clone() + block_mask_index[:, :block_start] = False + block_mask_index[:, block_end:] = False + + # If the current block has no masks, skip it + if block_mask_index.sum() == 0: + blocks_to_deactivate.append(block_id) + continue + + # Calculate relative position for logits + logit_offset = block_start - process_start_pos + block_rel_positions = torch.where(block_mask_index[0, block_start:block_end])[0] + + if block_rel_positions.size(0) > 0: + # Get logits for masked positions + block_mask_logits = logits[:, logit_offset + block_rel_positions, :] + + # Sample tokens + confidence, x0, initial_confidence = sample_tokens( + block_mask_logits.squeeze(0), + self.temperature, + top_p=self.top_p, + top_k=self.top_k, + neg_entropy=(self.sampling_strategy == "neg_entropy"), + margin_confidence=(self.sampling_strategy == "margin_confidence") + ) + + # Apply different sampling strategies based on the block's complete/incomplete state + is_complete = block_states[block_id]['is_complete'] + + if is_complete: + # Complete state: apply confidence threshold, if no high confidence, select highest + high_conf_indices = torch.where(initial_confidence > skip_threshold)[0] + + if len(high_conf_indices) == 0: + number_transfer_tokens = 1 + _, transfer_index = torch.topk(confidence, number_transfer_tokens) + else: + transfer_index = torch.tensor([], device=self.device, dtype=torch.long) + + # Merge indices + all_indices = torch.unique(torch.cat([transfer_index, high_conf_indices])) + else: + # Incomplete state: only apply confidence threshold, if none exceed, select no tokens + high_conf_indices = torch.where(initial_confidence > skip_threshold)[0] + all_indices = high_conf_indices + + # Update tokens + if len(all_indices) > 0: + x0_ = torch.zeros_like(x0, device=self.device, dtype=torch.long) + mask_id + x0_[all_indices] = x0[all_indices].clone() + + # Map indices back to original positions + for i, idx in enumerate(all_indices): + abs_pos = block_start + block_rel_positions[idx] + x_t[0, abs_pos] = x0_[idx] + + # Update block state + block_states[block_id]['mask_count'] -= len(all_indices) + + # Check EOS token + eos_token_id = self.tokenizer.eos_token_id + if eos_token_id is not None: + for idx in all_indices: + if x0[idx].item() == eos_token_id: + eos_detected = True + break + + # If no masks remain in this block, deactivate it + mask_index = (x_t == mask_id) + block_mask_index = mask_index.clone() + block_mask_index[:, :block_start] = False + block_mask_index[:, block_end:] = False + if block_mask_index.sum() == 0: + blocks_to_deactivate.append(block_id) + continue + + # Deactivate completed blocks and mark them for caching in the next iteration + for block_id in blocks_to_deactivate: + if block_states[block_id]['state'] == 'active': + # Check if all preceding blocks are already non-active + can_deactivate = True + for prev_block_id in range(block_id): + if prev_block_id in block_states and block_states[prev_block_id]['state'] == 'active': + can_deactivate = False + break + + # Only mark the current block as 'to_cache' if all preceding blocks are non-active + if can_deactivate: + block_states[block_id]['state'] = 'to_cache' + current_blocks -= 1 + # If there are active blocks before, keep current block as active (do nothing) + + # Safety check + if step > 10000: + print(f"WARNING: Hit safety check at step {step}. Exiting generation loop.") + break + + # First, calculate non-EOS tokens for the full generated sequence + generated_sequence = x_t[0, prompt.shape[1]:].tolist() + non_eos_tokens = self._count_non_eos_tokens_before_truncation( + x_t[0].tolist(), prompt.shape[1] + ) + + # Accumulate to total tokens + if not hasattr(self, 'total_generated_tokens'): + self.total_generated_tokens = 0 + self.total_generated_tokens += non_eos_tokens + + # Generate EOS-truncated response text (consistent with other file logic) + response = self.tokenizer.decode(generated_sequence).split(self.tokenizer.eos_token)[0] + + return response + + def _update_block_completion_states(self, block_states, decoded_token_threshold): + """ + Updates the complete/incomplete state of blocks. + Iterates through blocks from front to back. If a block's decoded token count + is greater than the threshold, the next block to its right (if it exists) + is set to a complete state. + """ + for block_id in sorted(block_states.keys()): + # if block_id == 0: # Skip prompt block + # continue + + # Calculate decoded tokens for the current block + decoded_tokens = block_states[block_id]['total_masks'] - block_states[block_id]['mask_count'] + decode_ratio = decoded_tokens / block_states[block_id]['total_masks'] + # If the current block's decoded token count is greater than the threshold, + # then the next block (if it exists) is set to a complete state. + # print("decode_ratio",decode_ratio) + # print("decoded_token_threshold",decoded_token_threshold) + if decode_ratio >= decoded_token_threshold: + next_block_id = block_id + 1 + if next_block_id in block_states: + block_states[next_block_id]['is_complete'] = True + + def _shift_logits(self, logits, last_logit=None, block_size=None): + """Shifts logits to the right by one position, for autoregressive generation""" + # Check if logits are empty + if logits.shape[1] == 0: + print("Warning: logits sequence length is 0, returning empty logits") + raise Exception("logits sequence length is 0") + + shifted_logits = torch.zeros_like(logits) + shifted_logits[:, 1:, :] = logits[:, :-1, :] + if last_logit is not None: + shifted_logits[:, 0, :] = last_logit + return shifted_logits + shifted_logits[:, 0, :] = 1.0 + return shifted_logits + + def generate_until(self, requests: List[Instance], disable_tqdm: bool = False): + res = [] + + # Initialize statistics counters + if not hasattr(self, 'total_generated_tokens'): + self.total_generated_tokens = 0 + num_tokens = 0 + num_nfe = 0 # Number of Forward Evaluations + + pbar = tqdm( + total=len(requests), + disable=(disable_tqdm or (self.rank != 0)), + desc="Running generate_until requests", + ) + + start_time = time.time() + + for batch_idx in range(0, len(requests), self.batch_size): + batch_requests = requests[batch_idx : batch_idx + self.batch_size] + contexts, gen_args = zip(*[req.arguments for req in batch_requests]) + responses = self._generate_batch(contexts) + if not self.escape_until: + for i, r in enumerate(responses): + for s in gen_args[0]['until']: + r = r.split(s)[0] + responses[i] = r + + res.extend(responses) + pbar.update(len(contexts)) + + end_time = time.time() + total_time = end_time - start_time + + # Accumulate statistics + num_tokens = self.total_generated_tokens + num_nfe = self.diffusion_steps * len(requests) # Estimate NFE + + # Save final statistics + final_stats = { + 'processed_samples': len(requests), + 'total_samples': len(requests), + 'total_tokens': num_tokens, + 'total_nfe': num_nfe, + 'total_time': total_time, + 'tokens_per_second': num_tokens / total_time if total_time > 0 else 0, + 'nfe_per_token': num_nfe / num_tokens if num_tokens > 0 else 0, + 'timestamp': time.strftime('%Y-%m-%d %H:%M:%S') + } + + # Save statistics to file + if self.save_dir is not None: + import os + os.makedirs(self.save_dir, exist_ok=True) + + # Save response results + save_path = os.path.join(self.save_dir, f'rank_{self.rank}_responses.jsonl') + with open(save_path, 'w', encoding='utf-8') as f: + for r in res: + f.write(json.dumps(r, ensure_ascii=False) + '\n') + + # Save statistics results + stats_path = os.path.join(self.save_dir, f'rank_{self.rank}_final_stats.json') + with open(stats_path, 'w', encoding='utf-8') as f: + json.dump(final_stats, f, ensure_ascii=False, indent=2) + + # Print final statistics + print("\n" + "="*60) + print("=== Final Statistics ===") + print("="*60) + print(f"Processed Samples: {final_stats['processed_samples']}") + print(f"Total Samples: {final_stats['total_samples']}") + print(f"Total Tokens: {final_stats['total_tokens']}") + print(f"Total NFE: {final_stats['total_nfe']}") + print(f"Total Time: {final_stats['total_time']:.4f}s") + print(f"Tokens/Second: {final_stats['tokens_per_second']:.2f}") + print(f"NFE/Token: {final_stats['nfe_per_token']:.4f}") + print(f"Completion Time: {final_stats['timestamp']}") + print("="*60) + + return res + + def _forward_process(self, batch): + b, l = batch.shape + # sample from U[0, 1] following https://arxiv.org/pdf/2107.00630 I.1 + u0 = torch.rand(1, device=batch.device, dtype=torch.float32) + indices = torch.arange(b, device=batch.device).float() + t = (u0 + indices / b) % 1 + + p_mask = (1 - self.sampling_eps) * t + self.sampling_eps + + p_mask = p_mask[:, None].repeat(1, l) + + mask_indices = torch.rand((b, l), device=batch.device) < p_mask + # always unmask bos and eos + mask_indices[:, 0] = False + mask_indices[:, -1] = False + + noisy_batch = torch.where(mask_indices, self.mask_token_id, batch) + return noisy_batch, p_mask + + @torch.no_grad() + def get_logits(self, batch, prompt_index): + ''' + prompt_index : 1D bool tensor, length=batch.shape[1] + ''' + if self.classifier_free_guidance > 1.: + assert len(prompt_index) == batch.shape[1] + prompt_index = prompt_index.unsqueeze(0).repeat(batch.shape[0], 1) + un_batch = batch.clone() + un_batch[prompt_index] = self.mask_token_id + batch = torch.cat([batch, un_batch]) + + input = batch + + with torch.amp.autocast('cuda', dtype=torch.bfloat16): + logits = self.model(input).logits + # since bos always unmask, the first logits will not be used + logits = torch.cat([logits[:,:1], logits[:, :-1]], dim=1) + + if self.classifier_free_guidance > 1.: + logits, un_logits = torch.chunk(logits, 2, dim=0) + logits = un_logits + self.cfg * (logits - un_logits) + return logits[:, :batch.shape[1]] + + @torch.no_grad() + def _eval_target_nll_mc(self, prefix, target): + if prefix is None: + seq = target[None, :] + else: + seq = torch.concatenate([prefix, target])[None, :] + seq = seq.repeat((self.batch_size, 1)).to(self.device) + + if self.log_type == 'ftb': + prompt_index = torch.arange(seq.shape[1], device=self.device) < len(prefix) + else: + prompt_index = torch.arange(seq.shape[1], device=self.device) >= len(prefix) + + loss_acc = [] + for _ in range(max(self.mc_num // self.batch_size, 1)): + perturbed_seq = seq.clone() + # eval_logger.info("before noising") + perturbed_seq_, p_mask = self._forward_process(seq) + # eval_logger.info("end noising") + if self.log_type == 'ftb': + perturbed_seq[:, -len(target):] = perturbed_seq_[:, -len(target):] + elif self.log_type == 'btf': + perturbed_seq[:, :len(prefix)] = perturbed_seq_[:, :len(prefix)] + elif self.log_type == 'union': + perturbed_seq = perturbed_seq_ + else: + raise NotImplementedError(self.log_type) + + mask_indices = perturbed_seq == self.mask_token_id + logits = self.get_logits(perturbed_seq, prompt_index) + loss = F.cross_entropy(logits[mask_indices], seq[mask_indices], reduction='none') / p_mask[mask_indices] + loss = loss.sum() / self.batch_size + loss_acc.append(loss.item()) + + return sum(loss_acc) / len(loss_acc) + + @torch.no_grad() + def _eval_target_nll_ar(self, prefix, target): + prefix, target = prefix.unsqueeze(0), target.unsqueeze(0) # 1*l1, 1*l2 + assert self.log_type in ['ftb', 'btf'] + assert self.nll_type in ['ar_ftb', 'ar_btf'] + + if self.log_type == 'ftb': + prompt_index = torch.arange(prefix.shape[1] + target.shape[1], device=self.device) < prefix.shape[1] + else: + prompt_index = torch.arange(prefix.shape[1] + target.shape[1], device=self.device) >= prefix.shape[1] + + if self.log_type == 'ftb': + perturbed_ = target.repeat(target.shape[1], 1).clone().contiguous() # l2*l2 + else: + perturbed_ = prefix.repeat(prefix.shape[1], 1).clone().contiguous() # l1*l1 + + mask_index = torch.ones((perturbed_.shape[1], perturbed_.shape[1]), dtype=torch.bool) + if self.nll_type == 'ar_ftb': + mask_index = torch.triu(mask_index) + else: + mask_index = torch.tril(mask_index) + perturbed_[mask_index] = self.mask_token_id + if self.log_type == 'ftb': + perturbed_seq = torch.cat([prefix.repeat(perturbed_.shape[0], 1), perturbed_], dim=-1) + else: + perturbed_seq = torch.cat([perturbed_, target.repeat(perturbed_.shape[0], 1)], dim=-1) + + logits_ = [] + num = len(perturbed_seq) // self.batch_size if len(perturbed_seq) % self.batch_size == 0 else len(perturbed_seq) // self.batch_size + 1 + for i in range(num): + end = (i + 1) * self.batch_size if (i + 1) * self.batch_size < len(perturbed_seq) else len(perturbed_seq) + perturbed_seq_ = perturbed_seq[i * self.batch_size: end] + perturbed_seq_ = perturbed_seq_.to(self.device) + if len(perturbed_seq_.shape) == 1: + perturbed_seq_ = perturbed_seq_.unsqueeze(0) + logits = self.get_logits(perturbed_seq_, prompt_index) + logits_.append(logits.cpu()) + logits = torch.cat(logits_, dim=0) + + temp_index = torch.ones((perturbed_.shape[1], perturbed_.shape[1]), dtype=torch.bool) + if self.nll_type == 'ar_ftb': + temp_index = torch.triu(temp_index, diagonal=1) + else: + temp_index = torch.tril(temp_index, diagonal=-1) + mask_index[temp_index] = False + if self.log_type == 'ftb': + logits_index = torch.cat([torch.zeros((perturbed_.shape[1], prefix.shape[1]), dtype=torch.bool), mask_index], dim=-1) + else: + logits_index = torch.cat([mask_index, torch.zeros((perturbed_.shape[1], target.shape[1]), dtype=torch.bool)], dim=-1) + + if self.log_type == 'ftb': + loss = F.cross_entropy(logits[logits_index], target[0], reduction='sum').cpu().item() + else: + loss = F.cross_entropy(logits[logits_index], prefix[0], reduction='sum').cpu().item() + return loss + + def _encode_pair(self, context, continuation): + if self.add_bos_token: + context = self.tokenizer.bos_token + context + + n_spaces = len(context) - len(context.rstrip()) + if n_spaces > 0: + continuation = context[-n_spaces:] + continuation + context = context[:-n_spaces] + + whole_enc = self.tokenizer.encode(context + continuation) + [self.tokenizer.eos_token_id] + context_enc = self.tokenizer.encode(context) + + context_enc_len = len(context_enc) + continuation_enc = whole_enc[context_enc_len:] + + # by default truncate on the left + cutoff_length = max(len(whole_enc) - self.max_length, 0) + if cutoff_length > 0: + eval_logger.warning(f"Text length {len(whole_enc)} is larger than {self.max_length}, cutoff on the left side") + context_remain = context_enc_len-cutoff_length + if context_remain > 0: + context_enc = context_enc[-context_remain:] + else: + eval_logger.warning(f"All context (prompt) is truncated.") + context_enc = "" + continuation_enc = whole_enc[-self.max_length:] + return context_enc, continuation_enc + + def loglikelihood(self, requests: List[Instance]) -> List[Tuple[float, bool]]: + def _tokenize(e): + prefix, target = self._encode_pair(e["prefix"], e["target"]) + return { + "prefix_text": e["prefix"], + "target_text": e["target"], + "prefix": prefix, + "target": target, + } + + ds = [] + ds = [{"prefix": req.args[0], "target": req.args[1]} for req in requests] + ds = Dataset.from_list(ds) + print(ds[0]) + ds = ds.map(_tokenize) + ds = ds.with_format("torch") + + out = [] + with torch.no_grad(): + for elem in tqdm(ds, desc="Computing likelihood..."): + prefix = elem["prefix"] + target = elem["target"] + # likelihood calculations are modified from https://github.com/ML-GSAI/SMDM/blob/main/evaluate_diff.py + if self.nll_type == 'mc': + ll = -self._eval_target_nll_mc(prefix, target) + if self.log_type == 'union': + ll = ll / (len(target) + len(prefix)) + elif self.nll_type == 'ar_ftb' or self.nll_type == 'ar_btf': + ll = -self._eval_target_nll_ar(prefix, target) + else: + raise NotImplementedError(self.nll_type) + + # TODO: greedy decoding + is_target_greedy_dec = False + + out.append((ll, 1.0 if is_target_greedy_dec else 0.0)) + return out + + def loglikelihood_rolling(self, requests: List[Instance]) -> List[float]: + raise NotImplementedError + + +if __name__ == "__main__": + set_seed(1234) + cli_evaluate() \ No newline at end of file diff --git a/Discrete-Diffusion-Forcing/D2F-eval/eval_dream.sh b/Discrete-Diffusion-Forcing/D2F-eval/eval_dream.sh new file mode 100644 index 0000000000000000000000000000000000000000..6e7f0794e8637013026af6896500a9eb7d622a0d --- /dev/null +++ b/Discrete-Diffusion-Forcing/D2F-eval/eval_dream.sh @@ -0,0 +1,158 @@ +tasks="gsm8k_cot mbpp minerva_math" +nshots="8 3 4" +lengths="256 256 256" +temperatures="0 0 0" +limits="10000 10000 10000" +block_sizes="32 48 64" +block_add_thresholds="0.1 0.1 0.1" +decoded_token_thresholds="0.95 0.95 0.95" +skip_thresholds="0.9 0.9 0.9" +top_ps="none none none" +dtypes="bfloat16 bfloat16 bfloat16" +sampling_strategies="default default default" + + + +humaneval_nshots="0" +humaneval_lengths="256" +humaneval_temperatures="0" +humaneval_limits="10000" +humaneval_diffusion_steps="256" +humaneval_block_sizes="32" +humaneval_block_add_thresholds="0.9" +humaneval_decoded_token_thresholds="0.95" +humaneval_skip_thresholds="0.95" +humaneval_top_ps="none" +humaneval_dtypes="bfloat16" +humaneval_sampling_strategies="default" + + + +base_model=Dream-org/Dream-v0-Base-7B + + +lora_models=( + "SJTU-Deng-Lab/D2F_Dream_Base_7B_Lora" +) + + +read -ra TASKS_ARRAY <<< "$tasks" +read -ra NSHOTS_ARRAY <<< "$nshots" +read -ra LENGTH_ARRAY <<< "$lengths" +read -ra TEMP_ARRAY <<< "$temperatures" +read -ra LIMITS_ARRAY <<< "$limits" +read -ra BLOCK_SIZES_ARRAY <<< "$block_sizes" +read -ra BLOCK_ADD_THRESHOLDS_ARRAY <<< "$block_add_thresholds" +read -ra DECODED_TOKEN_THRESHOLDS_ARRAY <<< "$decoded_token_thresholds" +read -ra SKIP_THRESHOLDS_ARRAY <<< "$skip_thresholds" +read -ra TOP_PS_ARRAY <<< "$top_ps" +read -ra DTYPES_ARRAY <<< "$dtypes" +read -ra SAMPLING_STRATEGIES_ARRAY <<< "$sampling_strategies" + + +read -ra HUMANEVAL_NSHOTS_ARRAY <<< "$humaneval_nshots" +read -ra HUMANEVAL_LENGTHS_ARRAY <<< "$humaneval_lengths" +read -ra HUMANEVAL_TEMP_ARRAY <<< "$humaneval_temperatures" +read -ra HUMANEVAL_LIMITS_ARRAY <<< "$humaneval_limits" +read -ra HUMANEVAL_DIFFUSION_STEPS_ARRAY <<< "$humaneval_diffusion_steps" +read -ra HUMANEVAL_BLOCK_SIZES_ARRAY <<< "$humaneval_block_sizes" +read -ra HUMANEVAL_BLOCK_ADD_THRESHOLDS_ARRAY <<< "$humaneval_block_add_thresholds" +read -ra HUMANEVAL_DECODED_TOKEN_THRESHOLDS_ARRAY <<< "$humaneval_decoded_token_thresholds" +read -ra HUMANEVAL_SKIP_THRESHOLDS_ARRAY <<< "$humaneval_skip_thresholds" +read -ra HUMANEVAL_TOP_PS_ARRAY <<< "$humaneval_top_ps" +read -ra HUMANEVAL_DTYPES_ARRAY <<< "$humaneval_dtypes" +read -ra HUMANEVAL_SAMPLING_STRATEGIES_ARRAY <<< "$humaneval_sampling_strategies" + + +array_length=${#TASKS_ARRAY[@]} +if [[ ${#NSHOTS_ARRAY[@]} -ne $array_length ]] || \ + [[ ${#LENGTH_ARRAY[@]} -ne $array_length ]] || \ + [[ ${#TEMP_ARRAY[@]} -ne $array_length ]] || \ + [[ ${#LIMITS_ARRAY[@]} -ne $array_length ]] || \ + [[ ${#BLOCK_SIZES_ARRAY[@]} -ne $array_length ]] || \ + [[ ${#BLOCK_ADD_THRESHOLDS_ARRAY[@]} -ne $array_length ]] || \ + [[ ${#DECODED_TOKEN_THRESHOLDS_ARRAY[@]} -ne $array_length ]] || \ + [[ ${#SKIP_THRESHOLDS_ARRAY[@]} -ne $array_length ]] || \ + [[ ${#TOP_PS_ARRAY[@]} -ne $array_length ]] || \ + [[ ${#SAMPLING_STRATEGIES_ARRAY[@]} -ne $array_length ]] || \ + [[ ${#DTYPES_ARRAY[@]} -ne $array_length ]]; then + echo "Error: All configuration arrays must have the same length!" + echo "Tasks: ${#TASKS_ARRAY[@]}, Nshots: ${#NSHOTS_ARRAY[@]}, Lengths: ${#LENGTH_ARRAY[@]}, Temperatures: ${#TEMP_ARRAY[@]}, Limits: ${#LIMITS_ARRAY[@]}, Block sizes: ${#BLOCK_SIZES_ARRAY[@]}, Block thresholds: ${#BLOCK_ADD_THRESHOLDS_ARRAY[@]}, Decoded token thresholds: ${#DECODED_TOKEN_THRESHOLDS_ARRAY[@]}, Skip thresholds: ${#SKIP_THRESHOLDS_ARRAY[@]}, Top_ps: ${#TOP_PS_ARRAY[@]}, Sampling strategies: ${#SAMPLING_STRATEGIES_ARRAY[@]}, Dtypes: ${#DTYPES_ARRAY[@]}" + exit 1 +fi + + +humaneval_array_length=${#HUMANEVAL_NSHOTS_ARRAY[@]} +if [[ ${#HUMANEVAL_LENGTHS_ARRAY[@]} -ne $humaneval_array_length ]] || \ + [[ ${#HUMANEVAL_TEMP_ARRAY[@]} -ne $humaneval_array_length ]] || \ + [[ ${#HUMANEVAL_LIMITS_ARRAY[@]} -ne $humaneval_array_length ]] || \ + [[ ${#HUMANEVAL_DIFFUSION_STEPS_ARRAY[@]} -ne $humaneval_array_length ]] || \ + [[ ${#HUMANEVAL_BLOCK_SIZES_ARRAY[@]} -ne $humaneval_array_length ]] || \ + [[ ${#HUMANEVAL_BLOCK_ADD_THRESHOLDS_ARRAY[@]} -ne $humaneval_array_length ]] || \ + [[ ${#HUMANEVAL_DECODED_TOKEN_THRESHOLDS_ARRAY[@]} -ne $humaneval_array_length ]] || \ + [[ ${#HUMANEVAL_SKIP_THRESHOLDS_ARRAY[@]} -ne $humaneval_array_length ]] || \ + [[ ${#HUMANEVAL_TOP_PS_ARRAY[@]} -ne $humaneval_array_length ]] || \ + [[ ${#HUMANEVAL_DTYPES_ARRAY[@]} -ne $humaneval_array_length ]] || \ + [[ ${#HUMANEVAL_SAMPLING_STRATEGIES_ARRAY[@]} -ne $humaneval_array_length ]]; then + echo "Error: All HumanEval configuration arrays must have the same length!" + echo "HumanEval Nshots: ${#HUMANEVAL_NSHOTS_ARRAY[@]}, Lengths: ${#HUMANEVAL_LENGTHS_ARRAY[@]}, Temperatures: ${#HUMANEVAL_TEMP_ARRAY[@]}, Limits: ${#HUMANEVAL_LIMITS_ARRAY[@]}, Diffusion steps: ${#HUMANEVAL_DIFFUSION_STEPS_ARRAY[@]}, Block sizes: ${#HUMANEVAL_BLOCK_SIZES_ARRAY[@]}, Block thresholds: ${#HUMANEVAL_BLOCK_ADD_THRESHOLDS_ARRAY[@]}, Decoded token thresholds: ${#HUMANEVAL_DECODED_TOKEN_THRESHOLDS_ARRAY[@]}, Skip thresholds: ${#HUMANEVAL_SKIP_THRESHOLDS_ARRAY[@]}, Top_ps: ${#HUMANEVAL_TOP_PS_ARRAY[@]}, Dtypes: ${#HUMANEVAL_DTYPES_ARRAY[@]}, Sampling strategies: ${#HUMANEVAL_SAMPLING_STRATEGIES_ARRAY[@]}" + exit 1 +fi + +export HF_ALLOW_CODE_EVAL=1 +for lora_model in "${lora_models[@]}"; do + lora_model_name="$lora_model" + echo "====================================================================" + echo "Evaluating LoRA model: $lora_model_name" + echo "====================================================================" + + + + for i in "${!HUMANEVAL_NSHOTS_ARRAY[@]}"; do + output_path="evals_dream${lora_model_name}/humaneval-ns${HUMANEVAL_NSHOTS_ARRAY[$i]}-len${HUMANEVAL_LENGTHS_ARRAY[$i]}-temp${HUMANEVAL_TEMP_ARRAY[$i]}-limit${HUMANEVAL_LIMITS_ARRAY[$i]}-diffsteps${HUMANEVAL_DIFFUSION_STEPS_ARRAY[$i]}-block${HUMANEVAL_BLOCK_SIZES_ARRAY[$i]}-thresh${HUMANEVAL_BLOCK_ADD_THRESHOLDS_ARRAY[$i]}-decodethresh${HUMANEVAL_DECODED_TOKEN_THRESHOLDS_ARRAY[$i]}-skip${HUMANEVAL_SKIP_THRESHOLDS_ARRAY[$i]}-topp${HUMANEVAL_TOP_PS_ARRAY[$i]}-dtype${HUMANEVAL_DTYPES_ARRAY[$i]}-sampling${HUMANEVAL_SAMPLING_STRATEGIES_ARRAY[$i]}" + echo "Running HumanEval evaluation $((i+1))/${humaneval_array_length} for $lora_model_name..." + echo "HumanEval Config: Shots: ${HUMANEVAL_NSHOTS_ARRAY[$i]}, Length: ${HUMANEVAL_LENGTHS_ARRAY[$i]}, Temperature: ${HUMANEVAL_TEMP_ARRAY[$i]}, Limit: ${HUMANEVAL_LIMITS_ARRAY[$i]}, Diffusion Steps: ${HUMANEVAL_DIFFUSION_STEPS_ARRAY[$i]}, Block Size: ${HUMANEVAL_BLOCK_SIZES_ARRAY[$i]}, Block Add Threshold: ${HUMANEVAL_BLOCK_ADD_THRESHOLDS_ARRAY[$i]}, Decoded Token Threshold: ${HUMANEVAL_DECODED_TOKEN_THRESHOLDS_ARRAY[$i]}, Skip Threshold: ${HUMANEVAL_SKIP_THRESHOLDS_ARRAY[$i]}, Top_p: ${HUMANEVAL_TOP_PS_ARRAY[$i]}, Sampling Strategy: ${HUMANEVAL_SAMPLING_STRATEGIES_ARRAY[$i]}, Dtype: ${HUMANEVAL_DTYPES_ARRAY[$i]}; Output: $output_path" + + if [[ "${HUMANEVAL_TOP_PS_ARRAY[$i]}" == "none" ]]; then + humaneval_model_args="pretrained=${base_model},lora_path=${lora_model},max_new_tokens=${HUMANEVAL_LENGTHS_ARRAY[$i]},diffusion_steps=${HUMANEVAL_DIFFUSION_STEPS_ARRAY[$i]},temperature=${HUMANEVAL_TEMP_ARRAY[$i]},add_bos_token=true,escape_until=true,block_size=${HUMANEVAL_BLOCK_SIZES_ARRAY[$i]},block_add_threshold=${HUMANEVAL_BLOCK_ADD_THRESHOLDS_ARRAY[$i]},skip_threshold=${HUMANEVAL_SKIP_THRESHOLDS_ARRAY[$i]},decoded_token_threshold=${HUMANEVAL_DECODED_TOKEN_THRESHOLDS_ARRAY[$i]},dtype=${HUMANEVAL_DTYPES_ARRAY[$i]},sampling_strategy=${HUMANEVAL_SAMPLING_STRATEGIES_ARRAY[$i]},save_dir=${output_path}" + else + humaneval_model_args="pretrained=${base_model},lora_path=${lora_model},max_new_tokens=${HUMANEVAL_LENGTHS_ARRAY[$i]},diffusion_steps=${HUMANEVAL_DIFFUSION_STEPS_ARRAY[$i]},temperature=${HUMANEVAL_TEMP_ARRAY[$i]},top_p=${HUMANEVAL_TOP_PS_ARRAY[$i]},add_bos_token=true,escape_until=true,block_size=${HUMANEVAL_BLOCK_SIZES_ARRAY[$i]},block_add_threshold=${HUMANEVAL_BLOCK_ADD_THRESHOLDS_ARRAY[$i]},skip_threshold=${HUMANEVAL_SKIP_THRESHOLDS_ARRAY[$i]},decoded_token_threshold=${HUMANEVAL_DECODED_TOKEN_THRESHOLDS_ARRAY[$i]},dtype=${HUMANEVAL_DTYPES_ARRAY[$i]},sampling_strategy=${HUMANEVAL_SAMPLING_STRATEGIES_ARRAY[$i]},save_dir=${output_path}" + fi + + CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 accelerate launch --main_process_port 29520 --num_processes 8 eval_dream.py --model dream_lora \ + --model_args $humaneval_model_args \ + --tasks humaneval \ + --num_fewshot ${HUMANEVAL_NSHOTS_ARRAY[$i]} \ + --batch_size 1 \ + --output_path $output_path \ + --log_samples \ + --confirm_run_unsafe_code + done + + ### NOTICE: use postprocess for humaneval + # python postprocess_code.py {the samples_xxx.jsonl file under output_path} + + + for i in "${!TASKS_ARRAY[@]}"; do + output_path="evals_dream${lora_model_name}/${TASKS_ARRAY[$i]}-ns${NSHOTS_ARRAY[$i]}-len${LENGTH_ARRAY[$i]}-temp${TEMP_ARRAY[$i]}-limit${LIMITS_ARRAY[$i]}-diffsteps${LENGTH_ARRAY[$i]}-block${BLOCK_SIZES_ARRAY[$i]}-thresh${BLOCK_ADD_THRESHOLDS_ARRAY[$i]}-decodethresh${DECODED_TOKEN_THRESHOLDS_ARRAY[$i]}-skip${SKIP_THRESHOLDS_ARRAY[$i]}-topp${TOP_PS_ARRAY[$i]}-dtype${DTYPES_ARRAY[$i]}-sampling${SAMPLING_STRATEGIES_ARRAY[$i]}" + echo "Task: ${TASKS_ARRAY[$i]}, Shots: ${NSHOTS_ARRAY[$i]}, Length: ${LENGTH_ARRAY[$i]}, Temperature: ${TEMP_ARRAY[$i]}, Limit: ${LIMITS_ARRAY[$i]}, Block Size: ${BLOCK_SIZES_ARRAY[$i]}, Block Add Threshold: ${BLOCK_ADD_THRESHOLDS_ARRAY[$i]}, Decoded Token Threshold: ${DECODED_TOKEN_THRESHOLDS_ARRAY[$i]}, Skip Threshold: ${SKIP_THRESHOLDS_ARRAY[$i]}, Top_p: ${TOP_PS_ARRAY[$i]}, Sampling Strategy: ${SAMPLING_STRATEGIES_ARRAY[$i]}, Dtype: ${DTYPES_ARRAY[$i]}; Output: $output_path" + + if [[ "${TOP_PS_ARRAY[$i]}" == "none" ]]; then + model_args="pretrained=${base_model},lora_path=${lora_model},max_new_tokens=${LENGTH_ARRAY[$i]},diffusion_steps=${LENGTH_ARRAY[$i]},add_bos_token=true,temperature=${TEMP_ARRAY[$i]},block_size=${BLOCK_SIZES_ARRAY[$i]},block_add_threshold=${BLOCK_ADD_THRESHOLDS_ARRAY[$i]},skip_threshold=${SKIP_THRESHOLDS_ARRAY[$i]},decoded_token_threshold=${DECODED_TOKEN_THRESHOLDS_ARRAY[$i]},dtype=${DTYPES_ARRAY[$i]},sampling_strategy=${SAMPLING_STRATEGIES_ARRAY[$i]},save_dir=${output_path}" + else + model_args="pretrained=${base_model},lora_path=${lora_model},max_new_tokens=${LENGTH_ARRAY[$i]},diffusion_steps=${LENGTH_ARRAY[$i]},add_bos_token=true,temperature=${TEMP_ARRAY[$i]},top_p=${TOP_PS_ARRAY[$i]},block_size=${BLOCK_SIZES_ARRAY[$i]},block_add_threshold=${BLOCK_ADD_THRESHOLDS_ARRAY[$i]},skip_threshold=${SKIP_THRESHOLDS_ARRAY[$i]},decoded_token_threshold=${DECODED_TOKEN_THRESHOLDS_ARRAY[$i]},dtype=${DTYPES_ARRAY[$i]},sampling_strategy=${SAMPLING_STRATEGIES_ARRAY[$i]},save_dir=${output_path}" + fi + + CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 accelerate launch --main_process_port 29520 --num_processes 8 eval_dream.py --model dream_lora \ + --model_args $model_args \ + --tasks ${TASKS_ARRAY[$i]} \ + --limit ${LIMITS_ARRAY[$i]} \ + --num_fewshot ${NSHOTS_ARRAY[$i]} \ + --batch_size 1 \ + --output_path $output_path \ + --log_samples \ + --confirm_run_unsafe_code + done +done + +echo "All evaluations completed!" \ No newline at end of file diff --git a/Discrete-Diffusion-Forcing/D2F-eval/eval_dream_d2f_vllm.py b/Discrete-Diffusion-Forcing/D2F-eval/eval_dream_d2f_vllm.py new file mode 100644 index 0000000000000000000000000000000000000000..1a2402a5d773a5f524441b8e8223b89fc1e4d5ed --- /dev/null +++ b/Discrete-Diffusion-Forcing/D2F-eval/eval_dream_d2f_vllm.py @@ -0,0 +1,764 @@ +import logging +import gc +import time +import json +from datetime import timedelta +from typing import List, Optional, Tuple, Type, TypeVar, Union +import torch +import torch.nn.functional as F +import torch.distributions as dists +import transformers +from accelerate import ( + Accelerator, + InitProcessGroupKwargs, +) +from datasets import Dataset +from packaging import version +from tqdm import tqdm +from peft import PeftConfig, PeftModel +import numpy as np + +from lm_eval import utils +from lm_eval.api.instance import Instance +from lm_eval.api.model import LM +from lm_eval.api.registry import register_model +from lm_eval.models.utils import get_dtype +from lm_eval.__main__ import cli_evaluate + +eval_logger = logging.getLogger(__name__) +T = TypeVar("T", bound="LM") +import random +def set_seed(seed): + torch.manual_seed(seed) + random.seed(seed) + np.random.seed(seed) + + torch.backends.cudnn.deterministic = True + torch.backends.cudnn.benchmark = False + +def shift_logits(logits): + shifted_logits = torch.zeros_like(logits) + shifted_logits[:, 1:, :] = logits[:, :-1, :] + shifted_logits[:, 0, :] = 1.0 + return shifted_logits + +def create_full_block_attention_mask(prompt_length, max_length, block_size, device=None, dtype=None): + """ + Creates a complete attention mask for the entire sequence with block-based causal attention. + + Args: + prompt_length: Length of the prompt (first irregular block) + max_length: Maximum total sequence length + block_size: Size of each regular block + device: Device to create tensor on + dtype: Data type for the attention mask + + Returns: + attention_mask: Tensor of shape [1, 1, max_length, max_length] + """ + # Use the provided dtype or default to bfloat16 + if dtype is None: + dtype = torch.bfloat16 + + # Initialize mask with -inf (no attention) + attention_mask = torch.full((1, 1, max_length, max_length), -torch.inf, device=device, dtype=dtype) + + # Block 0: Prompt (can see itself) + attention_mask[:, :, :prompt_length, :prompt_length] = 0 + + # Calculate the number of regular blocks after prompt + remaining_length = max_length - prompt_length + num_blocks = (remaining_length + block_size - 1) // block_size + + # Process each regular block + for b in range(num_blocks): + block_start = prompt_length + b * block_size + block_end = min(prompt_length + (b + 1) * block_size, max_length) + + # Current block can see the prompt + attention_mask[:, :, block_start:block_end, :prompt_length] = 0 + + # Current block can see all previous regular blocks + for prev_b in range(b): + prev_start = prompt_length + prev_b * block_size + prev_end = min(prompt_length + (prev_b + 1) * block_size, max_length) + attention_mask[:, :, block_start:block_end, prev_start:prev_end] = 0 + + # Current block can see itself (full attention within block) + attention_mask[:, :, block_start:block_end, block_start:block_end] = 0 + + return attention_mask + +def extract_attention_mask(full_mask, start_pos, input_length, cache_length): + """ + Extract the relevant portion of attention mask for current forward pass. + + Args: + full_mask: Complete attention mask [1, 1, max_length, max_length] + start_pos: Starting position in the full sequence + input_length: Length of current input sequence + cache_length: Length of cached sequence + + Returns: + attention_mask: Extracted mask [1, 1, input_length, cache_length + input_length] + """ + end_pos = start_pos + input_length + total_length = cache_length + input_length + + # Extract the relevant rows (current input positions) + # and columns (cache + current input positions) + extracted_mask = torch.full((1, 1, input_length, total_length), -torch.inf, + device=full_mask.device, dtype=full_mask.dtype) + + # Copy cache columns (0 to cache_length in the extracted mask corresponds to 0 to cache_length in full mask) + extracted_mask[:, :, :, :cache_length] = full_mask[:, :, start_pos:end_pos, :cache_length] + + # Copy current input columns + extracted_mask[:, :, :, cache_length:] = full_mask[:, :, start_pos:end_pos, start_pos:end_pos] + + return extracted_mask + +def build_custom_float_attention_mask(input_ids, prompt_length, block_size, device=None, dtype=None): + B, seq_len = input_ids.shape + # Use the provided dtype or default to float32 + if dtype is None: + dtype = torch.float32 + # Initialize to all -inf + attn_mask = torch.full((B, 1, seq_len, seq_len), float('-inf'), dtype=dtype, device=device) + # 1. Prompt part: each token can attend to the entire prompt + for i in range(B): + attn_mask[i, :, :, :prompt_length[i]] = 0.0 # Allow all tokens to see the prompt + + # 2. Block division: divide into blocks starting from prompt_length + num_blocks = (seq_len - prompt_length[i] + block_size - 1) // block_size + + for b in range(num_blocks): + block_start = prompt_length[i] + b * block_size + block_end = min(block_start + block_size, seq_len) + + # Full attention within the block + attn_mask[i, :, block_start:block_end, block_start:block_end] = 0.0 + + # Causal attention between blocks (can only see previous blocks) + for prev_b in range(b): + prev_start = prompt_length[i] + prev_b * block_size + prev_end = min(prev_start + block_size, seq_len) + + # Current block can see previous blocks + attn_mask[i, :, block_start:block_end, prev_start:prev_end] = 0.0 + + return attn_mask + +def top_p_logits(logits, top_p=None): + sorted_logits, sorted_indices = torch.sort(logits, descending=True) + cumulative_probs = torch.cumsum(F.softmax(sorted_logits, dim=-1), dim=-1) + sorted_indices_to_remove = cumulative_probs > top_p + # Shift the indices to the right to keep the first token above the threshold + sorted_indices_to_remove[..., 1:] = sorted_indices_to_remove[..., :-1].clone() + sorted_indices_to_remove[..., 0] = 0 + + mask = torch.zeros_like(logits, dtype=torch.bool, device=logits.device) + mask = mask.scatter_(-1, sorted_indices, sorted_indices_to_remove) + logits = logits.masked_fill(mask, torch.finfo(logits.dtype).min) + return logits + +def top_k_logits(logits, top_k=None): + top_k = min(top_k, logits.size(-1)) # Safety check + # Remove all tokens with a probability less than the last token of the top-k + indices_to_remove = logits < torch.topk(logits, top_k)[0][..., -1, None] + logits = logits.masked_fill(indices_to_remove, torch.finfo(logits.dtype).min) + return logits + +def sample_tokens(logits, temperature=0.0, top_p=None, top_k=None, margin_confidence=False, neg_entropy=False): + if temperature > 0: + logits = logits / temperature + if top_p is not None and top_p < 1: + logits = top_p_logits(logits, top_p) + if top_k is not None: + logits = top_k_logits(logits, top_k) + probs = torch.softmax(logits, dim=-1) + + if temperature > 0: + try: + x0 = dists.Categorical(probs=probs).sample() + initial_confidence = torch.gather(probs, -1, x0.unsqueeze(-1)).squeeze(-1) + except: + initial_confidence, x0 = probs.max(dim=-1) + else: + initial_confidence, x0 = probs.max(dim=-1) + + # Save initial confidence + confidence = initial_confidence.clone() + + if margin_confidence: + sorted_probs, _ = torch.sort(probs, dim=-1, descending=True) + # Extract top1 and top2 probabilities + top1_probs = sorted_probs[:, 0] + top2_probs = sorted_probs[:, 1] + # Calculate confidence as top1 - top2 + confidence = top1_probs - top2_probs + + if neg_entropy: + epsilon = 1e-10 + log_probs = torch.log(probs + epsilon) + confidence = torch.sum(probs * log_probs, dim=-1) + + return confidence, x0, initial_confidence + +@register_model("dream_lora") +class DreamLoRA(LM): + def __init__( + self, + pretrained: Union[str, transformers.PreTrainedModel], + lora_path: str, + batch_size: Optional[Union[int, str]] = 1, + device: Optional[str] = "cuda", + dtype: Optional[Union[str, torch.dtype]] = "auto", + max_new_tokens: Optional[int] = 128, + max_length: Optional[int] = 2048, # Updated to match example code + add_bos_token: Optional[bool] = False, + nll_type: Optional[str] = "mc", + log_type: Optional[str] = "ftb", + mc_num: Optional[int] = 128, + classifier_free_guidance: Optional[float] = 1.0, + sampling_eps: Optional[float] = 1e-3, + diffusion_steps: Optional[int] = 128, + trust_remote_code: Optional[bool] = True, + parallelize: Optional[bool] = False, + autogptq: Optional[Union[bool, str]] = False, + temperature: Optional[float] = 0.2, # Updated default + top_p: Optional[float] = None, # Updated default + top_k: Optional[float] = None, + alg: Optional[str] = "entropy", + alg_temp: Optional[float] = 0.0, + escape_until: Optional[bool] = False, + block_size: Optional[int] = 4, # Updated to match example code + mask_token_id: Optional[int] = 151666, # Added mask_token_id parameter + block_add_threshold: Optional[float] = 0.5, # Added block_add_threshold parameter + decoded_token_threshold: Optional[int] = 0.9, # Added decoded_token_threshold parameter + skip_threshold: Optional[float] = 1.0, # Added skip_threshold parameter + sampling_strategy: Optional[str] = "default", # Added sampling_strategy parameter + save_dir: Optional[str] = None, + **kwargs, + ) -> None: + super().__init__() + + # prepare for parallelism + assert isinstance(device, str) + assert isinstance(pretrained, str) + assert isinstance(batch_size, (int, str)) + + gpus = torch.cuda.device_count() + accelerator_kwargs = InitProcessGroupKwargs(timeout=timedelta(weeks=52)) + accelerator = Accelerator(kwargs_handlers=[accelerator_kwargs]) + if accelerator.num_processes > 1: + self.accelerator = accelerator + + if "npu" in accelerator.device.type: + gpus = torch.npu.device_count() + + # using one process with no model parallelism + if not (parallelize or accelerator.num_processes > 1): + # use user-passed device + device_list = set( + ["cuda", "cpu"] + + [f"cuda:{i}" for i in range(gpus)] + + ["mps", "mps:0"] + + [f"npu:{i}" for i in range(gpus)] + ) + if device and device in device_list: + self._device = torch.device(device) + eval_logger.info(f"Using device '{device}'") + if device in ("mps", "mps:0") and version.parse( + torch.__version__ + ) < version.parse("2.1"): + raise RuntimeError( + f"mps requires torch >= 2.1. You have {torch.__version__}" + ) + else: + eval_logger.info("Device not specified") + eval_logger.info(f"Cuda Available? {torch.cuda.is_available()}") + self._device = ( + torch.device("cuda") + if torch.cuda.is_available() + else torch.device("cpu") + ) + else: # Parallelism managed by accelerate + if device != "cuda": + eval_logger.info( + f"Using `accelerate launch` or `parallelize=True`, device '{device}' will be overridden when placing model." + ) + # TODO: include in warning that `load_in_8bit` etc. affect this too + self._device = ( + self.accelerator.device + if hasattr(self, "accelerator") + else torch.device(device) + ) + + self.batch_size_per_gpu = batch_size + if isinstance(batch_size, str): + self.batch_size_per_gpu = int(batch_size) + + # Save LoRA path and block_size + self.lora_path = lora_path + self.block_size = block_size + self.block_add_threshold = block_add_threshold # New block_add_threshold attribute + self.skip_threshold = skip_threshold # New skip_threshold attribute + self.sampling_strategy = sampling_strategy # Save sampling strategy parameter + self.decoded_token_threshold = decoded_token_threshold # New decoded_token_threshold attribute + self.save_dir = save_dir + + # Add metric tracking + self.total_forward_passes = 0 + self.total_generated_tokens = 0 + self.total_prompts = 0 + # Add time and token statistics + self.total_generation_time = 0.0 + self.total_block_tokens = 0 # Number of blocks * block_size + self.total_actual_tokens = 0 # Actual generated tokens (excluding EOS) + self.total_non_eos_tokens = 0 # Total non-EOS tokens in the entire sequence + self.all_generation_times = [] + self.all_block_tokens = [] + self.all_actual_tokens = [] + self.all_non_eos_tokens = [] + + # Save target_dtype for later use + self.target_dtype = get_dtype(dtype) + + # if isinstance(pretrained, str): + # if gpus >= 1 or str(self.device) == "mps": + # # TODO: can remove this whole snippet except in the mps case, perhaps? + # if not (parallelize or autogptq or hasattr(self, "accelerator")): + # # place model onto device requested manually, + # # if not using HF Accelerate or device_map + # # or any other option that preloads model onto device + # try: + # self.model.to(self.device) + # except ValueError: + # eval_logger.debug( + # "Failed to place model onto specified device. This may be because the model is quantized via `bitsandbytes` or `device_map` is provided. If the desired GPU is being used, this message is safe to ignore." + # ) + # # multigpu data-parallel support when launched with accelerate + # if gpus > 1: + # if accelerator.num_processes > 1: + # if parallelize: + # eval_logger.warning( + # "You are both using a HF Accelerate `device_map` (`--model_args parallelize=True`) and launching via `accelerate launch`. This will attempt to do model and data parallelism depending on the resources available." + # ) + # elif gpus > accelerator.num_processes: + # eval_logger.warning( + # "WARNING: The number of total system GPUs does not match the number of spawned processes. " + # "If you would like to use data parallelism, please launch the script " + # "with 'accelerate launch *script*'. " + # f"Current run will proceed with {accelerator.num_processes} devices." + # ) + # if self.accelerator.is_local_main_process: + # eval_logger.info( + # f"Using {gpus} devices with data parallelism" + # ) + + # self._device = torch.device(f"{accelerator.device}") + # self.accelerator = accelerator + + # self._rank = self.accelerator.local_process_index + # self._world_size = self.accelerator.num_processes + # else: + # # if we aren't launching via accelerate, ditch + # self._rank = 0 + # self._world_size = 1 + # else: + # # if a PreTrainedModel was passed into HFLM, we forgo distributed setup. + # eval_logger.warning( + # "Passed an already-initialized model through `pretrained`, assuming single-process call to evaluate() or custom distributed integration" + # ) + # self._rank = 0 + # self._world_size = 1 + + self.max_length = max_length + self.add_bos_token = add_bos_token + # generation params + self.max_new_tokens = max_new_tokens + self.diffusion_steps = diffusion_steps + self.temperature = temperature + self.top_p = top_p + self.top_k = top_k + self.alg = alg + self.alg_temp = alg_temp + self.escape_until = escape_until + self.block_size = block_size + self.mask_token_id = mask_token_id + + # loglikelihood params + self.nll_type = nll_type + self.log_type = log_type + self.mc_num = mc_num + self.classifier_free_guidance = classifier_free_guidance + self.sampling_eps = sampling_eps + + self._create_model_and_tokenizer(pretrained, dtype, trust_remote_code) + + @property + def batch_size(self): + return self.batch_size_per_gpu + + @property + def device(self): + return self._device + + @property + def rank(self): + return self._rank + + @property + def world_size(self): + return self._world_size + + def _create_model_and_tokenizer(self, pretrained, dtype, trust_remote_code): + from d2f_vllm import LLM, SamplingParams + + self.LLM = LLM( + pretrained, + lora_path=self.lora_path, + use_lora=True, + model_name="dream", + model_type="diffusion_lm", + enforce_eager=True, + tensor_parallel_size=1, + gpu_memory_utilization=0.60, + max_num_batched_tokens=2048, + max_num_seqs=20, + max_model_len=1024, + accept_threshold=self.skip_threshold, + complete_threshold=self.decoded_token_threshold, + add_new_block_threshold=1-self.block_add_threshold, + kv_cache_layout="unified" + ) + self.tokenizer = self.LLM.tokenizer + self.sampling_params = SamplingParams(temperature=self.temperature, max_tokens=self.max_new_tokens) + + + def tok_decode(self, tokens, skip_special_tokens=True): + return self.tokenizer.decode(tokens, skip_special_tokens=skip_special_tokens) + + def tok_encode(self, text, add_special_tokens=True): + return self.tokenizer( + text, return_tensors="pt", add_special_tokens=add_special_tokens + ).input_ids + + @classmethod + def create_from_arg_string( + cls: Type[T], arg_string: str, additional_config: Optional[dict] = None + ) -> T: + """ + Creates an instance of the LM class using the given argument string and additional config. + + Parameters: + - arg_string: A string containing arguments in the format key1=value1,key2=value2. + - additional_config: Optional dictionary containing additional configuration parameters. + + Returns: + - Instance of the LM class. + """ + additional_config = {} if additional_config is None else additional_config + args = utils.simple_parse_args_string(arg_string) + args2 = {k: v for k, v in additional_config.items() if v is not None} + return cls(**args, **args2) + + def apply_chat_template( + self, chat_history, add_generation_prompt: bool = True + ) -> str: + """ + Method to apply a chat template to a list of chat history between user and model. + """ + chat_templated = self.tokenizer.apply_chat_template( + chat_history, + tokenize=False, + add_generation_prompt=add_generation_prompt, + continue_final_message=not add_generation_prompt, + ) + + return chat_templated + + @property + def tokenizer_name(self) -> str: + return self.tokenizer.name_or_path.replace("/", "__") + + def generate_until(self, requests: List[Instance], disable_tqdm: bool = False): + res = [] + + # Initialize statistics counters + if not hasattr(self, 'total_generated_tokens'): + self.total_generated_tokens = 0 + num_tokens = 0 + num_nfe = 0 # Number of Forward Evaluations + + prompts, gen_args = [], [] + print("Preparing prompts...") + for req in tqdm(requests): + prompts.append(self.tokenizer.bos_token + req.arguments[0]) + gen_args.append(req.arguments[1]) + + start_time = time.time() + + outputs = self.LLM.generate(prompts, self.sampling_params) + + end_time = time.time() + total_time = end_time - start_time + + # Accumulate statistics + res = [output['text'] for output in outputs] + num_tokens = sum(len(output['token_ids']) for output in outputs) + num_nfe = sum(output['n_diff_steps'] for output in outputs) + + # Save final statistics + final_stats = { + 'processed_samples': len(requests), + 'total_samples': len(requests), + 'total_tokens': num_tokens, + 'total_nfe': num_nfe, + 'total_time': total_time, + 'tokens_per_second': num_tokens / total_time if total_time > 0 else 0, + 'nfe_per_token': num_nfe / num_tokens if num_tokens > 0 else 0, + 'timestamp': time.strftime('%Y-%m-%d %H:%M:%S') + } + + # Save statistics to file + if self.save_dir is not None: + import os + os.makedirs(self.save_dir, exist_ok=True) + + # Save response results + save_path = os.path.join(self.save_dir, f'rank_{self.rank}_responses.jsonl') + with open(save_path, 'w', encoding='utf-8') as f: + for r in res: + f.write(json.dumps(r, ensure_ascii=False) + '\n') + + # Save statistics results + stats_path = os.path.join(self.save_dir, f'rank_{self.rank}_final_stats.json') + with open(stats_path, 'w', encoding='utf-8') as f: + json.dump(final_stats, f, ensure_ascii=False, indent=2) + + # Print final statistics + print("\n" + "="*60) + print("=== Final Statistics ===") + print("="*60) + print(f"Processed Samples: {final_stats['processed_samples']}") + print(f"Total Samples: {final_stats['total_samples']}") + print(f"Total Tokens: {final_stats['total_tokens']}") + print(f"Total NFE: {final_stats['total_nfe']}") + print(f"Total Time: {final_stats['total_time']:.4f}s") + print(f"Tokens/Second: {final_stats['tokens_per_second']:.2f}") + print(f"NFE/Token: {final_stats['nfe_per_token']:.4f}") + print(f"Completion Time: {final_stats['timestamp']}") + print("="*60) + + return res + + def _forward_process(self, batch): + b, l = batch.shape + # sample from U[0, 1] following https://arxiv.org/pdf/2107.00630 I.1 + u0 = torch.rand(1, device=batch.device, dtype=torch.float32) + indices = torch.arange(b, device=batch.device).float() + t = (u0 + indices / b) % 1 + + p_mask = (1 - self.sampling_eps) * t + self.sampling_eps + + p_mask = p_mask[:, None].repeat(1, l) + + mask_indices = torch.rand((b, l), device=batch.device) < p_mask + # always unmask bos and eos + mask_indices[:, 0] = False + mask_indices[:, -1] = False + + noisy_batch = torch.where(mask_indices, self.mask_token_id, batch) + return noisy_batch, p_mask + + @torch.no_grad() + def get_logits(self, batch, prompt_index): + ''' + prompt_index : 1D bool tensor, length=batch.shape[1] + ''' + if self.classifier_free_guidance > 1.: + assert len(prompt_index) == batch.shape[1] + prompt_index = prompt_index.unsqueeze(0).repeat(batch.shape[0], 1) + un_batch = batch.clone() + un_batch[prompt_index] = self.mask_token_id + batch = torch.cat([batch, un_batch]) + + input = batch + + with torch.amp.autocast('cuda', dtype=torch.bfloat16): + logits = self.model(input).logits + # since bos always unmask, the first logits will not be used + logits = torch.cat([logits[:,:1], logits[:, :-1]], dim=1) + + if self.classifier_free_guidance > 1.: + logits, un_logits = torch.chunk(logits, 2, dim=0) + logits = un_logits + self.cfg * (logits - un_logits) + return logits[:, :batch.shape[1]] + + @torch.no_grad() + def _eval_target_nll_mc(self, prefix, target): + if prefix is None: + seq = target[None, :] + else: + seq = torch.concatenate([prefix, target])[None, :] + seq = seq.repeat((self.batch_size, 1)).to(self.device) + + if self.log_type == 'ftb': + prompt_index = torch.arange(seq.shape[1], device=self.device) < len(prefix) + else: + prompt_index = torch.arange(seq.shape[1], device=self.device) >= len(prefix) + + loss_acc = [] + for _ in range(max(self.mc_num // self.batch_size, 1)): + perturbed_seq = seq.clone() + # eval_logger.info("before noising") + perturbed_seq_, p_mask = self._forward_process(seq) + # eval_logger.info("end noising") + if self.log_type == 'ftb': + perturbed_seq[:, -len(target):] = perturbed_seq_[:, -len(target):] + elif self.log_type == 'btf': + perturbed_seq[:, :len(prefix)] = perturbed_seq_[:, :len(prefix)] + elif self.log_type == 'union': + perturbed_seq = perturbed_seq_ + else: + raise NotImplementedError(self.log_type) + + mask_indices = perturbed_seq == self.mask_token_id + logits = self.get_logits(perturbed_seq, prompt_index) + loss = F.cross_entropy(logits[mask_indices], seq[mask_indices], reduction='none') / p_mask[mask_indices] + loss = loss.sum() / self.batch_size + loss_acc.append(loss.item()) + + return sum(loss_acc) / len(loss_acc) + + @torch.no_grad() + def _eval_target_nll_ar(self, prefix, target): + prefix, target = prefix.unsqueeze(0), target.unsqueeze(0) # 1*l1, 1*l2 + assert self.log_type in ['ftb', 'btf'] + assert self.nll_type in ['ar_ftb', 'ar_btf'] + + if self.log_type == 'ftb': + prompt_index = torch.arange(prefix.shape[1] + target.shape[1], device=self.device) < prefix.shape[1] + else: + prompt_index = torch.arange(prefix.shape[1] + target.shape[1], device=self.device) >= prefix.shape[1] + + if self.log_type == 'ftb': + perturbed_ = target.repeat(target.shape[1], 1).clone().contiguous() # l2*l2 + else: + perturbed_ = prefix.repeat(prefix.shape[1], 1).clone().contiguous() # l1*l1 + + mask_index = torch.ones((perturbed_.shape[1], perturbed_.shape[1]), dtype=torch.bool) + if self.nll_type == 'ar_ftb': + mask_index = torch.triu(mask_index) + else: + mask_index = torch.tril(mask_index) + perturbed_[mask_index] = self.mask_token_id + if self.log_type == 'ftb': + perturbed_seq = torch.cat([prefix.repeat(perturbed_.shape[0], 1), perturbed_], dim=-1) + else: + perturbed_seq = torch.cat([perturbed_, target.repeat(perturbed_.shape[0], 1)], dim=-1) + + logits_ = [] + num = len(perturbed_seq) // self.batch_size if len(perturbed_seq) % self.batch_size == 0 else len(perturbed_seq) // self.batch_size + 1 + for i in range(num): + end = (i + 1) * self.batch_size if (i + 1) * self.batch_size < len(perturbed_seq) else len(perturbed_seq) + perturbed_seq_ = perturbed_seq[i * self.batch_size: end] + perturbed_seq_ = perturbed_seq_.to(self.device) + if len(perturbed_seq_.shape) == 1: + perturbed_seq_ = perturbed_seq_.unsqueeze(0) + logits = self.get_logits(perturbed_seq_, prompt_index) + logits_.append(logits.cpu()) + logits = torch.cat(logits_, dim=0) + + temp_index = torch.ones((perturbed_.shape[1], perturbed_.shape[1]), dtype=torch.bool) + if self.nll_type == 'ar_ftb': + temp_index = torch.triu(temp_index, diagonal=1) + else: + temp_index = torch.tril(temp_index, diagonal=-1) + mask_index[temp_index] = False + if self.log_type == 'ftb': + logits_index = torch.cat([torch.zeros((perturbed_.shape[1], prefix.shape[1]), dtype=torch.bool), mask_index], dim=-1) + else: + logits_index = torch.cat([mask_index, torch.zeros((perturbed_.shape[1], target.shape[1]), dtype=torch.bool)], dim=-1) + + if self.log_type == 'ftb': + loss = F.cross_entropy(logits[logits_index], target[0], reduction='sum').cpu().item() + else: + loss = F.cross_entropy(logits[logits_index], prefix[0], reduction='sum').cpu().item() + return loss + + def _encode_pair(self, context, continuation): + if self.add_bos_token: + context = self.tokenizer.bos_token + context + + n_spaces = len(context) - len(context.rstrip()) + if n_spaces > 0: + continuation = context[-n_spaces:] + continuation + context = context[:-n_spaces] + + whole_enc = self.tokenizer.encode(context + continuation) + [self.tokenizer.eos_token_id] + context_enc = self.tokenizer.encode(context) + + context_enc_len = len(context_enc) + continuation_enc = whole_enc[context_enc_len:] + + # by default truncate on the left + cutoff_length = max(len(whole_enc) - self.max_length, 0) + if cutoff_length > 0: + eval_logger.warning(f"Text length {len(whole_enc)} is larger than {self.max_length}, cutoff on the left side") + context_remain = context_enc_len-cutoff_length + if context_remain > 0: + context_enc = context_enc[-context_remain:] + else: + eval_logger.warning(f"All context (prompt) is truncated.") + context_enc = "" + continuation_enc = whole_enc[-self.max_length:] + return context_enc, continuation_enc + + def loglikelihood(self, requests: List[Instance]) -> List[Tuple[float, bool]]: + def _tokenize(e): + prefix, target = self._encode_pair(e["prefix"], e["target"]) + return { + "prefix_text": e["prefix"], + "target_text": e["target"], + "prefix": prefix, + "target": target, + } + + ds = [] + ds = [{"prefix": req.args[0], "target": req.args[1]} for req in requests] + ds = Dataset.from_list(ds) + print(ds[0]) + ds = ds.map(_tokenize) + ds = ds.with_format("torch") + + out = [] + with torch.no_grad(): + for elem in tqdm(ds, desc="Computing likelihood..."): + prefix = elem["prefix"] + target = elem["target"] + # likelihood calculations are modified from https://github.com/ML-GSAI/SMDM/blob/main/evaluate_diff.py + if self.nll_type == 'mc': + ll = -self._eval_target_nll_mc(prefix, target) + if self.log_type == 'union': + ll = ll / (len(target) + len(prefix)) + elif self.nll_type == 'ar_ftb' or self.nll_type == 'ar_btf': + ll = -self._eval_target_nll_ar(prefix, target) + else: + raise NotImplementedError(self.nll_type) + + # TODO: greedy decoding + is_target_greedy_dec = False + + out.append((ll, 1.0 if is_target_greedy_dec else 0.0)) + return out + + def loglikelihood_rolling(self, requests: List[Instance]) -> List[float]: + raise NotImplementedError + + +if __name__ == "__main__": + set_seed(1234) + cli_evaluate() \ No newline at end of file diff --git a/Discrete-Diffusion-Forcing/D2F-eval/eval_dream_d2f_vllm.sh b/Discrete-Diffusion-Forcing/D2F-eval/eval_dream_d2f_vllm.sh new file mode 100644 index 0000000000000000000000000000000000000000..55ca7bf7e388a429aeeb7eb570968cc80a74bc0d --- /dev/null +++ b/Discrete-Diffusion-Forcing/D2F-eval/eval_dream_d2f_vllm.sh @@ -0,0 +1,135 @@ +tasks="gsm8k_cot mbpp minerva_math" +nshots="8 3 4" +lengths="256 256 256" +temperatures="0 0 0" +limits="10000 10000 10000" +block_sizes="32 48 64" +block_add_thresholds="0.1 0.1 0.1" +decoded_token_thresholds="0.95 0.95 0.95" +skip_thresholds="0.9 0.9 0.9" +top_ps="none none none" +dtypes="bfloat16 bfloat16 bfloat16" +sampling_strategies="default default default" + +humaneval_nshots="0" +humaneval_lengths="256" +humaneval_temperatures="0" +humaneval_limits="10000" +humaneval_diffusion_steps="256" +humaneval_block_sizes="32" +humaneval_block_add_thresholds="0.9" +humaneval_decoded_token_thresholds="0.95" +humaneval_skip_thresholds="0.95" +humaneval_top_ps="none" +humaneval_dtypes="bfloat16" +humaneval_sampling_strategies="default" + +base_model=Dream-org/Dream-v0-Base-7B + +lora_models=( + "SJTU-Deng-Lab/D2F_Dream_Base_7B_Lora" +) + +read -ra TASKS_ARRAY <<< "$tasks" +read -ra NSHOTS_ARRAY <<< "$nshots" +read -ra LENGTH_ARRAY <<< "$lengths" +read -ra TEMP_ARRAY <<< "$temperatures" +read -ra LIMITS_ARRAY <<< "$limits" +read -ra BLOCK_SIZES_ARRAY <<< "$block_sizes" +read -ra BLOCK_ADD_THRESHOLDS_ARRAY <<< "$block_add_thresholds" +read -ra DECODED_TOKEN_THRESHOLDS_ARRAY <<< "$decoded_token_thresholds" +read -ra SKIP_THRESHOLDS_ARRAY <<< "$skip_thresholds" +read -ra TOP_PS_ARRAY <<< "$top_ps" +read -ra DTYPES_ARRAY <<< "$dtypes" +read -ra SAMPLING_STRATEGIES_ARRAY <<< "$sampling_strategies" + +read -ra HUMANEVAL_NSHOTS_ARRAY <<< "$humaneval_nshots" +read -ra HUMANEVAL_LENGTHS_ARRAY <<< "$humaneval_lengths" +read -ra HUMANEVAL_TEMP_ARRAY <<< "$humaneval_temperatures" +read -ra HUMANEVAL_LIMITS_ARRAY <<< "$humaneval_limits" +read -ra HUMANEVAL_DIFFUSION_STEPS_ARRAY <<< "$humaneval_diffusion_steps" +read -ra HUMANEVAL_BLOCK_SIZES_ARRAY <<< "$humaneval_block_sizes" +read -ra HUMANEVAL_BLOCK_ADD_THRESHOLDS_ARRAY <<< "$humaneval_block_add_thresholds" +read -ra HUMANEVAL_DECODED_TOKEN_THRESHOLDS_ARRAY <<< "$humaneval_decoded_token_thresholds" +read -ra HUMANEVAL_SKIP_THRESHOLDS_ARRAY <<< "$humaneval_skip_thresholds" +read -ra HUMANEVAL_TOP_PS_ARRAY <<< "$humaneval_top_ps" +read -ra HUMANEVAL_DTYPES_ARRAY <<< "$humaneval_dtypes" +read -ra HUMANEVAL_SAMPLING_STRATEGIES_ARRAY <<< "$humaneval_sampling_strategies" + +array_length=${#TASKS_ARRAY[@]} +if [[ ${#NSHOTS_ARRAY[@]} -ne $array_length ]] || \ + [[ ${#LENGTH_ARRAY[@]} -ne $array_length ]] || \ + [[ ${#TEMP_ARRAY[@]} -ne $array_length ]] || \ + [[ ${#LIMITS_ARRAY[@]} -ne $array_length ]] || \ + [[ ${#BLOCK_SIZES_ARRAY[@]} -ne $array_length ]] || \ + [[ ${#BLOCK_ADD_THRESHOLDS_ARRAY[@]} -ne $array_length ]] || \ + [[ ${#DECODED_TOKEN_THRESHOLDS_ARRAY[@]} -ne $array_length ]] || \ + [[ ${#SKIP_THRESHOLDS_ARRAY[@]} -ne $array_length ]] || \ + [[ ${#TOP_PS_ARRAY[@]} -ne $array_length ]] || \ + [[ ${#SAMPLING_STRATEGIES_ARRAY[@]} -ne $array_length ]] || \ + [[ ${#DTYPES_ARRAY[@]} -ne $array_length ]]; then + echo "Error: All configuration arrays must have the same length!" + exit 1 +fi + +humaneval_array_length=${#HUMANEVAL_NSHOTS_ARRAY[@]} +if [[ ${#HUMANEVAL_LENGTHS_ARRAY[@]} -ne $humaneval_array_length ]] || \ + [[ ${#HUMANEVAL_TEMP_ARRAY[@]} -ne $humaneval_array_length ]] || \ + [[ ${#HUMANEVAL_LIMITS_ARRAY[@]} -ne $humaneval_array_length ]] || \ + [[ ${#HUMANEVAL_DIFFUSION_STEPS_ARRAY[@]} -ne $humaneval_array_length ]] || \ + [[ ${#HUMANEVAL_BLOCK_SIZES_ARRAY[@]} -ne $humaneval_array_length ]] || \ + [[ ${#HUMANEVAL_BLOCK_ADD_THRESHOLDS_ARRAY[@]} -ne $humaneval_array_length ]] || \ + [[ ${#HUMANEVAL_DECODED_TOKEN_THRESHOLDS_ARRAY[@]} -ne $humaneval_array_length ]] || \ + [[ ${#HUMANEVAL_SKIP_THRESHOLDS_ARRAY[@]} -ne $humaneval_array_length ]] || \ + [[ ${#HUMANEVAL_TOP_PS_ARRAY[@]} -ne $humaneval_array_length ]] || \ + [[ ${#HUMANEVAL_DTYPES_ARRAY[@]} -ne $humaneval_array_length ]] || \ + [[ ${#HUMANEVAL_SAMPLING_STRATEGIES_ARRAY[@]} -ne $humaneval_array_length ]]; then + echo "Error: All HumanEval configuration arrays must have the same length!" + exit 1 +fi + +export HF_ALLOW_CODE_EVAL=1 +for lora_model in "${lora_models[@]}"; do + lora_model_name="$lora_model" + echo "====================================================================" + echo "Evaluating LoRA model: $lora_model_name" + echo "====================================================================" + + for i in "${!HUMANEVAL_NSHOTS_ARRAY[@]}"; do + output_path="evals_dream${lora_model_name}/humaneval-ns${HUMANEVAL_NSHOTS_ARRAY[$i]}-len${HUMANEVAL_LENGTHS_ARRAY[$i]}-temp${HUMANEVAL_TEMP_ARRAY[$i]}-limit${HUMANEVAL_LIMITS_ARRAY[$i]}-diffsteps${HUMANEVAL_DIFFUSION_STEPS_ARRAY[$i]}-block${HUMANEVAL_BLOCK_SIZES_ARRAY[$i]}-thresh${HUMANEVAL_BLOCK_ADD_THRESHOLDS_ARRAY[$i]}-decodethresh${HUMANEVAL_DECODED_TOKEN_THRESHOLDS_ARRAY[$i]}-skip${HUMANEVAL_SKIP_THRESHOLDS_ARRAY[$i]}-topp${HUMANEVAL_TOP_PS_ARRAY[$i]}-dtype${HUMANEVAL_DTYPES_ARRAY[$i]}-sampling${HUMANEVAL_SAMPLING_STRATEGIES_ARRAY[$i]}" + echo "Running HumanEval evaluation $((i+1))/${humaneval_array_length} for $lora_model_name..." + if [[ "${HUMANEVAL_TOP_PS_ARRAY[$i]}" == "none" ]]; then + humaneval_model_args="pretrained=${base_model},lora_path=${lora_model},max_new_tokens=${HUMANEVAL_LENGTHS_ARRAY[$i]},diffusion_steps=${HUMANEVAL_DIFFUSION_STEPS_ARRAY[$i]},temperature=${HUMANEVAL_TEMP_ARRAY[$i]},add_bos_token=true,escape_until=true,block_size=${HUMANEVAL_BLOCK_SIZES_ARRAY[$i]},block_add_threshold=${HUMANEVAL_BLOCK_ADD_THRESHOLDS_ARRAY[$i]},skip_threshold=${HUMANEVAL_SKIP_THRESHOLDS_ARRAY[$i]},decoded_token_threshold=${HUMANEVAL_DECODED_TOKEN_THRESHOLDS_ARRAY[$i]},dtype=${HUMANEVAL_DTYPES_ARRAY[$i]},sampling_strategy=${HUMANEVAL_SAMPLING_STRATEGIES_ARRAY[$i]},save_dir=${output_path}" + else + humaneval_model_args="pretrained=${base_model},lora_path=${lora_model},max_new_tokens=${HUMANEVAL_LENGTHS_ARRAY[$i]},diffusion_steps=${HUMANEVAL_DIFFUSION_STEPS_ARRAY[$i]},temperature=${HUMANEVAL_TEMP_ARRAY[$i]},top_p=${HUMANEVAL_TOP_PS_ARRAY[$i]},add_bos_token=true,escape_until=true,block_size=${HUMANEVAL_BLOCK_SIZES_ARRAY[$i]},block_add_threshold=${HUMANEVAL_BLOCK_ADD_THRESHOLDS_ARRAY[$i]},skip_threshold=${HUMANEVAL_SKIP_THRESHOLDS_ARRAY[$i]},decoded_token_threshold=${HUMANEVAL_DECODED_TOKEN_THRESHOLDS_ARRAY[$i]},dtype=${HUMANEVAL_DTYPES_ARRAY[$i]},sampling_strategy=${HUMANEVAL_SAMPLING_STRATEGIES_ARRAY[$i]},save_dir=${output_path}" + fi + CUDA_VISIBLE_DEVICES=5 accelerate launch --main_process_port 29520 --num_processes 1 eval_dream_d2f_vllm.py --model dream_lora \ + --model_args $humaneval_model_args \ + --tasks humaneval \ + --num_fewshot ${HUMANEVAL_NSHOTS_ARRAY[$i]} \ + --batch_size 1 \ + --output_path $output_path \ + --log_samples \ + --confirm_run_unsafe_code + done + + for i in "${!TASKS_ARRAY[@]}"; do + output_path="evals_dream${lora_model_name}/${TASKS_ARRAY[$i]}-ns${NSHOTS_ARRAY[$i]}-len${LENGTH_ARRAY[$i]}-temp${TEMP_ARRAY[$i]}-limit${LIMITS_ARRAY[$i]}-diffsteps${LENGTH_ARRAY[$i]}-block${BLOCK_SIZES_ARRAY[$i]}-thresh${BLOCK_ADD_THRESHOLDS_ARRAY[$i]}-decodethresh${DECODED_TOKEN_THRESHOLDS_ARRAY[$i]}-skip${SKIP_THRESHOLDS_ARRAY[$i]}-topp${TOP_PS_ARRAY[$i]}-dtype${DTYPES_ARRAY[$i]}-sampling${SAMPLING_STRATEGIES_ARRAY[$i]}" + if [[ "${TOP_PS_ARRAY[$i]}" == "none" ]]; then + model_args="pretrained=${base_model},lora_path=${lora_model},max_new_tokens=${LENGTH_ARRAY[$i]},diffusion_steps=${LENGTH_ARRAY[$i]},add_bos_token=true,temperature=${TEMP_ARRAY[$i]},block_size=${BLOCK_SIZES_ARRAY[$i]},block_add_threshold=${BLOCK_ADD_THRESHOLDS_ARRAY[$i]},skip_threshold=${SKIP_THRESHOLDS_ARRAY[$i]},decoded_token_threshold=${DECODED_TOKEN_THRESHOLDS_ARRAY[$i]},dtype=${DTYPES_ARRAY[$i]},sampling_strategy=${SAMPLING_STRATEGIES_ARRAY[$i]},save_dir=${output_path}" + else + model_args="pretrained=${base_model},lora_path=${lora_model},max_new_tokens=${LENGTH_ARRAY[$i]},diffusion_steps=${LENGTH_ARRAY[$i]},add_bos_token=true,temperature=${TEMP_ARRAY[$i]},top_p=${TOP_PS_ARRAY[$i]},block_size=${BLOCK_SIZES_ARRAY[$i]},block_add_threshold=${BLOCK_ADD_THRESHOLDS_ARRAY[$i]},skip_threshold=${SKIP_THRESHOLDS_ARRAY[$i]},decoded_token_threshold=${DECODED_TOKEN_THRESHOLDS_ARRAY[$i]},dtype=${DTYPES_ARRAY[$i]},sampling_strategy=${SAMPLING_STRATEGIES_ARRAY[$i]},save_dir=${output_path}" + fi + CUDA_VISIBLE_DEVICES=5 accelerate launch --main_process_port 29520 --num_processes 1 eval_dream_d2f_vllm.py --model dream_lora \ + --model_args $model_args \ + --tasks ${TASKS_ARRAY[$i]} \ + --limit ${LIMITS_ARRAY[$i]} \ + --num_fewshot ${NSHOTS_ARRAY[$i]} \ + --batch_size 1 \ + --output_path $output_path \ + --log_samples \ + --confirm_run_unsafe_code + done +done + +echo "All evaluations completed!" \ No newline at end of file diff --git a/Discrete-Diffusion-Forcing/D2F-eval/eval_llada.py b/Discrete-Diffusion-Forcing/D2F-eval/eval_llada.py new file mode 100644 index 0000000000000000000000000000000000000000..2ee17158252717a3bb811f59475ad50b765eb9e6 --- /dev/null +++ b/Discrete-Diffusion-Forcing/D2F-eval/eval_llada.py @@ -0,0 +1,1198 @@ +import logging +import gc +import json +import time # Add time module +from datetime import timedelta +from typing import List, Optional, Tuple, Type, TypeVar, Union, Dict +import torch +import torch.nn.functional as F +import torch.distributions as dists +import transformers +from transformers import AutoTokenizer +from peft import LoraConfig, get_peft_model +from accelerate import ( + Accelerator, + InitProcessGroupKwargs, +) +from datasets import Dataset +from packaging import version +from tqdm import tqdm +from peft import PeftConfig, PeftModel +import numpy as np # Add numpy import +import os +import jinja2 + +# Import LLaDA model related modules +from model_cache.llada.modeling_llada import LLaDAModelLM +from model_cache.llada.configuration_llada import LLaDAConfig + +from lm_eval import utils +from lm_eval.api.instance import Instance +from lm_eval.api.model import TemplateLM +from lm_eval.api.registry import register_model +from lm_eval.models.utils import get_dtype +from lm_eval.__main__ import cli_evaluate + +eval_logger = logging.getLogger(__name__) +T = TypeVar("T", bound="TemplateLM") + +import random +def set_seed(seed): + torch.manual_seed(seed) + random.seed(seed) + np.random.seed(seed) + + torch.backends.cudnn.deterministic = True + torch.backends.cudnn.benchmark = False + +def create_full_block_attention_mask(prompt_length, max_length, block_size, device=None, dtype=None): + """ + Creates a complete attention mask for the entire sequence with block-based causal attention. + + Args: + prompt_length: Length of the prompt (first irregular block) + max_length: Maximum total sequence length + block_size: Size of each regular block + device: Device to create tensor on + dtype: Data type for the attention mask + + Returns: + attention_mask: Tensor of shape [1, 1, max_length, max_length] + """ + # Use the provided dtype or default to bfloat16 + if dtype is None: + dtype = torch.bfloat16 + + # Initialize mask with -inf (no attention) + attention_mask = torch.full((1, 1, max_length, max_length), -torch.inf, device=device, dtype=dtype) + + # Block 0: Prompt (can see itself) + attention_mask[:, :, :prompt_length, :prompt_length] = 0 + + # Calculate the number of regular blocks after prompt + remaining_length = max_length - prompt_length + num_blocks = (remaining_length + block_size - 1) // block_size + + # Process each regular block + for b in range(num_blocks): + block_start = prompt_length + b * block_size + block_end = min(prompt_length + (b + 1) * block_size, max_length) + + # Current block can see the prompt + attention_mask[:, :, block_start:block_end, :prompt_length] = 0 + + # Current block can see all previous regular blocks + for prev_b in range(b): + prev_start = prompt_length + prev_b * block_size + prev_end = min(prompt_length + (prev_b + 1) * block_size, max_length) + attention_mask[:, :, block_start:block_end, prev_start:prev_end] = 0 + + # Current block can see itself (full attention within block) + attention_mask[:, :, block_start:block_end, block_start:block_end] = 0 + + return attention_mask + +def extract_attention_mask(full_mask, start_pos, input_length, cache_length): + """ + Extract the relevant portion of attention mask for current forward pass. + + Args: + full_mask: Complete attention mask [1, 1, max_length, max_length] + start_pos: Starting position in the full sequence + input_length: Length of current input sequence + cache_length: Length of cached sequence + + Returns: + attention_mask: Extracted mask [1, 1, input_length, cache_length + input_length] + """ + end_pos = start_pos + input_length + total_length = cache_length + input_length + + # Extract the relevant rows (current input positions) + # and columns (cache + current input positions) + extracted_mask = torch.full((1, 1, input_length, total_length), -torch.inf, + device=full_mask.device, dtype=full_mask.dtype) + + # Copy cache columns (0 to cache_length in the extracted mask corresponds to 0 to cache_length in full mask) + extracted_mask[:, :, :, :cache_length] = full_mask[:, :, start_pos:end_pos, :cache_length] + + # Copy current input columns + extracted_mask[:, :, :, cache_length:] = full_mask[:, :, start_pos:end_pos, start_pos:end_pos] + + return extracted_mask + +def build_custom_float_attention_mask(input_ids, prompt_length, block_size, device=None, dtype=None): + """ + Builds a custom float attention mask with block-based causal attention. + + Args: + input_ids: Input token IDs. + prompt_length: Length of the prompt for each sequence in the batch. + block_size: Size of each regular block. + device: Device to create tensor on. + dtype: Data type for the attention mask. + + Returns: + attn_mask: Tensor of shape [B, 1, seq_len, seq_len]. + """ + B, seq_len = input_ids.shape + # Use the provided dtype or default to float32 + if dtype is None: + dtype = torch.float32 + # Initialize to all -inf + attn_mask = torch.full((B, 1, seq_len, seq_len), float('-inf'), dtype=dtype, device=device) + # 1. Prompt section: each token can attend to the entire prompt + for i in range(B): + attn_mask[i, :, :, :prompt_length[i]] = 0.0 # Allow all tokens to see the prompt + + # 2. Block division: divide blocks starting from prompt_length + num_blocks = (seq_len - prompt_length[i] + block_size - 1) // block_size + + for b in range(num_blocks): + block_start = prompt_length[i] + b * block_size + block_end = min(block_start + block_size, seq_len) + + # Full attention within the block + attn_mask[i, :, block_start:block_end, block_start:block_end] = 0.0 + + # Causal attention between blocks (can only see previous blocks) + for prev_b in range(b): + prev_start = prompt_length[i] + prev_b * block_size + prev_end = min(prev_start + block_size, seq_len) + + # Current block can see previous blocks + attn_mask[i, :, block_start:block_end, prev_start:prev_end] = 0.0 + + return attn_mask + +def top_p_logits(logits, top_p=None): + sorted_logits, sorted_indices = torch.sort(logits, descending=True) + cumulative_probs = torch.cumsum(F.softmax(sorted_logits, dim=-1), dim=-1) + sorted_indices_to_remove = cumulative_probs > top_p + # Shift the indices to the right to keep the first token above the threshold + sorted_indices_to_remove[..., 1:] = sorted_indices_to_remove[..., :-1].clone() + sorted_indices_to_remove[..., 0] = 0 + + mask = torch.zeros_like(logits, dtype=torch.bool, device=logits.device) + mask = mask.scatter_(-1, sorted_indices, sorted_indices_to_remove) + logits = logits.masked_fill(mask, torch.finfo(logits.dtype).min) + return logits + +def top_k_logits(logits, top_k=None): + top_k = min(top_k, logits.size(-1)) # Safety check + # Remove all tokens with a probability less than the last token of the top-k + indices_to_remove = logits < torch.topk(logits, top_k)[0][..., -1, None] + logits = logits.masked_fill(indices_to_remove, torch.finfo(logits.dtype).min) + return logits + +def sample_tokens(logits, temperature=0.0, top_p=None, top_k=None, margin_confidence=False, neg_entropy=False): + if temperature > 0: + logits = logits / temperature + if top_p is not None and top_p < 1: + logits = top_p_logits(logits, top_p) + if top_k is not None: + logits = top_k_logits(logits, top_k) + probs = torch.softmax(logits, dim=-1) + + if temperature > 0: + try: + x0 = dists.Categorical(probs=probs).sample() + initial_confidence = torch.gather(probs, -1, x0.unsqueeze(-1)).squeeze(-1) + except: + initial_confidence, x0 = probs.max(dim=-1) + else: + initial_confidence, x0 = probs.max(dim=-1) + + # Save initial confidence + confidence = initial_confidence.clone() + + if margin_confidence: + sorted_probs, _ = torch.sort(probs, dim=-1, descending=True) + # Extract top1 and top2 probabilities + top1_probs = sorted_probs[:, 0] + top2_probs = sorted_probs[:, 1] + # Calculate confidence as top1 - top2 + confidence = top1_probs - top2_probs + + if neg_entropy: + epsilon = 1e-10 + log_probs = torch.log(probs + epsilon) + confidence = torch.sum(probs * log_probs, dim=-1) + + return confidence, x0, initial_confidence + +@register_model("dream_lora") +class DreamLoRA(TemplateLM): + def __init__( + self, + pretrained: Union[str, transformers.PreTrainedModel], + lora_path: str, + batch_size: Optional[Union[int, str]] = 1, + device: Optional[str] = "cuda", + dtype: Optional[Union[str, torch.dtype]] = "auto", + max_new_tokens: Optional[int] = 128, + max_length: Optional[int] = 4096, # Updated to match example code + add_bos_token: Optional[bool] = False, + nll_type: Optional[str] = "mc", + log_type: Optional[str] = "ftb", + mc_num: Optional[int] = 128, + classifier_free_guidance: Optional[float] = 1.0, + sampling_eps: Optional[float] = 1e-3, + diffusion_steps: Optional[int] = 128, + trust_remote_code: Optional[bool] = True, + parallelize: Optional[bool] = False, + autogptq: Optional[Union[bool, str]] = False, + temperature: Optional[float] = 0.2, # Updated default value + top_p: Optional[float] = None, # Updated default value + top_k: Optional[float] = None, + alg: Optional[str] = "entropy", + alg_temp: Optional[float] = 0.0, + escape_until: Optional[bool] = False, + block_size: Optional[int] = 4, # Updated to match example code + mask_token_id: Optional[int] = 126336, # Added mask_token_id parameter + block_add_threshold: Optional[float] = 0.5, # Added block_add_threshold parameter + decoded_token_threshold: Optional[float] = 0.9, # Added decoded token threshold parameter + skip_threshold: Optional[float] = 1.0, # Added skip_threshold parameter + sampling_strategy: Optional[str] = "default", # Added sampling strategy parameter + save_dir: Optional[str] = None, # Added save directory parameter + show_speed: Optional[bool] = True, # Added speed statistics parameter + **kwargs, + ) -> None: + super().__init__() + + # prepare for parallelism + assert isinstance(device, str) + assert isinstance(pretrained, str) + assert isinstance(batch_size, (int, str)) + + gpus = torch.cuda.device_count() + accelerator_kwargs = InitProcessGroupKwargs(timeout=timedelta(weeks=52)) + accelerator = Accelerator(kwargs_handlers=[accelerator_kwargs]) + if accelerator.num_processes > 1: + self.accelerator = accelerator + + if "npu" in accelerator.device.type: + gpus = torch.npu.device_count() + + # using one process with no model parallelism + if not (parallelize or accelerator.num_processes > 1): + # use user-passed device + device_list = set( + ["cuda", "cpu"] + + [f"cuda:{i}" for i in range(gpus)] + + ["mps", "mps:0"] + + [f"npu:{i}" for i in range(gpus)] + ) + if device and device in device_list: + self._device = torch.device(device) + eval_logger.info(f"Using device '{device}'") + if device in ("mps", "mps:0") and version.parse( + torch.__version__ + ) < version.parse("2.1"): + raise RuntimeError( + f"mps requires torch >= 2.1. You have {torch.__version__}" + ) + else: + eval_logger.info("Device not specified") + eval_logger.info(f"Cuda Available? {torch.cuda.is_available()}") + self._device = ( + torch.device("cuda") + if torch.cuda.is_available() + else torch.device("cpu") + ) + else: # Parallelism managed by accelerate + if device != "cuda": + eval_logger.info( + f"Using `accelerate launch` or `parallelize=True`, device '{device}' will be overridden when placing model." + ) + # TODO: include in warning that `load_in_8bit` etc. affect this too + self._device = ( + self.accelerator.device + if hasattr(self, "accelerator") + else torch.device(device) + ) + + self.batch_size_per_gpu = batch_size + if isinstance(batch_size, str): + self.batch_size_per_gpu = int(batch_size) + + # Save LoRA path and block_size + self.lora_path = lora_path + self.block_size = block_size + self.block_add_threshold = block_add_threshold # Added block_add_threshold attribute + self.skip_threshold = skip_threshold # Added skip_threshold attribute + self.sampling_strategy = sampling_strategy # Save sampling strategy parameter + self.decoded_token_threshold = decoded_token_threshold # Added decoded token threshold attribute + + # Save target_dtype for later use + self.target_dtype = get_dtype(dtype) + + self._create_model_and_tokenizer(pretrained, dtype, trust_remote_code) + + if isinstance(pretrained, str): + if gpus >= 1 or str(self.device) == "mps": + # TODO: can remove this whole snippet except in the mps case, perhaps? + if not (parallelize or autogptq or hasattr(self, "accelerator")): + # place model onto device requested manually, + # if not using HF Accelerate or device_map + # or any other option that preloads model onto device + try: + self.model.to(self.device) + except ValueError: + eval_logger.debug( + "Failed to place model onto specified device. This may be because the model is quantized via `bitsandbytes` or `device_map` is provided. If the desired GPU is being used, this message is safe to ignore." + ) + # multigpu data-parallel support when launched with accelerate + if gpus > 1: + if accelerator.num_processes > 1: + if parallelize: + eval_logger.warning( + "You are both using a HF Accelerate `device_map` (`--model_args parallelize=True`) and launching via `accelerate launch`. This will attempt to do model and data parallelism depending on the resources available." + ) + elif gpus > accelerator.num_processes: + eval_logger.warning( + "WARNING: The number of total system GPUs does not match the number of spawned processes. " + "If you would like to use data parallelism, please launch the script " + "with 'accelerate launch *script*'. " + f"Current run will proceed with {accelerator.num_processes} devices." + ) + if self.accelerator.is_local_main_process: + eval_logger.info( + f"Using {gpus} devices with data parallelism" + ) + + self._device = torch.device(f"{accelerator.device}") + self.accelerator = accelerator + + self._rank = self.accelerator.local_process_index + self._world_size = self.accelerator.num_processes + else: + # if we aren't launching via accelerate, ditch + self._rank = 0 + self._world_size = 1 + else: + # if a PreTrainedModel was passed into HFLM, we forgo distributed setup. + eval_logger.warning( + "Passed an already-initialized model through `pretrained`, assuming single-process call to evaluate() or custom distributed integration" + ) + self._rank = 0 + self._world_size = 1 + + self.max_length = max_length + self.add_bos_token = add_bos_token + # generation params + self.max_new_tokens = max_new_tokens + self.diffusion_steps = diffusion_steps + self.temperature = temperature + self.top_p = top_p + self.top_k = top_k + self.alg = alg + self.alg_temp = alg_temp + self.escape_until = escape_until + self.block_size = block_size + self.mask_token_id = mask_token_id + + # loglikelihood params + self.nll_type = nll_type + self.log_type = log_type + self.mc_num = mc_num + self.classifier_free_guidance = classifier_free_guidance + self.sampling_eps = sampling_eps + + # Add backend attribute, consistent with LLaDA.py + self.backend = "causal" + + # Add truncation attribute, consistent with LLaDA.py + self.truncation = False + + self.save_dir = save_dir + self.show_speed = show_speed + + @property + def batch_size(self): + return self.batch_size_per_gpu + + @property + def eot_token_id(self): + # we use EOT because end of *text* is more accurate for what we're doing than end of *sentence* + return self.tokenizer.eos_token_id + + @property + def device(self): + return self._device + + @property + def rank(self): + return self._rank + + @property + def world_size(self): + return self._world_size + + def _create_model_and_tokenizer(self, pretrained, dtype, trust_remote_code): + # Get correct data type + target_dtype = get_dtype(dtype) + + # Load LLaDA model and configuration + config = LLaDAConfig.from_pretrained(pretrained) + self.model = LLaDAModelLM.from_pretrained( + pretrained, + config=config, + torch_dtype=target_dtype, + trust_remote_code=False, + ).eval() + + # Load LoRA configuration and model + peft_config = PeftConfig.from_pretrained(self.lora_path) + self.model = PeftModel.from_pretrained(self.model, self.lora_path) + + # Convert data type only when target_dtype is not None and not "auto" + if target_dtype is not None and target_dtype != "auto": + self.model = self.model.to(target_dtype) + + # Move to specified device + self.model = self.model.to(self.device) + + # Load tokenizer + self.tokenizer = AutoTokenizer.from_pretrained( + pretrained, trust_remote_code=trust_remote_code + ) + + def tok_encode( + self, string: str, left_truncate_len=None, add_special_tokens=None + ) -> List[int]: + """ """ + # default for None - empty dict, use predefined tokenizer param + # used for all models except for CausalLM or predefined value + special_tokens_kwargs = {} + + # by default for CausalLM - false or self.add_bos_token is set + if add_special_tokens is None: + if self.backend == "causal": + special_tokens_kwargs = { + "add_special_tokens": False or self.add_bos_token + } + # otherwise the method explicitly defines the value + else: + special_tokens_kwargs = {"add_special_tokens": add_special_tokens} + + encoding = self.tokenizer.encode(string, **special_tokens_kwargs) + + # left-truncate the encoded context to be at most `left_truncate_len` tokens long + if left_truncate_len: + encoding = encoding[-left_truncate_len:] + return encoding + + def tok_batch_encode( + self, + strings: List[str], + padding_side: str = "left", + left_truncate_len: int = None, + truncation: bool = False, + ) -> Tuple[torch.Tensor, torch.Tensor]: + # encode a batch of strings. converts to tensors and pads automatically, unlike tok_encode. + old_padding_side = self.tokenizer.padding_side + self.tokenizer.padding_side = padding_side + + add_special_tokens = {} + if self.backend == "causal": + add_special_tokens = {"add_special_tokens": False or self.add_bos_token} + + encoding = self.tokenizer( + strings, + truncation=truncation, + padding="longest", + return_tensors="pt", + **add_special_tokens, + ) + if left_truncate_len: + original_lengths = encoding["input_ids"].size(1) + if original_lengths > left_truncate_len: + eval_logger.warn( + f"Left truncation applied. Original sequence length was {original_lengths}, " + f"truncating to last {left_truncate_len} tokens. Some content will be lost.", + ) + encoding["input_ids"] = encoding["input_ids"][:, -left_truncate_len:] + encoding["attention_mask"] = encoding["attention_mask"][ + :, -left_truncate_len: + ] + self.tokenizer.padding_side = old_padding_side + + return encoding["input_ids"].to(self.device), encoding["attention_mask"].to(self.device) + + def tok_decode(self, tokens, skip_special_tokens=True): + return self.tokenizer.decode(tokens, skip_special_tokens=skip_special_tokens) + + + + def _count_tokens_after_truncation(self, response_text: str, until_terms: List[str] = None) -> int: + """ + Unified token counting function: calculates the number of non-126081 tokens after truncating the response. + """ + # Apply truncation based on until parameters + truncated_text = response_text + if until_terms and not self.escape_until: + for term in until_terms: + if len(term) > 0: + truncated_text = truncated_text.split(term)[0] + + # Re-tokenize processed answer and count non-126081 tokens + generated_answer_ids = torch.tensor(self.tokenizer(truncated_text)["input_ids"]) + return int((generated_answer_ids != 126081).sum()) + + @classmethod + def create_from_arg_string( + cls: Type[T], arg_string: str, additional_config: Optional[dict] = None + ) -> T: + """ + Creates an instance of the LM class using the given argument string and additional config. + + Parameters: + - arg_string: A string containing arguments in the format key1=value1,key2=value2. + - additional_config: Optional dictionary containing additional configuration parameters. + + Returns: + - Instance of the LM class. + """ + additional_config = {} if additional_config is None else additional_config + args = utils.simple_parse_args_string(arg_string) + args2 = {k: v for k, v in additional_config.items() if v is not None} + return cls(**args, **args2) + + def apply_chat_template( + self, chat_history: List[Dict[str, str]], add_generation_prompt: bool = True + ) -> str: + """ + Method to apply a chat template to a list of chat history between user and model. + """ + try: + chat_templated = self.tokenizer.apply_chat_template( + chat_history, + tokenize=False, + add_generation_prompt=add_generation_prompt, + continue_final_message=not add_generation_prompt, + ) + except jinja2.exceptions.TemplateError: + eval_logger.warning( + "Failed to apply chat template. removing the system role in chat history." + ) + chat_history = [msg for msg in chat_history if msg["role"] != "system"] + chat_templated = self.tokenizer.apply_chat_template( + chat_history, + tokenize=False, + add_generation_prompt=add_generation_prompt, + continue_final_message=not add_generation_prompt, + ) + + return chat_templated + + @property + def tokenizer_name(self) -> str: + return self.tokenizer.name_or_path.replace("/", "__") + + def _generate_block_single(self, prompt): + """ + Generates a response for a single prompt using parallel block generation, based on KV cache, and uses pre-generated attention masks. + Returns: generated_sequence (List[int]) - List of generated token IDs + """ + self.model.eval() + + mask_id = self.mask_token_id + block_size = self.block_size + block_add_threshold = self.block_add_threshold + skip_threshold = self.skip_threshold + + # Pre-generate the full attention mask, using the model's data type + prompt_length = prompt.shape[1] + full_attention_mask = create_full_block_attention_mask( + prompt_length=prompt_length, + max_length=self.max_length, + block_size=block_size, + device=self.device, + dtype=self.target_dtype if self.target_dtype is not None and self.target_dtype != "auto" else torch.bfloat16 + ) + + with torch.inference_mode(): + # Initialization + x_t = prompt.to(self.device) + + # Track block states - states can be: 'active', 'to_cache', 'in_cache' + # Added 'is_complete' field to indicate whether it's a complete state (True) or incomplete state (False) + block_states = { + 0: { + 'start_pos': 0, + 'end_pos': prompt.shape[1], + 'mask_count': 0, + 'total_masks': prompt.shape[1], + 'state': 'to_cache', # Prompt is immediately ready for caching + 'is_complete': True, # Prompt is always in a complete state + }, + } + + # Initialize cache + past_key_values = None + + current_blocks = 0 # Number of active blocks + step = 0 + eos_detected = False # EOS detection flag + cache_length = 0 + while current_blocks >= 0: + step += 1 + + # Check if a new block needs to be added + if len(block_states)-1 < (self.max_new_tokens // block_size) and not eos_detected: + last_block_id = len(block_states) - 1 + current_progress = (block_states[last_block_id]['total_masks'] - + block_states[last_block_id]['mask_count']) / block_states[last_block_id]['total_masks'] + if current_progress >= block_add_threshold: + # Add new block + new_block_id = len(block_states) + new_start_pos = x_t.shape[1] + x_t = torch.cat([x_t, torch.tensor([[mask_id] * block_size]).to(self.device)], dim=1) + + block_states[new_block_id] = { + 'start_pos': new_start_pos, + 'end_pos': new_start_pos + block_size, + 'mask_count': block_size, + 'total_masks': block_size, + 'state': 'active', + 'is_complete': False, # New block defaults to an incomplete state + } + current_blocks += 1 + + # At the beginning of each loop, update the block's complete/incomplete states + self._update_block_completion_states(block_states, self.decoded_token_threshold) + # Check if there are still mask tokens + mask_index = (x_t == mask_id) + if mask_index.sum() == 0 and current_blocks == 0: + break + + # Determine which blocks need to be added to the cache + blocks_to_cache = [bid for bid, state in block_states.items() + if state['state'] == 'to_cache'] + + # Determine the part to be processed + update_kvcache = 0 + if blocks_to_cache: + # Find the earliest block to be cached + earliest_block_id = min(blocks_to_cache) + earliest_pos = block_states[earliest_block_id]['start_pos'] + + # Find the latest block to be cached + latest_block_id = max(blocks_to_cache) + latest_pos = block_states[latest_block_id]['end_pos'] + + # Update the cache for all blocks within this range + update_kvcache = latest_pos - earliest_pos + + # Create input sequence for forward pass + process_start_pos = cache_length + + if update_kvcache > 0: + # Need to update cache - use completed blocks + earliest_block_to_cache = min(blocks_to_cache) + input_seq = x_t[:, block_states[earliest_block_to_cache]['start_pos']:] + process_start_pos = block_states[earliest_block_to_cache]['start_pos'] + else: + # Only process active blocks + active_blocks = [bid for bid, state in block_states.items() if state['state'] == 'active'] + if active_blocks: + # Get all active blocks after caching + earliest_active_after_cache = float('inf') + for bid in active_blocks: + if block_states[bid]['start_pos'] >= cache_length: + earliest_active_after_cache = min(earliest_active_after_cache, block_states[bid]['start_pos']) + + if earliest_active_after_cache < float('inf'): + input_seq = x_t[:, earliest_active_after_cache:] + process_start_pos = earliest_active_after_cache + else: + # No active blocks after caching, this should not happen + input_seq = x_t[:, cache_length:] + # If cache length is already equal to or exceeds sequence length, exit + if cache_length >= x_t.shape[1]: + print(f"Cache length ({cache_length}) >= sequence length ({x_t.shape[1]}) at step {step}. Exiting generation loop.") + raise Exception("Cache length >= sequence length") + else: + # No active blocks, but blocks might need to be cached in the next iteration + break + + # Check if input_seq is empty + if input_seq.shape[1] == 0: + print(f"Warning: input_seq is empty at step {step}. Breaking generation loop.") + raise Exception("input_seq is empty") + + # Extract the attention mask for the current input from the pre-generated full mask + input_length = input_seq.shape[1] + attention_mask = extract_attention_mask( + full_mask=full_attention_mask, + start_pos=process_start_pos, + input_length=input_length, + cache_length=cache_length + ) + + outputs = self.model( + input_seq, + attention_bias=attention_mask, + past_key_values=past_key_values, + use_cache=True, + update_kvcache=update_kvcache+cache_length, + ) + + # Get current logits - LLaDA model directly uses logits, no shifting needed + logits = outputs.logits + + # Update cache if needed + if update_kvcache > 0: + # Update cache + past_key_values = outputs.past_key_values + + # Mark blocks as cached + for block_id in blocks_to_cache: + block_states[block_id]['state'] = 'in_cache' + + # Process mask tokens for each active block + blocks_to_deactivate = [] + + for block_id in sorted(block_states.keys()): + if block_states[block_id]['state'] != 'active': + continue + + # Get mask positions for this block + block_start = block_states[block_id]['start_pos'] + block_end = block_states[block_id]['end_pos'] + block_mask_index = mask_index.clone() + block_mask_index[:, :block_start] = False + block_mask_index[:, block_end:] = False + + # Skip if the current block has no masks + if block_mask_index.sum() == 0: + blocks_to_deactivate.append(block_id) + continue + + + # Calculate relative position of logits + logit_offset = block_start - process_start_pos + block_rel_positions = torch.where(block_mask_index[0, block_start:block_end])[0] + + + if block_rel_positions.size(0) > 0: + # Get logits for masked positions + block_mask_logits = logits[:, logit_offset + block_rel_positions, :] + + # Sample tokens + confidence, x0, initial_confidence = sample_tokens( + block_mask_logits.squeeze(0), + self.temperature, + top_p=self.top_p, + top_k=self.top_k, + neg_entropy=(self.sampling_strategy == "neg_entropy"), + margin_confidence=(self.sampling_strategy == "margin_confidence") + ) + + # Use different sampling strategies based on the block's complete/incomplete state + is_complete = block_states[block_id]['is_complete'] + + if is_complete: + # Complete state: apply confidence threshold, if no high confidence, select the highest + high_conf_indices = torch.where(initial_confidence > skip_threshold)[0] + + if len(high_conf_indices) == 0: + number_transfer_tokens = 1 + _, transfer_index = torch.topk(confidence, number_transfer_tokens) + else: + transfer_index = torch.tensor([], device=self.device, dtype=torch.long) + + # Merge indices + all_indices = torch.unique(torch.cat([transfer_index, high_conf_indices])) + else: + # Incomplete state: only apply confidence threshold, if no tokens exceed the threshold, select none + high_conf_indices = torch.where(initial_confidence > skip_threshold)[0] + all_indices = high_conf_indices + + # Update tokens + if len(all_indices) > 0: + x0_ = torch.zeros_like(x0, device=self.device, dtype=torch.long) + mask_id + x0_[all_indices] = x0[all_indices].clone() + + # Map indices back to original positions + for i, idx in enumerate(all_indices): + abs_pos = block_start + block_rel_positions[idx] + x_t[0, abs_pos] = x0_[idx] + + # Update block state + block_states[block_id]['mask_count'] -= len(all_indices) + + # Check for EOS token + eos_token_id = 126081 + if eos_token_id is not None: + for idx in all_indices: + if x0[idx].item() == eos_token_id: + eos_detected = True + break + + # Deactivate this block if no masks remain + mask_index = (x_t == mask_id) + block_mask_index = mask_index.clone() + block_mask_index[:, :block_start] = False + block_mask_index[:, block_end:] = False + if block_mask_index.sum() == 0: + blocks_to_deactivate.append(block_id) + continue + + # Deactivate completed blocks and mark them for caching in the next iteration + for block_id in blocks_to_deactivate: + if block_states[block_id]['state'] == 'active': + # Check if all preceding blocks are already in a non-active state + can_deactivate = True + for prev_block_id in range(block_id): + if prev_block_id in block_states and block_states[prev_block_id]['state'] == 'active': + can_deactivate = False + break + + # Only mark the current block as 'to_cache' if all preceding blocks are not active + if can_deactivate: + block_states[block_id]['state'] = 'to_cache' + current_blocks -= 1 + # If there are active preceding blocks, keep the current block in active state (do nothing) + + if update_kvcache > 0: + cache_length += update_kvcache + # Safety check + if step > 10000: + print(f"WARNING: Hit safety check at step {step}. Exiting generation loop.") + break + + current_text = self.tokenizer.decode(x_t[0, prompt.shape[1]:].tolist(),skip_special_tokens=False) + + # Generate final answer + generated_sequence = x_t[0, prompt.shape[1]:].tolist() + + return generated_sequence + + + + def generate_until(self, requests: List[Instance], disable_tqdm: bool = False): + res = [] + start_time = time.time() + + # Statistics variables + num_tokens = 0 + num_nfe = 0 + + bar = tqdm(total=len(requests), disable=(disable_tqdm or (self.rank != 0)), desc="Running generate_until requests") + + for i, req in enumerate(requests): + question = req.args[0] + # print("question:",question) + # exit() + gen_kwargs = req.args[1] + + # Process input in LLaDA.py style + # print("Self.add_bos_token:", self.add_bos_token) + contexts = [question] + if self.add_bos_token: + contexts = [self.tokenizer.bos_token + p for p in contexts] + + # Use the same tokenization method as LLaDA.py + context_enc, attn_masks = self.tok_batch_encode( + contexts, + truncation=self.truncation, + ) + + + + input_ids = context_enc[0].unsqueeze(0) # Take the first one and add batch dimension + + # Add length check + if input_ids.shape[1] > self.max_length - self.max_new_tokens: + eval_logger.warning(f"Prompt length {input_ids.shape[1]} is larger than {self.max_length-self.max_new_tokens}, cutoff on the left side") + input_ids = input_ids[:, -(self.max_length-self.max_new_tokens):] + + # Generate token IDs + generated_answer = self._generate_block_single(input_ids) + + # Use tokenizer.batch_decode for decoding, consistent with LLaDA.py + cont_toks_list = self.tokenizer.batch_decode([generated_answer], skip_special_tokens=True) + s = cont_toks_list[0] # Take the first (and only) result + + # Use unified token counting function + if self.show_speed: + num_tokens += self._count_tokens_after_truncation(s, gen_kwargs.get("until", [])) + num_nfe += 1 # NFE uses simplified statistics (fixed to 1) + + # Handle until truncation in LLaDA.py style + if not self.escape_until: + for term in gen_kwargs.get("until", []): + if len(term) > 0: + s = s.split(term)[0] + + res.append(s) + bar.update(1) + + bar.close() + + # Save statistics only at the end + if self.save_dir is not None: + os.makedirs(self.save_dir, exist_ok=True) + final_time = time.time() + total_time = final_time - start_time + + final_stats = { + "processed_samples": len(res), + "total_samples": len(requests), + "total_tokens": int(num_tokens), + "total_nfe": int(num_nfe), + "total_time": total_time, + "tokens_per_second": float(num_tokens) / total_time if total_time > 0 else 0.0, + "nfe_per_token": float(num_nfe) / float(num_tokens) if num_tokens > 0 else 0.0, + "timestamp": final_time + } + final_stats_path = os.path.join(self.save_dir, f'rank_{self.rank}_final_stats.json') + with open(final_stats_path, 'w', encoding='utf-8') as f: + json.dump(final_stats, f, ensure_ascii=False, indent=2) + + if self.show_speed: + final_time = time.time() + total_time = final_time - start_time + print(f"\n=== Final Statistics ===") + print(f"Processed samples: {len(res)}") + print(f"Total tokens: {num_tokens}") + print(f"Total time: {total_time:.2f} seconds") + print(f"Throughput: {num_tokens / total_time:.2f} tokens/s") + print(f"Total NFE: {num_nfe}") + + return res + + def _forward_process(self, batch): + b, l = batch.shape + # sample from U[0, 1] following https://arxiv.org/pdf/2107.00630 I.1 + u0 = torch.rand(1, device=batch.device, dtype=torch.float32) + indices = torch.arange(b, device=batch.device).float() + t = (u0 + indices / b) % 1 + + p_mask = (1 - self.sampling_eps) * t + self.sampling_eps + + p_mask = p_mask[:, None].repeat(1, l) + + mask_indices = torch.rand((b, l), device=batch.device) < p_mask + # always unmask bos and eos + mask_indices[:, 0] = False + mask_indices[:, -1] = False + + noisy_batch = torch.where(mask_indices, self.mask_token_id, batch) + return noisy_batch, p_mask + + @torch.no_grad() + def get_logits(self, batch, prompt_index): + ''' + prompt_index : 1D bool tensor, length=batch.shape[1] + ''' + if self.classifier_free_guidance > 1.: + assert len(prompt_index) == batch.shape[1] + prompt_index = prompt_index.unsqueeze(0).repeat(batch.shape[0], 1) + un_batch = batch.clone() + un_batch[prompt_index] = self.mask_token_id + batch = torch.cat([batch, un_batch]) + + input = batch + + with torch.amp.autocast('cuda', dtype=torch.bfloat16): + logits = self.model(input).logits + # since bos always unmask, the first logits will not be used + logits = torch.cat([logits[:,:1], logits[:, :-1]], dim=1) + + if self.classifier_free_guidance > 1.: + logits, un_logits = torch.chunk(logits, 2, dim=0) + logits = un_logits + self.cfg * (logits - un_logits) + return logits[:, :batch.shape[1]] + + @torch.no_grad() + def _eval_target_nll_mc(self, prefix, target): + if prefix is None: + seq = target[None, :] + else: + seq = torch.concatenate([prefix, target])[None, :] + seq = seq.repeat((self.batch_size, 1)).to(self.device) + + if self.log_type == 'ftb': + prompt_index = torch.arange(seq.shape[1], device=self.device) < len(prefix) + else: + prompt_index = torch.arange(seq.shape[1], device=self.device) >= len(prefix) + + loss_acc = [] + for _ in range(max(self.mc_num // self.batch_size, 1)): + perturbed_seq = seq.clone() + # eval_logger.info("before noising") + perturbed_seq_, p_mask = self._forward_process(seq) + # eval_logger.info("end noising") + if self.log_type == 'ftb': + perturbed_seq[:, -len(target):] = perturbed_seq_[:, -len(target):] + elif self.log_type == 'btf': + perturbed_seq[:, :len(prefix)] = perturbed_seq_[:, :len(prefix)] + elif self.log_type == 'union': + perturbed_seq = perturbed_seq_ + else: + raise NotImplementedError(self.log_type) + + mask_indices = perturbed_seq == self.mask_token_id + logits = self.get_logits(perturbed_seq, prompt_index) + loss = F.cross_entropy(logits[mask_indices], seq[mask_indices], reduction='none') / p_mask[mask_indices] + loss = loss.sum() / self.batch_size + loss_acc.append(loss.item()) + + return sum(loss_acc) / len(loss_acc) + + @torch.no_grad() + def _eval_target_nll_ar(self, prefix, target): + prefix, target = prefix.unsqueeze(0), target.unsqueeze(0) # 1*l1, 1*l2 + assert self.log_type in ['ftb', 'btf'] + assert self.nll_type in ['ar_ftb', 'ar_btf'] + + if self.log_type == 'ftb': + prompt_index = torch.arange(prefix.shape[1] + target.shape[1], device=self.device) < prefix.shape[1] + else: + prompt_index = torch.arange(prefix.shape[1] + target.shape[1], device=self.device) >= prefix.shape[1] + + if self.log_type == 'ftb': + perturbed_ = target.repeat(target.shape[1], 1).clone().contiguous() # l2*l2 + else: + perturbed_ = prefix.repeat(prefix.shape[1], 1).clone().contiguous() # l1*l1 + + mask_index = torch.ones((perturbed_.shape[1], perturbed_.shape[1]), dtype=torch.bool) + if self.nll_type == 'ar_ftb': + mask_index = torch.triu(mask_index) + else: + mask_index = torch.tril(mask_index) + perturbed_[mask_index] = self.mask_token_id + if self.log_type == 'ftb': + perturbed_seq = torch.cat([prefix.repeat(perturbed_.shape[0], 1), perturbed_], dim=-1) + else: + perturbed_seq = torch.cat([perturbed_, target.repeat(perturbed_.shape[0], 1)], dim=-1) + + logits_ = [] + num = len(perturbed_seq) // self.batch_size if len(perturbed_seq) % self.batch_size == 0 else len(perturbed_seq) // self.batch_size + 1 + for i in range(num): + end = (i + 1) * self.batch_size if (i + 1) * self.batch_size < len(perturbed_seq) else len(perturbed_seq) + perturbed_seq_ = perturbed_seq[i * self.batch_size: end] + perturbed_seq_ = perturbed_seq_.to(self.device) + if len(perturbed_seq_.shape) == 1: + perturbed_seq_ = perturbed_seq_.unsqueeze(0) + logits = self.get_logits(perturbed_seq_, prompt_index) + logits_.append(logits.cpu()) + logits = torch.cat(logits_, dim=0) + + temp_index = torch.ones((perturbed_.shape[1], perturbed_.shape[1]), dtype=torch.bool) + if self.nll_type == 'ar_ftb': + temp_index = torch.triu(temp_index, diagonal=1) + else: + temp_index = torch.tril(temp_index, diagonal=-1) + mask_index[temp_index] = False + if self.log_type == 'ftb': + logits_index = torch.cat([torch.zeros((perturbed_.shape[1], prefix.shape[1]), dtype=torch.bool), mask_index], dim=-1) + else: + logits_index = torch.cat([mask_index, torch.zeros((perturbed_.shape[1], target.shape[1]), dtype=torch.bool)], dim=-1) + + if self.log_type == 'ftb': + loss = F.cross_entropy(logits[logits_index], target[0], reduction='sum').cpu().item() + else: + loss = F.cross_entropy(logits[logits_index], prefix[0], reduction='sum').cpu().item() + return loss + + def _encode_pair(self, context, continuation): + if self.add_bos_token: + context = self.tokenizer.bos_token + context + + n_spaces = len(context) - len(context.rstrip()) + if n_spaces > 0: + continuation = context[-n_spaces:] + continuation + context = context[:-n_spaces] + + whole_enc = self.tokenizer.encode(context + continuation) + [self.tokenizer.eos_token_id] + context_enc = self.tokenizer.encode(context) + + context_enc_len = len(context_enc) + continuation_enc = whole_enc[context_enc_len:] + + # by default truncate on the left + cutoff_length = max(len(whole_enc) - self.max_length, 0) + if cutoff_length > 0: + eval_logger.warning(f"Text length {len(whole_enc)} is larger than {self.max_length}, cutoff on the left side") + context_remain = context_enc_len-cutoff_length + if context_remain > 0: + context_enc = context_enc[-context_remain:] + else: + eval_logger.warning(f"All context (prompt) is truncated.") + context_enc = "" + continuation_enc = whole_enc[-self.max_length:] + return context_enc, continuation_enc + + def loglikelihood(self, requests: List[Instance]) -> List[Tuple[float, bool]]: + def _tokenize(e): + prefix, target = self._encode_pair(e["prefix"], e["target"]) + return { + "prefix_text": e["prefix"], + "target_text": e["target"], + "prefix": prefix, + "target": target, + } + + ds = [] + ds = [{"prefix": req.args[0], "target": req.args[1]} for req in requests] + ds = Dataset.from_list(ds) + print(ds[0]) + ds = ds.map(_tokenize) + ds = ds.with_format("torch") + + out = [] + with torch.no_grad(): + for elem in tqdm(ds, desc="Computing likelihood..."): + prefix = elem["prefix"] + target = elem["target"] + # likelihood calculations are modified from https://github.com/ML-GSAI/SMDM/blob/main/evaluate_diff.py + if self.nll_type == 'mc': + ll = -self._eval_target_nll_mc(prefix, target) + if self.log_type == 'union': + ll = ll / (len(target) + len(prefix)) + elif self.nll_type == 'ar_ftb' or self.nll_type == 'ar_btf': + ll = -self._eval_target_nll_ar(prefix, target) + else: + raise NotImplementedError(self.nll_type) + + # TODO: greedy decoding + is_target_greedy_dec = False + + out.append((ll, 1.0 if is_target_greedy_dec else 0.0)) + return out + + def loglikelihood_rolling(self, requests: List[Instance]) -> List[float]: + raise NotImplementedError + + def _loglikelihood_tokens(self, requests, **kwargs) -> List[Tuple[float, bool]]: + raise NotImplementedError + + + def _update_block_completion_states(self, block_states, decoded_token_threshold): + """ + Updates the complete/incomplete state of blocks. + Iterates through blocks from front to back. If a block's decoded token count exceeds the threshold, the next block to its right (if it exists) is set to a complete state. + """ + for block_id in sorted(block_states.keys()): + # if block_id == 0: # Skip prompt block + # continue + + # Calculate decoded tokens for the current block + decoded_tokens = block_states[block_id]['total_masks'] - block_states[block_id]['mask_count'] + decode_ratio = decoded_tokens / block_states[block_id]['total_masks'] + # If current block's decoded token count exceeds the threshold, the next block (if exists) is set to a complete state + # print("decode_ratio",decode_ratio) + # print("decoded_token_threshold",decoded_token_threshold) + if decode_ratio >= decoded_token_threshold: + next_block_id = block_id + 1 + if next_block_id in block_states: + block_states[next_block_id]['is_complete'] = True + + +if __name__ == "__main__": + set_seed(1234) + cli_evaluate() \ No newline at end of file diff --git a/Discrete-Diffusion-Forcing/D2F-eval/eval_llada.sh b/Discrete-Diffusion-Forcing/D2F-eval/eval_llada.sh new file mode 100644 index 0000000000000000000000000000000000000000..b5f8954cb22d51db64fde397566c9a246b5ba089 --- /dev/null +++ b/Discrete-Diffusion-Forcing/D2F-eval/eval_llada.sh @@ -0,0 +1,155 @@ +#!/bin/bash + + +tasks="gsm8k mbpp minerva_math" +nshots="4 3 0" +lengths="512 512 512" +temperatures="0 0 0" +limits="10000 10000 10000" +block_sizes="64 32 32" +block_add_thresholds="0.7 0.9 0.1" +decoded_token_thresholds="0.95 0.95 0.95" +skip_thresholds="0.9 0.9 0.9" +top_ps="none none none" +dtypes="bfloat16 bfloat16 bfloat16" +sampling_strategies="default default default" + + +humaneval_nshots="0" +humaneval_lengths="512" +humaneval_temperatures="0" +humaneval_limits="10000" +humaneval_diffusion_steps="512" +humaneval_block_sizes="32" +humaneval_block_add_thresholds="0.1" +humaneval_decoded_token_thresholds="0.95" +humaneval_skip_thresholds="0.9" +humaneval_top_ps="none" +humaneval_dtypes="bfloat16" +humaneval_sampling_strategies="default" + + +base_model=GSAI-ML/LLaDA-8B-Instruct + +lora_models=( + "SJTU-Deng-Lab/D2F_LLaDA_Instruct_8B_Lora" +) + +read -ra TASKS_ARRAY <<< "$tasks" +read -ra NSHOTS_ARRAY <<< "$nshots" +read -ra LENGTH_ARRAY <<< "$lengths" +read -ra TEMP_ARRAY <<< "$temperatures" +read -ra LIMITS_ARRAY <<< "$limits" +read -ra BLOCK_SIZES_ARRAY <<< "$block_sizes" +read -ra BLOCK_ADD_THRESHOLDS_ARRAY <<< "$block_add_thresholds" +read -ra DECODED_TOKEN_THRESHOLDS_ARRAY <<< "$decoded_token_thresholds" +read -ra SKIP_THRESHOLDS_ARRAY <<< "$skip_thresholds" +read -ra TOP_PS_ARRAY <<< "$top_ps" +read -ra DTYPES_ARRAY <<< "$dtypes" +read -ra SAMPLING_STRATEGIES_ARRAY <<< "$sampling_strategies" + +read -ra HUMANEVAL_NSHOTS_ARRAY <<< "$humaneval_nshots" +read -ra HUMANEVAL_LENGTHS_ARRAY <<< "$humaneval_lengths" +read -ra HUMANEVAL_TEMP_ARRAY <<< "$humaneval_temperatures" +read -ra HUMANEVAL_LIMITS_ARRAY <<< "$humaneval_limits" +read -ra HUMANEVAL_DIFFUSION_STEPS_ARRAY <<< "$humaneval_diffusion_steps" +read -ra HUMANEVAL_BLOCK_SIZES_ARRAY <<< "$humaneval_block_sizes" +read -ra HUMANEVAL_BLOCK_ADD_THRESHOLDS_ARRAY <<< "$humaneval_block_add_thresholds" +read -ra HUMANEVAL_DECODED_TOKEN_THRESHOLDS_ARRAY <<< "$humaneval_decoded_token_thresholds" +read -ra HUMANEVAL_SKIP_THRESHOLDS_ARRAY <<< "$humaneval_skip_thresholds" +read -ra HUMANEVAL_TOP_PS_ARRAY <<< "$humaneval_top_ps" +read -ra HUMANEVAL_DTYPES_ARRAY <<< "$humaneval_dtypes" +read -ra HUMANEVAL_SAMPLING_STRATEGIES_ARRAY <<< "$humaneval_sampling_strategies" + +array_length=${#TASKS_ARRAY[@]} +if [[ ${#NSHOTS_ARRAY[@]} -ne $array_length ]] || \ + [[ ${#LENGTH_ARRAY[@]} -ne $array_length ]] || \ + [[ ${#TEMP_ARRAY[@]} -ne $array_length ]] || \ + [[ ${#LIMITS_ARRAY[@]} -ne $array_length ]] || \ + [[ ${#BLOCK_SIZES_ARRAY[@]} -ne $array_length ]] || \ + [[ ${#BLOCK_ADD_THRESHOLDS_ARRAY[@]} -ne $array_length ]] || \ + [[ ${#DECODED_TOKEN_THRESHOLDS_ARRAY[@]} -ne $array_length ]] || \ + [[ ${#SKIP_THRESHOLDS_ARRAY[@]} -ne $array_length ]] || \ + [[ ${#TOP_PS_ARRAY[@]} -ne $array_length ]] || \ + [[ ${#SAMPLING_STRATEGIES_ARRAY[@]} -ne $array_length ]] || \ + [[ ${#DTYPES_ARRAY[@]} -ne $array_length ]]; then + echo "Error: All configuration arrays must have the same length!" + echo "Tasks: ${#TASKS_ARRAY[@]}, Nshots: ${#NSHOTS_ARRAY[@]}, Lengths: ${#LENGTH_ARRAY[@]}, Temperatures: ${#TEMP_ARRAY[@]}, Limits: ${#LIMITS_ARRAY[@]}, Block sizes: ${#BLOCK_SIZES_ARRAY[@]}, Block thresholds: ${#BLOCK_ADD_THRESHOLDS_ARRAY[@]}, Decoded token thresholds: ${#DECODED_TOKEN_THRESHOLDS_ARRAY[@]}, Skip thresholds: ${#SKIP_THRESHOLDS_ARRAY[@]}, Top_ps: ${#TOP_PS_ARRAY[@]}, Sampling strategies: ${#SAMPLING_STRATEGIES_ARRAY[@]}, Dtypes: ${#DTYPES_ARRAY[@]}" + exit 1 +fi + +humaneval_array_length=${#HUMANEVAL_NSHOTS_ARRAY[@]} +if [[ ${#HUMANEVAL_LENGTHS_ARRAY[@]} -ne $humaneval_array_length ]] || \ + [[ ${#HUMANEVAL_TEMP_ARRAY[@]} -ne $humaneval_array_length ]] || \ + [[ ${#HUMANEVAL_LIMITS_ARRAY[@]} -ne $humaneval_array_length ]] || \ + [[ ${#HUMANEVAL_DIFFUSION_STEPS_ARRAY[@]} -ne $humaneval_array_length ]] || \ + [[ ${#HUMANEVAL_BLOCK_SIZES_ARRAY[@]} -ne $humaneval_array_length ]] || \ + [[ ${#HUMANEVAL_BLOCK_ADD_THRESHOLDS_ARRAY[@]} -ne $humaneval_array_length ]] || \ + [[ ${#HUMANEVAL_DECODED_TOKEN_THRESHOLDS_ARRAY[@]} -ne $humaneval_array_length ]] || \ + [[ ${#HUMANEVAL_SKIP_THRESHOLDS_ARRAY[@]} -ne $humaneval_array_length ]] || \ + [[ ${#HUMANEVAL_TOP_PS_ARRAY[@]} -ne $humaneval_array_length ]] || \ + [[ ${#HUMANEVAL_DTYPES_ARRAY[@]} -ne $humaneval_array_length ]] || \ + [[ ${#HUMANEVAL_SAMPLING_STRATEGIES_ARRAY[@]} -ne $humaneval_array_length ]]; then + echo "Error: All HumanEval configuration arrays must have the same length!" + echo "HumanEval Nshots: ${#HUMANEVAL_NSHOTS_ARRAY[@]}, Lengths: ${#HUMANEVAL_LENGTHS_ARRAY[@]}, Temperatures: ${#HUMANEVAL_TEMP_ARRAY[@]}, Limits: ${#HUMANEVAL_LIMITS_ARRAY[@]}, Diffusion steps: ${#HUMANEVAL_DIFFUSION_STEPS_ARRAY[@]}, Block sizes: ${#HUMANEVAL_BLOCK_SIZES_ARRAY[@]}, Block thresholds: ${#HUMANEVAL_BLOCK_ADD_THRESHOLDS_ARRAY[@]}, Decoded token thresholds: ${#HUMANEVAL_DECODED_TOKEN_THRESHOLDS_ARRAY[@]}, Skip thresholds: ${#HUMANEVAL_SKIP_THRESHOLDS_ARRAY[@]}, Top_ps: ${#HUMANEVAL_TOP_PS_ARRAY[@]}, Dtypes: ${#HUMANEVAL_DTYPES_ARRAY[@]}, Sampling strategies: ${#HUMANEVAL_SAMPLING_STRATEGIES_ARRAY[@]}" + exit 1 +fi + +export HF_ALLOW_CODE_EVAL=1 +for lora_model in "${lora_models[@]}"; do + lora_model_name="$lora_model" + echo "====================================================================" + echo "Evaluating LoRA model: $lora_model_name" + echo "====================================================================" + + + + for i in "${!HUMANEVAL_NSHOTS_ARRAY[@]}"; do + output_path="eval_llada${lora_model_name}/humaneval-ns${HUMANEVAL_NSHOTS_ARRAY[$i]}-len${HUMANEVAL_LENGTHS_ARRAY[$i]}-temp${HUMANEVAL_TEMP_ARRAY[$i]}-limit${HUMANEVAL_LIMITS_ARRAY[$i]}-diffsteps${HUMANEVAL_DIFFUSION_STEPS_ARRAY[$i]}-block${HUMANEVAL_BLOCK_SIZES_ARRAY[$i]}-thresh${HUMANEVAL_BLOCK_ADD_THRESHOLDS_ARRAY[$i]}-decodethresh${HUMANEVAL_DECODED_TOKEN_THRESHOLDS_ARRAY[$i]}-skip${HUMANEVAL_SKIP_THRESHOLDS_ARRAY[$i]}-topp${HUMANEVAL_TOP_PS_ARRAY[$i]}-dtype${HUMANEVAL_DTYPES_ARRAY[$i]}-sampling${HUMANEVAL_SAMPLING_STRATEGIES_ARRAY[$i]}" + echo "Running HumanEval evaluation $((i+1))/${humaneval_array_length} for $lora_model_name..." + echo "HumanEval Config: Shots: ${HUMANEVAL_NSHOTS_ARRAY[$i]}, Length: ${HUMANEVAL_LENGTHS_ARRAY[$i]}, Temperature: ${HUMANEVAL_TEMP_ARRAY[$i]}, Limit: ${HUMANEVAL_LIMITS_ARRAY[$i]}, Diffusion Steps: ${HUMANEVAL_DIFFUSION_STEPS_ARRAY[$i]}, Block Size: ${HUMANEVAL_BLOCK_SIZES_ARRAY[$i]}, Block Add Threshold: ${HUMANEVAL_BLOCK_ADD_THRESHOLDS_ARRAY[$i]}, Decoded Token Threshold: ${HUMANEVAL_DECODED_TOKEN_THRESHOLDS_ARRAY[$i]}, Skip Threshold: ${HUMANEVAL_SKIP_THRESHOLDS_ARRAY[$i]}, Top_p: ${HUMANEVAL_TOP_PS_ARRAY[$i]}, Sampling Strategy: ${HUMANEVAL_SAMPLING_STRATEGIES_ARRAY[$i]}, Dtype: ${HUMANEVAL_DTYPES_ARRAY[$i]}; Output: $output_path" + + if [[ "${HUMANEVAL_TOP_PS_ARRAY[$i]}" == "none" ]]; then + humaneval_model_args="pretrained=${base_model},lora_path=${lora_model},max_new_tokens=${HUMANEVAL_LENGTHS_ARRAY[$i]},diffusion_steps=${HUMANEVAL_DIFFUSION_STEPS_ARRAY[$i]},temperature=${HUMANEVAL_TEMP_ARRAY[$i]},add_bos_token=true,escape_until=true,block_size=${HUMANEVAL_BLOCK_SIZES_ARRAY[$i]},block_add_threshold=${HUMANEVAL_BLOCK_ADD_THRESHOLDS_ARRAY[$i]},skip_threshold=${HUMANEVAL_SKIP_THRESHOLDS_ARRAY[$i]},decoded_token_threshold=${HUMANEVAL_DECODED_TOKEN_THRESHOLDS_ARRAY[$i]},dtype=${HUMANEVAL_DTYPES_ARRAY[$i]},sampling_strategy=${HUMANEVAL_SAMPLING_STRATEGIES_ARRAY[$i]},save_dir=${output_path}" + else + humaneval_model_args="pretrained=${base_model},lora_path=${lora_model},max_new_tokens=${HUMANEVAL_LENGTHS_ARRAY[$i]},diffusion_steps=${HUMANEVAL_DIFFUSION_STEPS_ARRAY[$i]},temperature=${HUMANEVAL_TEMP_ARRAY[$i]},top_p=${HUMANEVAL_TOP_PS_ARRAY[$i]},add_bos_token=true,escape_until=true,block_size=${HUMANEVAL_BLOCK_SIZES_ARRAY[$i]},block_add_threshold=${HUMANEVAL_BLOCK_ADD_THRESHOLDS_ARRAY[$i]},skip_threshold=${HUMANEVAL_SKIP_THRESHOLDS_ARRAY[$i]},decoded_token_threshold=${HUMANEVAL_DECODED_TOKEN_THRESHOLDS_ARRAY[$i]},dtype=${HUMANEVAL_DTYPES_ARRAY[$i]},sampling_strategy=${HUMANEVAL_SAMPLING_STRATEGIES_ARRAY[$i]},save_dir=${output_path}" + fi + + CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 accelerate launch --main_process_port 29520 --num_processes 8 eval_llada.py --model dream_lora \ + --model_args $humaneval_model_args \ + --tasks humaneval \ + --num_fewshot ${HUMANEVAL_NSHOTS_ARRAY[$i]} \ + --batch_size 1 \ + --output_path $output_path \ + --log_samples \ + --confirm_run_unsafe_code + done + + ### NOTICE: use postprocess for humaneval + # python postprocess_code.py {the samples_xxx.jsonl file under output_path} + + for i in "${!TASKS_ARRAY[@]}"; do + output_path="eval_llada${lora_model_name}/${TASKS_ARRAY[$i]}-ns${NSHOTS_ARRAY[$i]}-len${LENGTH_ARRAY[$i]}-temp${TEMP_ARRAY[$i]}-limit${LIMITS_ARRAY[$i]}-diffsteps${LENGTH_ARRAY[$i]}-block${BLOCK_SIZES_ARRAY[$i]}-thresh${BLOCK_ADD_THRESHOLDS_ARRAY[$i]}-decodethresh${DECODED_TOKEN_THRESHOLDS_ARRAY[$i]}-skip${SKIP_THRESHOLDS_ARRAY[$i]}-topp${TOP_PS_ARRAY[$i]}-dtype${DTYPES_ARRAY[$i]}-sampling${SAMPLING_STRATEGIES_ARRAY[$i]}" + echo "Task: ${TASKS_ARRAY[$i]}, Shots: ${NSHOTS_ARRAY[$i]}, Length: ${LENGTH_ARRAY[$i]}, Temperature: ${TEMP_ARRAY[$i]}, Limit: ${LIMITS_ARRAY[$i]}, Block Size: ${BLOCK_SIZES_ARRAY[$i]}, Block Add Threshold: ${BLOCK_ADD_THRESHOLDS_ARRAY[$i]}, Decoded Token Threshold: ${DECODED_TOKEN_THRESHOLDS_ARRAY[$i]}, Skip Threshold: ${SKIP_THRESHOLDS_ARRAY[$i]}, Top_p: ${TOP_PS_ARRAY[$i]}, Sampling Strategy: ${SAMPLING_STRATEGIES_ARRAY[$i]}, Dtype: ${DTYPES_ARRAY[$i]}; Output: $output_path" + + if [[ "${TOP_PS_ARRAY[$i]}" == "none" ]]; then + model_args="pretrained=${base_model},lora_path=${lora_model},max_new_tokens=${LENGTH_ARRAY[$i]},diffusion_steps=${LENGTH_ARRAY[$i]},add_bos_token=true,temperature=${TEMP_ARRAY[$i]},block_size=${BLOCK_SIZES_ARRAY[$i]},block_add_threshold=${BLOCK_ADD_THRESHOLDS_ARRAY[$i]},skip_threshold=${SKIP_THRESHOLDS_ARRAY[$i]},decoded_token_threshold=${DECODED_TOKEN_THRESHOLDS_ARRAY[$i]},dtype=${DTYPES_ARRAY[$i]},sampling_strategy=${SAMPLING_STRATEGIES_ARRAY[$i]},save_dir=${output_path}" + else + model_args="pretrained=${base_model},lora_path=${lora_model},max_new_tokens=${LENGTH_ARRAY[$i]},diffusion_steps=${LENGTH_ARRAY[$i]},add_bos_token=true,temperature=${TEMP_ARRAY[$i]},top_p=${TOP_PS_ARRAY[$i]},block_size=${BLOCK_SIZES_ARRAY[$i]},block_add_threshold=${BLOCK_ADD_THRESHOLDS_ARRAY[$i]},skip_threshold=${SKIP_THRESHOLDS_ARRAY[$i]},decoded_token_threshold=${DECODED_TOKEN_THRESHOLDS_ARRAY[$i]},dtype=${DTYPES_ARRAY[$i]},sampling_strategy=${SAMPLING_STRATEGIES_ARRAY[$i]},save_dir=${output_path}" + fi + + CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 accelerate launch --main_process_port 29520 --num_processes 8 eval_llada.py --model dream_lora \ + --model_args $model_args \ + --tasks ${TASKS_ARRAY[$i]} \ + --limit ${LIMITS_ARRAY[$i]} \ + --num_fewshot ${NSHOTS_ARRAY[$i]} \ + --batch_size 1 \ + --output_path $output_path \ + --log_samples \ + --confirm_run_unsafe_code \ + --apply_chat_template \ + --fewshot_as_multiturn + done +done + +echo "All evaluations completed!" \ No newline at end of file diff --git a/Discrete-Diffusion-Forcing/D2F-eval/generate_llada_demo_ar.py b/Discrete-Diffusion-Forcing/D2F-eval/generate_llada_demo_ar.py new file mode 100644 index 0000000000000000000000000000000000000000..2aee7543c7ab3786befedfb03b5ff616379b0379 --- /dev/null +++ b/Discrete-Diffusion-Forcing/D2F-eval/generate_llada_demo_ar.py @@ -0,0 +1,660 @@ +import torch +import torch.nn.functional as F +import torch.distributions as dists +import transformers +from transformers import AutoTokenizer, AutoModelForCausalLM +from peft import PeftModel, PeftConfig +import numpy as np +import random +import time +import os +from typing import List, Dict, Optional, Tuple, Iterator, Set +import gradio as gr +import gc + +# Suppress some Hugging Face warnings +os.environ["TOKENIZERS_PARALLELISM"] = "false" + +# Import necessary model classes +# Assuming these custom classes are in the correct path +from model_cache.llada.modeling_llada import LLaDAModelLM +from model_cache.llada.configuration_llada import LLaDAConfig + +# --- Helper Functions (Unchanged) --- +def set_seed(seed): + torch.manual_seed(seed); random.seed(seed); np.random.seed(seed); + if torch.cuda.is_available(): torch.cuda.manual_seed_all(seed); torch.backends.cudnn.deterministic = True; torch.backends.cudnn.benchmark = False + +def create_full_block_attention_mask(prompt_length, max_length, block_size, device=None, dtype=None): + if dtype is None: dtype = torch.bfloat16 + attention_mask = torch.full((1, 1, max_length, max_length), -torch.inf, device=device, dtype=dtype) + attention_mask[:, :, :prompt_length, :prompt_length] = 0 + remaining_length = max_length - prompt_length + num_blocks = (remaining_length + block_size - 1) // block_size + for b in range(num_blocks): + block_start = prompt_length + b * block_size; block_end = min(prompt_length + (b + 1) * block_size, max_length) + attention_mask[:, :, block_start:block_end, :prompt_length] = 0 + for prev_b in range(b): + prev_start = prompt_length + prev_b * block_size; prev_end = min(prompt_length + (prev_b + 1) * block_size, max_length) + attention_mask[:, :, block_start:block_end, prev_start:prev_end] = 0 + attention_mask[:, :, block_start:block_end, block_start:block_end] = 0 + return attention_mask + +def extract_attention_mask(full_mask, start_pos, input_length, cache_length): + end_pos = start_pos + input_length; total_length = cache_length + input_length + extracted_mask = torch.full((1, 1, input_length, total_length), -torch.inf, device=full_mask.device, dtype=full_mask.dtype) + extracted_mask[:, :, :, :cache_length] = full_mask[:, :, start_pos:end_pos, :cache_length] + extracted_mask[:, :, :, cache_length:] = full_mask[:, :, start_pos:end_pos, start_pos:end_pos] + return extracted_mask + +def top_p_logits(logits, top_p=None): + sorted_logits, sorted_indices = torch.sort(logits, descending=True) + cumulative_probs = torch.cumsum(F.softmax(sorted_logits, dim=-1), dim=-1) + sorted_indices_to_remove = cumulative_probs > top_p + sorted_indices_to_remove[..., 1:] = sorted_indices_to_remove[..., :-1].clone() + sorted_indices_to_remove[..., 0] = 0 + mask = torch.zeros_like(logits, dtype=torch.bool, device=logits.device) + mask = mask.scatter_(-1, sorted_indices, sorted_indices_to_remove) + logits = logits.masked_fill(mask, torch.finfo(logits.dtype).min) + return logits + +def top_k_logits(logits, top_k=None): + top_k = min(top_k, logits.size(-1)) + indices_to_remove = logits < torch.topk(logits, top_k)[0][..., -1, None] + logits = logits.masked_fill(indices_to_remove, torch.finfo(logits.dtype).min) + return logits + +def sample_tokens(logits, temperature=0.0, top_p=None, top_k=None, margin_confidence=False, neg_entropy=False): + if temperature > 0: logits = logits / temperature + if top_p is not None and top_p < 1: logits = top_p_logits(logits, top_p) + if top_k is not None: logits = top_k_logits(logits, top_k) + probs = torch.softmax(logits, dim=-1) + if temperature > 0: + try: + x0 = dists.Categorical(probs=probs).sample() + initial_confidence = torch.gather(probs, -1, x0.unsqueeze(-1)).squeeze(-1) + except: initial_confidence, x0 = probs.max(dim=-1) + else: initial_confidence, x0 = probs.max(dim=-1) + confidence = initial_confidence.clone() + if margin_confidence: + sorted_probs, _ = torch.sort(probs, dim=-1, descending=True) + confidence = sorted_probs[:, 0] - sorted_probs[:, 1] + if neg_entropy: + epsilon = 1e-10 + confidence = torch.sum(probs * torch.log(probs + epsilon), dim=-1) + return confidence, x0, initial_confidence + + +class D2FInference: + CSS = """ + .gradio-container { + font-family: -apple-system, BlinkMacSystemFont, sans-serif; + } + .model-header { + font-size: 1.2em; + font-weight: bold; + margin-bottom: 10px; + padding: 8px; + border-radius: 5px; + text-align: center; + } + .d2f-header { + background-color: #DBEAFE; + color: #1E40AF; + } + .llama-header { + background-color: #FEF3C7; + color: #92400E; + } + .stats-container { + padding: 15px; + border: 1px solid #10B981; + border-radius: 8px; + background-color: #F0FDF4; + margin-top: 10px; + margin-bottom: 20px; + } + .output-textbox textarea { + font-size: 1.5em !important; + line-height: 1.6 !important; + height: 70vh !important; + overflow-y: auto !important; + } + """ + + def __init__(self, **kwargs): + print("Initializing D2F-LLaDA model...") + self.device = torch.device(kwargs.get("device", "cuda:3") if torch.cuda.is_available() else "cpu") + self.__dict__.update(kwargs) + if self.dtype == "bfloat16" and torch.cuda.is_bf16_supported(): self.target_dtype = torch.bfloat16 + elif self.dtype == "float16": self.target_dtype = torch.float16 + else: self.target_dtype = torch.float32 + self._setup_model(self.pretrained_path, self.lora_path) + print("D2F-LLaDA model and tokenizer setup complete.") + + def _setup_model(self, pretrained_path, lora_path): + config = LLaDAConfig.from_pretrained(pretrained_path) + self.model = LLaDAModelLM.from_pretrained(pretrained_path, config=config, torch_dtype=self.target_dtype).eval() + self.model = PeftModel.from_pretrained(self.model, lora_path) + self.model = self.model.to(self.device) + self.tokenizer = AutoTokenizer.from_pretrained(pretrained_path) + if self.tokenizer.pad_token is None: self.tokenizer.pad_token = self.tokenizer.eos_token + + def _apply_chat_template(self, prompt): + chat_history = [{"role": "user", "content": prompt}] + return self.tokenizer.apply_chat_template(chat_history, tokenize=False, add_generation_prompt=True) + + def _update_block_completion_states(self, block_states, decoded_token_threshold): + for block_id in sorted(block_states.keys()): + decoded_tokens = block_states[block_id]['total_masks'] - block_states[block_id]['mask_count'] + if block_states[block_id]['total_masks'] > 0: + decode_ratio = decoded_tokens / block_states[block_id]['total_masks'] + if decode_ratio >= decoded_token_threshold: + if (next_block_id := block_id + 1) in block_states: + block_states[next_block_id]['is_complete'] = True + + @torch.inference_mode() + def stream( + self, + prompt_text: str, + max_new_tokens: int, + block_size: int, + block_add_threshold: float, + decoded_token_threshold: float, + skip_threshold: float + ) -> Iterator[Tuple[str, str]]: + + start_time = time.time() + + input_ids = self.tokenizer(self._apply_chat_template(prompt_text), return_tensors="pt").input_ids.to(self.device) + prompt_length = input_ids.shape[1] + + full_attention_mask = create_full_block_attention_mask(prompt_length, self.max_length, block_size, self.device, self.target_dtype) + x_t = input_ids + block_states = {0: {'start_pos': 0, 'end_pos': prompt_length, 'mask_count': 0, 'total_masks': prompt_length, 'state': 'to_cache', 'is_complete': True}} + past_key_values, current_blocks, step, eos_detected, cache_length = None, 0, 0, False, 0 + + yield "", None + + tokens_generated = 0 + + while True: + step += 1 + updated_block_ids = set() + + if len(block_states) - 1 < (max_new_tokens // block_size) and not eos_detected: + last_block_id = max(block_states.keys()) + progress_ratio = (block_states[last_block_id]['total_masks'] - block_states[last_block_id]['mask_count']) / block_states[last_block_id]['total_masks'] if block_states[last_block_id]['total_masks'] > 0 else 1.0 + if progress_ratio >= block_add_threshold: + new_block_id = last_block_id + 1; new_start_pos = x_t.shape[1] + if new_start_pos + block_size <= self.max_length: + x_t = torch.cat([x_t, torch.full((1, block_size), self.mask_token_id, device=self.device, dtype=torch.long)], dim=1) + block_states[new_block_id] = {'start_pos': new_start_pos, 'end_pos': new_start_pos + block_size, 'mask_count': block_size, 'total_masks': block_size, 'state': 'active', 'is_complete': False} + current_blocks += 1 + + self._update_block_completion_states(block_states, decoded_token_threshold) + if (x_t == self.mask_token_id).sum() == 0 and current_blocks == 0: break + + blocks_to_cache = [bid for bid, state in block_states.items() if state['state'] == 'to_cache'] + update_kvcache = 0 + if blocks_to_cache: + start_pos, end_pos = block_states[min(blocks_to_cache)]['start_pos'], block_states[max(blocks_to_cache)]['end_pos'] + update_kvcache = end_pos - start_pos; input_seq, process_start_pos = x_t[:, start_pos:], start_pos + else: + active_blocks = [bid for bid, state in block_states.items() if state['state'] == 'active' and state['start_pos'] >= cache_length] + if not active_blocks: break + start_pos = min(block_states[bid]['start_pos'] for bid in active_blocks); input_seq, process_start_pos = x_t[:, start_pos:], start_pos + + if input_seq.shape[1] == 0: break + + attention_mask = extract_attention_mask(full_mask=full_attention_mask, + start_pos=process_start_pos, + input_length=input_seq.shape[1], + cache_length=cache_length) + + outputs = self.model(input_seq, + attention_bias=attention_mask, + past_key_values=past_key_values, + use_cache=True, + update_kvcache=update_kvcache + cache_length) + + if update_kvcache > 0: + past_key_values = outputs.past_key_values + for bid in blocks_to_cache: + block_states[bid]['state'] = 'in_cache' + + blocks_to_deactivate = [] + for block_id, state in block_states.items(): + if state['state'] != 'active': + continue + + block_mask_locs = (x_t[0, state['start_pos']:state['end_pos']] == self.mask_token_id).nonzero().squeeze(-1) + + if block_mask_locs.numel() == 0: + blocks_to_deactivate.append(block_id) + continue + + logit_offset = state['start_pos'] - process_start_pos + block_mask_logits = outputs.logits[:, logit_offset + block_mask_locs, :] + _, x0, initial_confidence = sample_tokens(block_mask_logits.squeeze(0), self.temperature, self.top_p, self.top_k) + all_indices = (initial_confidence > skip_threshold).nonzero().squeeze(-1) + + if state['is_complete'] and all_indices.numel() == 0 and block_mask_logits.numel() > 0: + all_indices = torch.tensor([torch.argmax(initial_confidence)], device=self.device) + + if all_indices.numel() > 0: + updated_block_ids.add(block_id) + positions_to_update = state['start_pos'] + block_mask_locs[all_indices] + x_t[0, positions_to_update] = x0[all_indices] + state['mask_count'] -= all_indices.numel() + tokens_generated += all_indices.numel() + + if self.tokenizer.eos_token_id in x0[all_indices]: + eos_detected = True + + if state['mask_count'] == 0: + blocks_to_deactivate.append(block_id) + + for bid in blocks_to_deactivate: + if block_states[bid]['state'] == 'active' and all(block_states.get(i, {}).get('state') != 'active' for i in range(bid)): + block_states[bid]['state'] = 'to_cache' + current_blocks -= 1 + + if update_kvcache > 0: + cache_length += update_kvcache + + generated_ids = x_t[0, prompt_length:] + valid_ids = generated_ids[generated_ids != self.mask_token_id] + live_text = self.tokenizer.decode(valid_ids, skip_special_tokens=True) + + yield live_text, None + + total_time = time.time() - start_time + final_generated_ids = x_t[0, prompt_length:] + eos_positions = (final_generated_ids == self.tokenizer.eos_token_id).nonzero() + + if eos_positions.numel() > 0: + final_generated_ids = final_generated_ids[:eos_positions[0, 0] + 1] + + final_text = self.tokenizer.decode(final_generated_ids, skip_special_tokens=True) + + tokens_incl_eos = len(final_generated_ids) + tokens_per_second = tokens_incl_eos / total_time if total_time > 0 else 0 + + stats = { + "total_time": total_time, + "tokens_generated": tokens_incl_eos, + "tokens_per_second": tokens_per_second + } + + if past_key_values is not None: + del past_key_values + del full_attention_mask + torch.cuda.empty_cache() + + yield final_text, stats + + +class LlamaInference: + def __init__(self, **kwargs): + print("Initializing LLaMA model...") + self.device = torch.device(kwargs.get("device", "cuda:4") if torch.cuda.is_available() else "cpu") + self.__dict__.update(kwargs) + self._setup_model(self.model_id) + print("LLaMA model and tokenizer setup complete.") + + def _setup_model(self, model_id): + print(f"Loading LLaMA model {model_id} on {self.device}...") + self.tokenizer = AutoTokenizer.from_pretrained(model_id) + + self.model = AutoModelForCausalLM.from_pretrained( + model_id, + torch_dtype=torch.bfloat16 if torch.cuda.is_bf16_supported() else torch.float16, + device_map=self.device + ).eval() + + if self.tokenizer.eos_token is None: + self.tokenizer.eos_token = "" + + if self.tokenizer.pad_token is None: + self.tokenizer.pad_token = self.tokenizer.eos_token + + def _apply_chat_template(self, prompt): + chat_history = [{"role": "user", "content": prompt}] + return self.tokenizer.apply_chat_template(chat_history, tokenize=False, add_generation_prompt=True) + + @torch.inference_mode() + def stream( + self, + prompt_text: str, + max_new_tokens: int, + temperature: float = 0.0, + top_p: float = 0.9, + top_k: int = None + ) -> Iterator[Tuple[str, str]]: + + start_time = time.time() + + formatted_prompt = self._apply_chat_template(prompt_text) + input_ids = self.tokenizer(formatted_prompt, return_tensors="pt").input_ids.to(self.device) + prompt_length = input_ids.shape[1] + + yield "", None + + tokens_generated = 0 + current_input_ids = input_ids.clone() + + for i in range(max_new_tokens): + with torch.no_grad(): + outputs = self.model(current_input_ids, use_cache=True) + + next_token_logits = outputs.logits[:, -1, :] + + if temperature > 0: + next_token_logits = next_token_logits / temperature + if top_p is not None and top_p < 1: + next_token_logits = top_p_logits(next_token_logits, top_p) + if top_k is not None: + next_token_logits = top_k_logits(next_token_logits, top_k) + probs = torch.softmax(next_token_logits, dim=-1) + next_token = torch.multinomial(probs, num_samples=1) + else: + next_token = torch.argmax(next_token_logits, dim=-1, keepdim=True) + + current_input_ids = torch.cat([current_input_ids, next_token], dim=-1) + tokens_generated += 1 + + if next_token[0, 0].item() == self.tokenizer.eos_token_id: + break + + generated_text = self.tokenizer.decode( + current_input_ids[0, prompt_length:], + skip_special_tokens=True + ) + + yield generated_text, None + + del outputs + + total_time = time.time() - start_time + tokens_per_second = tokens_generated / total_time if total_time > 0 else 0 + + final_text = self.tokenizer.decode(current_input_ids[0, prompt_length:], skip_special_tokens=True) + + stats = { + "total_time": total_time, + "tokens_generated": tokens_generated, + "tokens_per_second": tokens_per_second + } + + del current_input_ids + torch.cuda.empty_cache() + + yield final_text, stats + + +# --- Comparison Helper Functions --- +def create_comparison_html(d2f_results, llama_results): + d_tokens = d2f_results["tokens_generated"] + d_time = d2f_results["total_time"] + d_tokens_per_sec = d2f_results["tokens_per_second"] + + a_tokens = llama_results["tokens_generated"] + a_time = llama_results["total_time"] + a_tokens_per_sec = llama_results["tokens_per_second"] + + if a_tokens_per_sec > 0: + speedup = d_tokens_per_sec / a_tokens_per_sec + else: + speedup = 0 + + comparison_html = f""" +
+

⚡ Performance Comparison

+ + + + + + + + + + + + + + + + + + + + + + + + + +
MetricD2F-LLaDA-Instruct-8BLLaMA3-Instruct-8BDifference
Total tokens{d_tokens}{a_tokens}-
Generation time{d_time:.2f}s{a_time:.2f}s + {"D2F-LLaDA is " + f"{(a_time/d_time):.1f}x faster" if d_time > 0 and d_time < a_time else "LLaMA3 is " + f"{(d_time/a_time):.1f}x faster"} +
Tokens per second{d_tokens_per_sec:.2f}{a_tokens_per_sec:.2f} + {"D2F-LLaDA is " + f"{speedup:.1f}x faster" if speedup > 1 else "LLaMA3 is " + f"{(1/speedup if speedup > 0 else 0):.1f}x faster"} +
+
+ """ + + return comparison_html + + +def create_stats_html(model_name, results): + stats_html = f""" +
+

✓ {model_name} Generation Complete

+ +
+ """ + + return stats_html + + +# --- Main Interface --- +if __name__ == "__main__": + os.environ["CUDA_VISIBLE_DEVICES"] = "3,4" + + torch.cuda.empty_cache() + + d2f_config = { + "pretrained_path": "GSAI-ML/LLaDA-8B-Instruct", + "lora_path": "SJTU-Deng-Lab/D2F_LLaDA_Instruct_8B_Lora", + "device": "cuda:0", + "dtype": "bfloat16", + "max_length": 4096, + "temperature": 0.0, + "top_p": None, + "top_k": None, + "mask_token_id": 126336, + "sampling_strategy": "default", + } + + llama_config = { + "model_id": "meta-llama/Llama-3.1-8B-Instruct", + "device": "cuda:1", + } + + set_seed(42) + + d2f_engine = D2FInference(**d2f_config) + llama_engine = LlamaInference(**llama_config) + + with gr.Blocks(css=D2FInference.CSS, theme=gr.themes.Soft()) as demo: + gr.Markdown("# 🚀 D2F-LLaDA vs LLaMA3: Speed Comparison") + + with gr.Row(): + with gr.Column(scale=1): + prompt_input = gr.Textbox( + label="Enter your question", + placeholder="Example: Natalia sold clips to...", + lines=5 + ) + generate_button = gr.Button("🚀 Run Speed Comparison", variant="primary") + + with gr.Accordion("⚙️ D2F-LLaDA Parameter Settings", open=True): + with gr.Row(): + max_new_tokens_slider = gr.Slider( + minimum=64, maximum=2048, value=1024, step=64, + label="Max Tokens to Generate" + ) + block_size_slider = gr.Slider( + minimum=16, maximum=128, value=32, step=16, + label="Block Size" + ) + with gr.Row(): + block_add_thresh_slider = gr.Slider( + minimum=0.0, maximum=1.0, value=0.1, step=0.05, + label="Block Add Threshold" + ) + decoded_token_thresh_slider = gr.Slider( + minimum=0.0, maximum=1.0, value=0.5, step=0.05, + label="Decoding Completion Threshold" + ) + skip_thresh_slider = gr.Slider( + minimum=0.0, maximum=1.0, value=0.9, step=0.01, + label="Skip Threshold" + ) + + comparison_output = gr.HTML(label="Performance Comparison", elem_id="comparison-container") + + with gr.Row(): + with gr.Column(scale=1): + gr.HTML("
✨ D2F-LLaDA-Instruct-8B (Parallel Decoding)
") + d2f_output = gr.Textbox( + label="D2F-LLaDA Output", + interactive=False, + elem_classes=["output-textbox"] + ) + d2f_status = gr.HTML(label="D2F-LLaDA Stats") + + with gr.Column(scale=1): + gr.HTML("
🔄 LLaMA3-Instruct-8B (Standard)
") + llama_output = gr.Textbox( + label="LLaMA3 Output", + interactive=False, + elem_classes=["output-textbox"] + ) + llama_status = gr.HTML(label="LLaMA3 Stats") + + gr.Examples( + examples=[ + ["Solve the equation x² - 6x + 8 = 0. First, explain what a quadratic equation is and why it can have up to two solutions. Then solve this equation using three different methods: factoring, completing the square, and the quadratic formula. For each method, explain the mathematical reasoning behind it, show all steps in detail, and discuss when this particular method is most useful. Finally, verify your solutions by substituting them back into the original equation.", 1024, 32, 0.1, 0.55, 0.9], + ["A circular swimming pool has a diameter of 8 meters. Calculate the pool's circumference and area. First, explain the relationship between diameter, radius, circumference, and area of a circle, including the role of π in these formulas. Then perform the calculations using π ≈ 3.14159. Next, estimate how much water (in cubic meters) would be needed to fill this pool if it has a uniform depth of 1.5 meters. Finally, calculate how much it would cost to fill this pool if water costs $2.50 per cubic meter. Show all steps and include appropriate units in your answer.", 1024, 32, 0.1, 0.5, 0.9], + ["A movie theater offers a loyalty card that costs $15 and gives a 15% discount on all tickets. If a regular movie ticket costs $10, how many tickets would you need to buy to make the loyalty card worthwhile? First, explain the concept of a break-even point. Then set up an equation to find when the total cost with the card equals the total cost without the card. Solve this equation step by step, showing all your work. Finally, interpret your answer in the context of the problem.", 1024, 32, 0.1, 0.5, 0.9], + ], + inputs=[ + prompt_input, max_new_tokens_slider, block_size_slider, + block_add_thresh_slider, decoded_token_thresh_slider, skip_thresh_slider + ], + label="Examples (Math Problems)" + ) + + def run_models_streaming( + prompt_text, + max_new_tokens, + block_size, + block_add_threshold, + decoded_token_threshold, + skip_threshold + ): + torch.cuda.empty_cache() + + d2f_generator = d2f_engine.stream( + prompt_text=prompt_text, + max_new_tokens=max_new_tokens, + block_size=block_size, + block_add_threshold=block_add_threshold, + decoded_token_threshold=decoded_token_threshold, + skip_threshold=skip_threshold + ) + + llama_generator = llama_engine.stream( + prompt_text=prompt_text, + max_new_tokens=max_new_tokens + ) + + d2f_text = "" + llama_text = "" + d2f_stats = None + llama_stats = None + + yield d2f_text, llama_text, "", "", "" + + d2f_done = False + llama_done = False + + while not (d2f_done and llama_done): + if not d2f_done: + try: + new_d2f_text, new_d2f_stats = next(d2f_generator) + d2f_text = new_d2f_text + if new_d2f_stats is not None: + d2f_stats = new_d2f_stats + d2f_done = True + except StopIteration: + d2f_done = True + + if not llama_done: + try: + new_llama_text, new_llama_stats = next(llama_generator) + llama_text = new_llama_text + if new_llama_stats is not None: + llama_stats = new_llama_stats + llama_done = True + except StopIteration: + llama_done = True + + d2f_status_html = create_stats_html("D2F-LLaDA", d2f_stats) if d2f_stats else "" + llama_status_html = create_stats_html("LLaMA3", llama_stats) if llama_stats else "" + + comparison = "" + if d2f_done and llama_done and d2f_stats and llama_stats: + comparison = create_comparison_html(d2f_stats, llama_stats) + + yield d2f_text, llama_text, d2f_status_html, llama_status_html, comparison + + # MODIFICATION: Removed the _js parameter from here + generate_button.click( + fn=run_models_streaming, + inputs=[ + prompt_input, max_new_tokens_slider, block_size_slider, + block_add_thresh_slider, decoded_token_thresh_slider, skip_thresh_slider + ], + outputs=[ + d2f_output, llama_output, + d2f_status, llama_status, + comparison_output + ] + ) + + # MODIFICATION: Added a hidden HTML component with a script for auto-scrolling + # This method is compatible with older Gradio versions. + gr.HTML( + """ + + """, + visible=False + ) + + demo.queue().launch(share=True) \ No newline at end of file diff --git a/Discrete-Diffusion-Forcing/D2F-eval/generate_llada_demo_block.py b/Discrete-Diffusion-Forcing/D2F-eval/generate_llada_demo_block.py new file mode 100644 index 0000000000000000000000000000000000000000..f78a2394287f45b712808ed61657e022aa5363e5 --- /dev/null +++ b/Discrete-Diffusion-Forcing/D2F-eval/generate_llada_demo_block.py @@ -0,0 +1,630 @@ +import torch +import torch.nn.functional as F +import torch.distributions as dists +import transformers +from transformers import AutoTokenizer +from peft import PeftModel, PeftConfig +import numpy as np +import random +import time +import os +from typing import List, Dict, Optional, Tuple, Iterator, Set +import gradio as gr +import ipdb +# Suppress some Hugging Face warnings +os.environ["TOKENIZERS_PARALLELISM"] = "false" + +# Import necessary model classes +from model_cache.llada.modeling_llada import LLaDAModelLM +from model_cache.llada.configuration_llada import LLaDAConfig + +# --- Helper Functions (Unchanged) --- +def set_seed(seed): + torch.manual_seed(seed); random.seed(seed); np.random.seed(seed); + if torch.cuda.is_available(): torch.cuda.manual_seed_all(seed); torch.backends.cudnn.deterministic = True; torch.backends.cudnn.benchmark = False +def create_full_block_attention_mask(prompt_length, max_length, block_size, device=None, dtype=None): + if dtype is None: dtype = torch.bfloat16 + attention_mask = torch.full((1, 1, max_length, max_length), -torch.inf, device=device, dtype=dtype) + attention_mask[:, :, :prompt_length, :prompt_length] = 0 + remaining_length = max_length - prompt_length + num_blocks = (remaining_length + block_size - 1) // block_size + for b in range(num_blocks): + block_start = prompt_length + b * block_size; block_end = min(prompt_length + (b + 1) * block_size, max_length) + attention_mask[:, :, block_start:block_end, :prompt_length] = 0 + for prev_b in range(b): + prev_start = prompt_length + prev_b * block_size; prev_end = min(prompt_length + (prev_b + 1) * block_size, max_length) + attention_mask[:, :, block_start:block_end, prev_start:prev_end] = 0 + attention_mask[:, :, block_start:block_end, block_start:block_end] = 0 + return attention_mask +def extract_attention_mask(full_mask, start_pos, input_length, cache_length): + end_pos = start_pos + input_length; total_length = cache_length + input_length + extracted_mask = torch.full((1, 1, input_length, total_length), -torch.inf, device=full_mask.device, dtype=full_mask.dtype) + extracted_mask[:, :, :, :cache_length] = full_mask[:, :, start_pos:end_pos, :cache_length] + extracted_mask[:, :, :, cache_length:] = full_mask[:, :, start_pos:end_pos, start_pos:end_pos] + return extracted_mask +def top_p_logits(logits, top_p=None): + sorted_logits, sorted_indices = torch.sort(logits, descending=True) + cumulative_probs = torch.cumsum(F.softmax(sorted_logits, dim=-1), dim=-1) + sorted_indices_to_remove = cumulative_probs > top_p + sorted_indices_to_remove[..., 1:] = sorted_indices_to_remove[..., :-1].clone() + sorted_indices_to_remove[..., 0] = 0 + mask = torch.zeros_like(logits, dtype=torch.bool, device=logits.device) + mask = mask.scatter_(-1, sorted_indices, sorted_indices_to_remove) + logits = logits.masked_fill(mask, torch.finfo(logits.dtype).min) + return logits +def top_k_logits(logits, top_k=None): + top_k = min(top_k, logits.size(-1)) + indices_to_remove = logits < torch.topk(logits, top_k)[0][..., -1, None] + logits = logits.masked_fill(indices_to_remove, torch.finfo(logits.dtype).min) + return logits +def sample_tokens(logits, temperature=0.0, top_p=None, top_k=None, margin_confidence=False, neg_entropy=False): + if temperature > 0: logits = logits / temperature + if top_p is not None and top_p < 1: logits = top_p_logits(logits, top_p) + if top_k is not None: logits = top_k_logits(logits, top_k) + probs = torch.softmax(logits, dim=-1) + if temperature > 0: + try: + x0 = dists.Categorical(probs=probs).sample() + initial_confidence = torch.gather(probs, -1, x0.unsqueeze(-1)).squeeze(-1) + except: initial_confidence, x0 = probs.max(dim=-1) + else: initial_confidence, x0 = probs.max(dim=-1) + confidence = initial_confidence.clone() + if margin_confidence: + sorted_probs, _ = torch.sort(probs, dim=-1, descending=True) + confidence = sorted_probs[:, 0] - sorted_probs[:, 1] + if neg_entropy: + epsilon = 1e-10 + confidence = torch.sum(probs * torch.log(probs + epsilon), dim=-1) + return confidence, x0, initial_confidence + + +class DreamLoRAInference: + CSS = """ + /* Fixed height, scrollable visualization container */ + #viz-container { + height: 500px; + overflow-y: auto !important; + border: 1px solid #E5E7EB; + border-radius: 8px; + padding: 10px; + position: relative; + } + .block-container { + display: inline-block; border: 2px solid transparent; border-radius: 8px; + padding: 5px; margin: 4px 0; transition: border-color 0.3s, box-shadow 0.3s; + } + .block-updating { + border-color: #FF4500 !important; + box-shadow: 0 0 8px rgba(255, 69, 0, 0.7); + } + .token { padding: 2px 4px; margin: 2px; border-radius: 4px; display: inline-block; line-height: 1.4; font-family: monospace; } + .token.prompt { background-color: #E5E7EB; color: #4B5563; } + .token.gen-0 { background-color: #DBEAFE; color: #1E40AF; } /* Blue */ + .token.gen-1 { background-color: #D1FAE5; color: #065F46; } /* Green */ + .token.gen-2 { background-color: #FEF3C7; color: #92400E; } /* Yellow */ + .token.gen-3 { background-color: #FEE2E2; color: #991B1B; } /* Red */ + .token.gen-4 { background-color: #E0E7FF; color: #3730A3; } /* Indigo */ + .token.gen-5 { background-color: #F3E8FF; color: #6B21A8; } /* Purple */ + .token.mask { background-color: #F3F4F6; color: #9CA3AF; border: 1px dashed #D1D5DB; } + + /* Independent status box styles */ + #status-container { + height: 300px; + overflow-y: auto !important; + margin-top: 10px; padding: 15px; border: 1px solid #E5E7EB; border-radius: 8px; background-color: #F9FAFB; + position: relative; + } + #status-container h4 { margin-top: 0; } + .status-line { font-family: monospace; font-size: 13px; margin-bottom: 5px; margin-top: 5px; padding: 2px 4px; border-radius: 3px;} + #stats-output { padding: 15px; border: 1px solid #10B981; border-radius: 8px; background-color: #F0FDF4; margin-top: 10px; } + + /* Scroll anchor */ + .scroll-anchor { + height: 1px; + width: 100%; + } + + /* Force scrollbar styles */ + #viz-container::-webkit-scrollbar, #status-container::-webkit-scrollbar { + width: 10px !important; + background-color: #f5f5f5 !important; + } + #viz-container::-webkit-scrollbar-thumb, #status-container::-webkit-scrollbar-thumb { + background-color: #888 !important; + border-radius: 5px !important; + } + #viz-container::-webkit-scrollbar-track, #status-container::-webkit-scrollbar-track { + background-color: #f5f5f5 !important; + border-radius: 5px !important; + } + + /* Column height alignment */ + .left-column, .right-column { + display: flex; + flex-direction: column; + height: auto !important; + min-height: 800px; + } + + .live-text-container, .viz-status-container { + display: flex; + flex-direction: column; + flex: 1; + overflow: visible; + } + + #live-text-output, #stats-output { + margin-bottom: 20px; + } + + /* Fix for bottom content being cut off */ + .container { + padding-bottom: 40px; + } + + /* Make sure content is fully visible */ + .gradio-container { + overflow-y: visible !important; + } + + /* Add padding to bottom of page */ + .footer { + margin-top: 30px; + padding-bottom: 30px; + } + """ + + def __init__(self, **kwargs): + print("Initializing DreamLoRAInference...") + self.device = torch.device(kwargs.get("device", "cuda") if torch.cuda.is_available() else "cpu") + self.__dict__.update(kwargs) + if self.dtype == "bfloat16" and torch.cuda.is_bf16_supported(): self.target_dtype = torch.bfloat16 + elif self.dtype == "float16": self.target_dtype = torch.float16 + else: self.target_dtype = torch.float32 + self._setup_model(self.pretrained_path, self.lora_path) + print("Model and tokenizer setup complete.") + + def _setup_model(self, pretrained_path, lora_path): + config = LLaDAConfig.from_pretrained(pretrained_path) + self.model = LLaDAModelLM.from_pretrained(pretrained_path, config=config, torch_dtype=self.target_dtype).eval() + self.model = PeftModel.from_pretrained(self.model, lora_path) + self.model = self.model.to(self.device) + self.tokenizer = AutoTokenizer.from_pretrained(pretrained_path) + if self.tokenizer.pad_token is None: self.tokenizer.pad_token = self.tokenizer.eos_token + + def _apply_chat_template(self, prompt): + chat_history = [{"role": "user", "content": prompt}] + return self.tokenizer.apply_chat_template(chat_history, tokenize=False, add_generation_prompt=True) + + def _update_block_completion_states(self, block_states, decoded_token_threshold): + for block_id in sorted(block_states.keys()): + decoded_tokens = block_states[block_id]['total_masks'] - block_states[block_id]['mask_count'] + if block_states[block_id]['total_masks'] > 0: + decode_ratio = decoded_tokens / block_states[block_id]['total_masks'] + if decode_ratio >= decoded_token_threshold: + if (next_block_id := block_id + 1) in block_states: + block_states[next_block_id]['is_complete'] = True + + # Render visualization part (excluding prompt status info) + def _render_visualization_html(self, step: int, x_t: torch.Tensor, block_states: Dict, cache_length: int, updated_block_ids: Set[int]) -> str: + timestamp = int(time.time() * 1000) + + html_parts = [] + for block_id in sorted(k for k in block_states.keys() if k > 0): # Only render generated part (block_id > 0) + state = block_states[block_id] + container_classes = ["block-container"] + if block_id in updated_block_ids: container_classes.append("block-updating") + html_parts.append(f'
') + block_tokens = x_t[0, state['start_pos']:state['end_pos']] + for token_id in block_tokens: + token_id_int = token_id.item() + token_classes = ["token"] + if token_id_int == self.mask_token_id: + token_str = '░'; token_classes.append("mask") + else: + token_str = self.tokenizer.decode([token_id_int], skip_special_tokens=False) + token_str = token_str.replace('&', '&').replace('<', '<').replace('>', '>') + token_classes.append(f"gen-{(block_id - 1) % 6}") + html_parts.append(f'{token_str}') + html_parts.append('
') + + html_parts.append(f'
') + + complete_html = f""" +
+ {''.join(html_parts)} +
+ + + """ + + return complete_html + + # Render status box part (only shows generation block information) + def _render_status_html(self, step: int, block_states: Dict, cache_length: int) -> str: + timestamp = int(time.time() * 1000) + + html_parts = [] + html_parts.append(f'

Generation Block Status (Step: {step}, Cache Length: {cache_length})

') + for block_id in [k for k in sorted(block_states.keys()) if k > 0]: + state = block_states[block_id] + block_type = f"Block {block_id}" + masks_filled = state['total_masks'] - state['mask_count'] + color_class = f"gen-{(block_id - 1) % 6}" + status_line = f'{block_type.ljust(8)}: Pos=[{str(state["start_pos"]).rjust(4)}:{str(state["end_pos"]).ljust(4)}] | State=\'{state["state"].ljust(8)}\' | Filled={str(masks_filled).rjust(2)}/{state["total_masks"]}' + html_parts.append(f'

{status_line}

') + + html_parts.append(f'
') + + complete_html = f""" +
+ {''.join(html_parts)} +
+ + + """ + + return complete_html + + @torch.inference_mode() + def stream_and_capture_for_gradio( + self, + prompt_text: str, + max_new_tokens: int, + block_size: int, + block_add_threshold: float, + decoded_token_threshold: float, + skip_threshold: float + ) -> Iterator[Tuple[str, List[Tuple[str, str]], str, str, str]]: + + start_time = time.time() + captured_frames: List[Tuple[str, str]] = [] + + # Initialization + ipdb.set_trace() + input_ids = self.tokenizer(self._apply_chat_template(prompt_text), return_tensors="pt").input_ids.to(self.device) + prompt_length = input_ids.shape[1] + + full_attention_mask = create_full_block_attention_mask(prompt_length, self.max_length, block_size, self.device, self.target_dtype) + x_t = input_ids + block_states = {0: {'start_pos': 0, 'end_pos': prompt_length, 'mask_count': 0, 'total_masks': prompt_length, 'state': 'to_cache', 'is_complete': True}} + past_key_values, current_blocks, step, eos_detected, cache_length = None, 0, 0, False, 0 + + # Capture initial state + initial_viz_html = self._render_visualization_html(0, x_t, block_states, 0, set()) + initial_status_html = self._render_status_html(0, block_states, 0) + captured_frames.append((initial_viz_html, initial_status_html)) + + yield "", captured_frames, "Initializing generation process...", "Initializing visualization...", "Initializing block status..." + + # Main generation loop + while True: + step += 1 + updated_block_ids: Set[int] = set() + + if len(block_states) - 1 < (max_new_tokens // block_size) and not eos_detected: + last_block_id = max(block_states.keys()) + progress = (block_states[last_block_id]['total_masks'] - block_states[last_block_id]['mask_count']) / block_states[last_block_id]['total_masks'] if block_states[last_block_id]['total_masks'] > 0 else 1.0 + if progress >= block_add_threshold: + new_block_id = last_block_id + 1; new_start_pos = x_t.shape[1] + if new_start_pos + block_size <= self.max_length: + x_t = torch.cat([x_t, torch.full((1, block_size), self.mask_token_id, device=self.device, dtype=torch.long)], dim=1) + block_states[new_block_id] = {'start_pos': new_start_pos, 'end_pos': new_start_pos + block_size, 'mask_count': block_size, 'total_masks': block_size, 'state': 'active', 'is_complete': False} + current_blocks += 1 + + self._update_block_completion_states(block_states, decoded_token_threshold) + if (x_t == self.mask_token_id).sum() == 0 and current_blocks == 0: break + + + + #### D2F-BLOCK #### + blocks_to_cache = [bid for bid, state in block_states.items() if state['state'] == 'to_cache'] + update_kvcache = 0 + if blocks_to_cache: + start_pos, end_pos = block_states[min(blocks_to_cache)]['start_pos'], block_states[max(blocks_to_cache)]['end_pos'] + update_kvcache = end_pos - start_pos; input_seq, process_start_pos = x_t[:, start_pos:], start_pos + else: + active_blocks = [bid for bid, state in block_states.items() if state['state'] == 'active' and state['start_pos'] >= cache_length] + if not active_blocks: break + start_pos = min(block_states[bid]['start_pos'] for bid in active_blocks); input_seq, process_start_pos = x_t[:, start_pos:], start_pos + + if input_seq.shape[1] == 0: break + + attention_mask = extract_attention_mask(full_attention_mask, process_start_pos, input_seq.shape[1], cache_length) + outputs = self.model(input_seq, attention_bias=attention_mask, past_key_values=past_key_values, use_cache=True, update_kvcache=update_kvcache + cache_length) + if update_kvcache > 0: + past_key_values = outputs.past_key_values + for bid in blocks_to_cache: block_states[bid]['state'] = 'in_cache' + + blocks_to_deactivate = [] + for block_id, state in block_states.items(): + if state['state'] != 'active': continue + block_mask_locs = (x_t[0, state['start_pos']:state['end_pos']] == self.mask_token_id).nonzero().squeeze(-1) + if block_mask_locs.numel() == 0: + blocks_to_deactivate.append(block_id); continue + logit_offset = state['start_pos'] - process_start_pos + block_mask_logits = outputs.logits[:, logit_offset + block_mask_locs, :] + _, x0, initial_confidence = sample_tokens(block_mask_logits.squeeze(0), self.temperature, self.top_p, self.top_k) + all_indices = (initial_confidence > skip_threshold).nonzero().squeeze(-1) + if state['is_complete'] and all_indices.numel() == 0 and block_mask_logits.numel() > 0: + all_indices = torch.tensor([torch.argmax(initial_confidence)], device=self.device) + + if all_indices.numel() > 0: + updated_block_ids.add(block_id) + positions_to_update = state['start_pos'] + block_mask_locs[all_indices] + x_t[0, positions_to_update] = x0[all_indices]; state['mask_count'] -= all_indices.numel() + if self.tokenizer.eos_token_id in x0[all_indices]: eos_detected = True + if state['mask_count'] == 0: blocks_to_deactivate.append(block_id) + + for bid in blocks_to_deactivate: + if block_states[bid]['state'] == 'active' and all(block_states.get(i, {}).get('state') != 'active' for i in range(bid)): + block_states[bid]['state'] = 'to_cache'; current_blocks -= 1 + if update_kvcache > 0: cache_length += update_kvcache + + #### FlexMDM Cache Update #### + + + + + + # Capture current step's visualization and status frames + generated_ids = x_t[0, prompt_length:] + valid_ids = generated_ids[generated_ids != self.mask_token_id] + live_text = self.tokenizer.decode(valid_ids, skip_special_tokens=True) + + current_viz_html = self._render_visualization_html(step, x_t, block_states, cache_length, updated_block_ids) + current_status_html = self._render_status_html(step, block_states, cache_length) + captured_frames.append((current_viz_html, current_status_html)) + + yield live_text, captured_frames, "Generating...", "Generating...", "Generating..." + + + + # Final output + total_time = time.time() - start_time + final_generated_ids = x_t[0, prompt_length:] + eos_positions = (final_generated_ids == self.tokenizer.eos_token_id).nonzero() + if eos_positions.numel() > 0: + final_generated_ids = final_generated_ids[:eos_positions[0, 0] + 1] + + final_text = self.tokenizer.decode(final_generated_ids, skip_special_tokens=True) + final_viz_html = self._render_visualization_html(step, x_t, block_states, cache_length, set()) + final_status_html = self._render_status_html(step, block_states, cache_length) + captured_frames.append((final_viz_html, final_status_html)) + + tokens_incl_eos = len(final_generated_ids) + tokens_excl_eos = len(final_generated_ids[final_generated_ids != self.tokenizer.eos_token_id]) + stats_text = f""" + ### ✅ Generation Complete! + --- + - **Total time:** `{total_time:.2f} seconds` + - **Tokens generated (incl. EOS):** `{tokens_incl_eos}` + - **Tokens generated (excl. EOS):** `{tokens_excl_eos}` + - **Tokens per second:** `{(tokens_incl_eos / total_time):.2f}` + """ + + yield final_text, captured_frames, stats_text, "Generation complete, playback starting soon", "Generation complete, playback starting soon" + + +# --- Gradio UI and Event Handlers --- +if __name__ == "__main__": + os.environ["CUDA_VISIBLE_DEVICES"] = "3" + config = { + "pretrained_path": "GSAI-ML/LLaDA-8B-Instruct", + "lora_path": "SJTU-Deng-Lab/D2F_LLaDA_Instruct_8B_Lora", + "device": "cuda", "dtype": "bfloat16", "max_length": 4096, + "temperature": 0.0, "top_p": None, "top_k": None, "mask_token_id": 126336, + "sampling_strategy": "default", + } + set_seed(42) + inference_engine = DreamLoRAInference(**config) + + # Gradio helper for animation + def animate_visualization(html_frames_list: List[Tuple[str, str]], delay: float) -> Iterator[Tuple[str, str]]: + if not html_frames_list: + yield "No visualization data captured", "No status data captured" + return + for viz_frame, status_frame in html_frames_list: + yield viz_frame, status_frame + time.sleep(delay) + + # Global auto-scroll JS + auto_scroll_js = """ + + """ + + with gr.Blocks(css=DreamLoRAInference.CSS, theme=gr.themes.Soft()) as demo: + html_frames_state = gr.State([]) + + gr.Markdown("# ✨ D2F-LLaDA: Real-time Text vs. Slow-motion Visualization") + gr.Markdown("Left side shows real-time streaming output. Right side plays back the decoding process visualization after generation completes.") + + # Inject global auto-scroll JS + gr.HTML(auto_scroll_js) + + with gr.Row(): + # --- Left Column --- + with gr.Column(scale=2, elem_classes=["left-column"]): + prompt_input = gr.Textbox(label="Enter your question", placeholder="Example: Natalia sold clips to...", lines=5) + generate_button = gr.Button("🚀 Generate & Visualize", variant="primary") + with gr.Group(elem_classes=["live-text-container"]): + live_text_output = gr.Textbox(label="Real-time Generation Output", interactive=False, lines=25, elem_id="live-text-output") + stats_output = gr.Markdown(label="Generation Statistics", elem_id="stats-output") + + # --- Right Column --- + with gr.Column(scale=3, elem_classes=["right-column"]): + with gr.Accordion("⚙️ Parameter Settings", open=True): + with gr.Row(): + max_new_tokens_slider = gr.Slider(minimum=64, maximum=2048, value=1024, step=64, label="Max Tokens to Generate") + block_size_slider = gr.Slider(minimum=16, maximum=128, value=32, step=16, label="Block Size") + with gr.Row(): + block_add_thresh_slider = gr.Slider(minimum=0.0, maximum=1.0, value=0.1, step=0.05, label="Block Add Threshold") + decoded_token_thresh_slider = gr.Slider(minimum=0.0, maximum=1.0, value=0.5, step=0.05, label="Decoding Completion Threshold") + skip_thresh_slider = gr.Slider(minimum=0.0, maximum=1.0, value=0.9, step=0.01, label="Skip Threshold") + delay_slider = gr.Slider(minimum=0.0, maximum=1.0, value=0.1, step=0.05, label="Playback Delay (seconds)", info="Adjust visualization playback speed.") + + with gr.Group(elem_classes=["viz-status-container"]): + visualization_output = gr.HTML(label="Generation Process Visualization", elem_id="viz-container") + status_output_html = gr.HTML(label="Generation Block Status", elem_id="status-container") + + gr.Examples( + examples=[ + ["Solve the equation x² - 6x + 8 = 0. First, explain what a quadratic equation is and why it can have up to two solutions. Then solve this equation using three different methods: factoring, completing the square, and the quadratic formula. For each method, explain the mathematical reasoning behind it, show all steps in detail, and discuss when this particular method is most useful. Finally, verify your solutions by substituting them back into the original equation.", 1024, 32, 0.1, 0.55, 0.9, 0.1], + + ["A circular swimming pool has a diameter of 8 meters. Calculate the pool's circumference and area. First, explain the relationship between diameter, radius, circumference, and area of a circle, including the role of π in these formulas. Then perform the calculations using π ≈ 3.14159. Next, estimate how much water (in cubic meters) would be needed to fill this pool if it has a uniform depth of 1.5 meters. Finally, calculate how much it would cost to fill this pool if water costs $2.50 per cubic meter. Show all steps and include appropriate units in your answer.", 1024, 32, 0.1, 0.5, 0.9, 0.1], + + ["A movie theater offers a loyalty card that costs $15 and gives a 15% discount on all tickets. If a regular movie ticket costs $10, how many tickets would you need to buy to make the loyalty card worthwhile? First, explain the concept of a break-even point. Then set up an equation to find when the total cost with the card equals the total cost without the card. Solve this equation step by step, showing all your work. Finally, interpret your answer in the context of the problem.", 1024, 32, 0.1, 0.5, 0.9, 0.1], + ], + inputs=[ + prompt_input, max_new_tokens_slider, block_size_slider, block_add_thresh_slider, + decoded_token_thresh_slider, skip_thresh_slider, delay_slider + ], + label="Examples (Math Problems)" + ) + + # --- Event Handling Chain --- + inputs_list = [ + prompt_input, max_new_tokens_slider, block_size_slider, + block_add_thresh_slider, decoded_token_thresh_slider, skip_thresh_slider + ] + ipdb.set_trace() + generation_event = generate_button.click( + fn=inference_engine.stream_and_capture_for_gradio, + inputs=inputs_list, + outputs=[live_text_output, html_frames_state, stats_output, visualization_output, status_output_html] + ) + + generation_event.then( + fn=animate_visualization, + inputs=[html_frames_state, delay_slider], + outputs=[visualization_output, status_output_html] + ) + + demo.queue().launch(share=True) \ No newline at end of file diff --git a/Discrete-Diffusion-Forcing/D2F-eval/model_cache/dream/configuration_dream.py b/Discrete-Diffusion-Forcing/D2F-eval/model_cache/dream/configuration_dream.py new file mode 100644 index 0000000000000000000000000000000000000000..6a8c49df87fbadb29cf2a570d67b8c770204d147 --- /dev/null +++ b/Discrete-Diffusion-Forcing/D2F-eval/model_cache/dream/configuration_dream.py @@ -0,0 +1,88 @@ + +# coding=utf-8 +# Copyright 2024 The Dream team, HKUNLP Group and the HuggingFace Inc. team. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Dream model configuration""" + +from transformers.configuration_utils import PretrainedConfig +from transformers.modeling_rope_utils import rope_config_validation +from transformers.utils import logging + + +logger = logging.get_logger(__name__) + + +class DreamConfig(PretrainedConfig): + model_type = "Dream" + keys_to_ignore_at_inference = ["past_key_values"] + + def __init__( + self, + vocab_size=151936, + hidden_size=4096, + intermediate_size=22016, + num_hidden_layers=32, + num_attention_heads=32, + num_key_value_heads=32, + hidden_act="silu", + max_position_embeddings=32768, + initializer_range=0.02, + rms_norm_eps=1e-6, + use_cache=False, # cache not used in diffusion + tie_word_embeddings=False, + rope_theta=10000.0, + rope_scaling=None, + use_sliding_window=False, + sliding_window=4096, + max_window_layers=28, + attention_dropout=0.0, + mask_token_id=151666, + pad_token_id=151643, + **kwargs, + ): + self.vocab_size = vocab_size + self.max_position_embeddings = max_position_embeddings + self.hidden_size = hidden_size + self.intermediate_size = intermediate_size + self.num_hidden_layers = num_hidden_layers + self.num_attention_heads = num_attention_heads + self.use_sliding_window = use_sliding_window + self.sliding_window = sliding_window if use_sliding_window else None + self.max_window_layers = max_window_layers + + # for backward compatibility + if num_key_value_heads is None: + num_key_value_heads = num_attention_heads + + self.num_key_value_heads = num_key_value_heads + self.hidden_act = hidden_act + self.initializer_range = initializer_range + self.rms_norm_eps = rms_norm_eps + self.use_cache = use_cache + self.rope_theta = rope_theta + self.rope_scaling = rope_scaling + self.attention_dropout = attention_dropout + # Validate the correctness of rotary position embeddings parameters + # BC: if there is a 'type' field, move it to 'rope_type'. + if self.rope_scaling is not None and "type" in self.rope_scaling: + self.rope_scaling["rope_type"] = self.rope_scaling["type"] + rope_config_validation(self) + + super().__init__( + tie_word_embeddings=tie_word_embeddings, + **kwargs, + ) + self.mask_token_id = mask_token_id + self.pad_token_id = pad_token_id + diff --git a/Discrete-Diffusion-Forcing/D2F-eval/model_cache/dream/generation_utils.py b/Discrete-Diffusion-Forcing/D2F-eval/model_cache/dream/generation_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..ecc30e19744b69b2515b7ff5698b3fa8d84396a8 --- /dev/null +++ b/Discrete-Diffusion-Forcing/D2F-eval/model_cache/dream/generation_utils.py @@ -0,0 +1,463 @@ +# coding=utf-8 +# Copyright 2024 The Dream team, HKUNLP Group and the HuggingFace Inc. team. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import warnings +import copy +from dataclasses import dataclass +from typing import Any, Dict, Optional, Tuple, Union + +import torch +import torch.distributions as dists +from torch.nn import functional as F +from transformers import __version__ +from transformers.generation.configuration_utils import ( + GenerationConfig +) +from transformers.utils import ( + ModelOutput, + is_torchdynamo_compiling, + logging, +) + +logger = logging.get_logger(__name__) + + +def top_p_logits(logits, top_p=None): + sorted_logits, sorted_indices = torch.sort(logits, descending=True) + cumulative_probs = torch.cumsum(F.softmax(sorted_logits, dim=-1), dim=-1) + sorted_indices_to_remove = cumulative_probs > top_p + # Shift the indices to the right to keep the first token above the threshold + sorted_indices_to_remove[..., 1:] = sorted_indices_to_remove[..., :-1].clone() + sorted_indices_to_remove[..., 0] = 0 + + mask = torch.zeros_like(logits, dtype=torch.bool, device=logits.device) + mask = mask.scatter_(-1, sorted_indices, sorted_indices_to_remove) + logits = logits.masked_fill(mask, torch.finfo(logits.dtype).min) + return logits + +def top_k_logits(logits, top_k=None): + top_k = min(top_k, logits.size(-1)) # Safety check + # Remove all tokens with a probability less than the last token of the top-k + indices_to_remove = logits < torch.topk(logits, top_k)[0][..., -1, None] + logits = logits.masked_fill(indices_to_remove, torch.finfo(logits.dtype).min) + return logits + + +def sample_tokens(logits, temperature=0.0, top_p=None, top_k=None, margin_confidence=False, neg_entropy=False): + + if temperature > 0: + logits = logits / temperature + if top_p is not None and top_p < 1: + logits = top_p_logits(logits, top_p) + if top_k is not None: + logits = top_k_logits(logits, top_k) + probs = torch.softmax(logits, dim=-1) + + if temperature > 0: + try: + x0 = dists.Categorical(probs=probs).sample() + confidence = torch.gather(probs, -1, x0.unsqueeze(-1)).squeeze(-1) + except: + confidence, x0 = probs.max(dim=-1) + else: + confidence, x0 = probs.max(dim=-1) + + if margin_confidence: + sorted_probs, _ = torch.sort(probs, dim=-1, descending=True) + # Extract top1 and top2 probabilities + top1_probs = sorted_probs[:, 0] + top2_probs = sorted_probs[:, 1] + # Calculate confidence as top1 - top2 + confidence = top1_probs - top2_probs + + if neg_entropy: + epsilon = 1e-10 + log_probs = torch.log(probs + epsilon) + confidence = torch.sum(probs * log_probs, dim=-1) + + return confidence, x0 + + +@dataclass +class DreamModelOutput(ModelOutput): + sequences: torch.LongTensor = None + history: Optional[Tuple[torch.FloatTensor]] = None + + +class DreamGenerationConfig(GenerationConfig): + def __init__(self, **kwargs): + self.temperature: float = kwargs.pop("temperature", 0.0) + self.top_p: Optional[float] = kwargs.pop("top_p", None) + self.top_k: Optional[int] = kwargs.pop("top_k", None) + self.max_length = kwargs.pop("max_length", 20) + self.max_new_tokens = kwargs.pop("max_new_tokens", None) + # diffusion specific params + self.eps: float = kwargs.pop("eps", 1e-3) + self.steps: int = kwargs.pop("steps", 512) + self.alg: str = kwargs.pop("alg", 'origin') + self.alg_temp: Optional[float] = kwargs.pop("alg_temp", None) + + # Parameters that define the output variables of `generate` + self.num_return_sequences: int = kwargs.pop("num_return_sequences", 1) + self.return_dict_in_generate: bool = kwargs.pop("return_dict_in_generate", False) + self.output_history: bool = kwargs.pop("output_history", False) + + # Special tokens that can be used at generation time + self.mask_token_id = kwargs.pop("mask_token_id", None) + self.pad_token_id = kwargs.pop("pad_token_id", None) + self.bos_token_id = kwargs.pop("bos_token_id", None) + self.eos_token_id = kwargs.pop("eos_token_id", None) + + # Wild card + self.generation_kwargs = kwargs.pop("generation_kwargs", {}) + + # The remaining attributes do not parametrize `.generate()`, but are informative and/or used by the hub + # interface. + self._from_model_config = kwargs.pop("_from_model_config", False) + self._commit_hash = kwargs.pop("_commit_hash", None) + self.transformers_version = kwargs.pop("transformers_version", __version__) + + # Additional attributes without default values + if not self._from_model_config: + # we don't want to copy values from the model config if we're initializing a `GenerationConfig` from a + # model's default configuration file + for key, value in kwargs.items(): + try: + setattr(self, key, value) + except AttributeError as err: + logger.error(f"Can't set {key} with value {value} for {self}") + raise err + + # Validate the values of the attributes + self.validate(is_init=True) + + def validate(self, is_init=False): + pass + +class DreamGenerationMixin: + @staticmethod + def _expand_inputs_for_generation( + expand_size: int = 1, + input_ids: Optional[torch.LongTensor] = None, + attention_mask: Optional[torch.LongTensor] = None + ) -> Tuple[torch.LongTensor, Dict[str, Any]]: + """Expands tensors from [batch_size, ...] to [batch_size * expand_size, ...]""" + # Do not call torch.repeat_interleave if expand_size is 1 because it clones + # the input tensor and thus requires more memory although no change is applied + if expand_size == 1: + return input_ids, attention_mask + if input_ids is not None: + input_ids = input_ids.repeat_interleave(expand_size, dim=0) + if attention_mask is not None: + attention_mask = attention_mask.repeat_interleave(expand_size, dim=0) + return input_ids, attention_mask + + def _validate_generated_length(self, generation_config, input_ids_length, has_default_max_length): + """Performs validation related to the resulting generated length""" + + # Can't throw warnings/exceptions during compilation + if is_torchdynamo_compiling(): + return + + # 1. Max length warnings related to poor parameterization + if has_default_max_length and generation_config.max_new_tokens is None and generation_config.max_length == 20: + # 20 is the default max_length of the generation config + warnings.warn( + f"Using the model-agnostic default `max_length` (={generation_config.max_length}) to control the " + "generation length. We recommend setting `max_new_tokens` to control the maximum length of the " + "generation.", + UserWarning, + ) + if input_ids_length >= generation_config.max_length: + input_ids_string = "input_ids" + raise ValueError( + f"Input length of {input_ids_string} is {input_ids_length}, but `max_length` is set to" + f" {generation_config.max_length}. This can lead to unexpected behavior. You should consider" + " increasing `max_length` or, better yet, setting `max_new_tokens`." + ) + + def _prepare_generated_length( + self, + generation_config, + has_default_max_length, + input_ids_length, + ): + """Prepared max and min length in generation configs to avoid clashes between similar attributes""" + + if generation_config.max_new_tokens is not None: + if not has_default_max_length and generation_config.max_length is not None: + logger.warning( + f"Both `max_new_tokens` (={generation_config.max_new_tokens}) and `max_length`(=" + f"{generation_config.max_length}) seem to have been set. `max_new_tokens` will take precedence. " + "Please refer to the documentation for more information. " + "(https://huggingface.co/docs/transformers/main/en/main_classes/text_generation)" + ) + generation_config.max_length = generation_config.max_new_tokens + input_ids_length + + elif has_default_max_length: + if generation_config.max_length == DreamGenerationConfig().max_length: + generation_config.max_length = generation_config.max_length + input_ids_length + max_position_embeddings = getattr(self.config, "max_position_embeddings", None) + if max_position_embeddings is not None: + generation_config.max_length = min(generation_config.max_length, max_position_embeddings) + + return generation_config + + def _prepare_generation_config( + self, generation_config: Optional[DreamGenerationConfig], **kwargs: Dict + ) -> DreamGenerationConfig: + """ + Prepares the base generation config, then applies any generation configuration options from kwargs. This + function handles retrocompatibility with respect to configuration files. + """ + # priority: `generation_config` argument > `model.generation_config` (the default generation config) + using_model_generation_config = False + if generation_config is None: + generation_config = DreamGenerationConfig.from_model_config(self.config) + using_model_generation_config = True + + # `torch.compile` can't compile `copy.deepcopy`, arguments in `kwargs` that are part of `generation_config` + # will mutate the object with `.update`. As such, passing these arguments through `kwargs` is disabled -- an + # exception will be raised in `_validate_model_kwargs` + if not is_torchdynamo_compiling(): + generation_config = copy.deepcopy(generation_config) + _kwargs = generation_config.update(**kwargs) + # If `generation_config` is provided, let's fallback ALL special tokens to the default values for the model + if not using_model_generation_config: + if generation_config.bos_token_id is None: + generation_config.bos_token_id = self.generation_config.bos_token_id + if generation_config.eos_token_id is None: + generation_config.eos_token_id = self.generation_config.eos_token_id + if generation_config.pad_token_id is None: + generation_config.pad_token_id = self.generation_config.pad_token_id + if generation_config.mask_token_id is None: + generation_config.mask_token_id = self.generation_config.mask_token_id + + return generation_config + + def _prepare_special_tokens( + self, + generation_config: DreamGenerationConfig, + device: Optional[Union[torch.device, str]] = None, + ): + """ + Prepares the special tokens for generation, overwriting the generation config with their processed versions + converted to tensor. + Note that `generation_config` is changed in place and stops being serializable after this method is called. + That is no problem if called within `generate` (`generation_config` is a local copy that doesn't leave the + function). However, if called outside `generate`, consider creating a copy of `generation_config` first. + """ + + # Convert special tokens to tensors + def _tensor_or_none(token, device=None): + if token is None: + return token + + device = device if device is not None else self.device + if isinstance(token, torch.Tensor): + return token.to(device) + return torch.tensor(token, device=device, dtype=torch.long) + + bos_token_tensor = _tensor_or_none(generation_config.bos_token_id, device=device) + eos_token_tensor = _tensor_or_none(generation_config.eos_token_id, device=device) + pad_token_tensor = _tensor_or_none(generation_config.pad_token_id, device=device) + mask_token_tensor = _tensor_or_none(generation_config.mask_token_id, device=device) + + # We can have more than one eos token. Always treat it as a 1D tensor (when it exists). + if eos_token_tensor is not None and eos_token_tensor.ndim == 0: + eos_token_tensor = eos_token_tensor.unsqueeze(0) + + # Set pad token if unset (and there are conditions to do so) + if pad_token_tensor is None and eos_token_tensor is not None: + pad_token_tensor = eos_token_tensor[0] + logger.warning(f"Setting `pad_token_id` to `eos_token_id`:{pad_token_tensor} for open-end generation.") + + # Update generation config with the updated special tokens tensors + # NOTE: this must be written into a different attribute name than the one holding the original special tokens + # (in their non-tensor form), in order to enable end-to-end compilation. See + # https://pytorch.org/docs/stable/torch.compiler_cudagraph_trees.html#limitations + generation_config._bos_token_tensor = bos_token_tensor + generation_config._eos_token_tensor = eos_token_tensor + generation_config._pad_token_tensor = pad_token_tensor + generation_config._mask_token_tensor = mask_token_tensor + + @torch.no_grad() + def diffusion_generate( + self, + inputs: Optional[torch.Tensor] = None, + generation_config: Optional[DreamGenerationConfig] = None, + **kwargs, + ) -> Union[DreamModelOutput, torch.LongTensor]: + # 1. Handle `generation_config` and kwargs that might update it, and validate the `.generate()` call + generation_config = self._prepare_generation_config(generation_config, **kwargs) + generation_tokens_hook_func = kwargs.pop("generation_tokens_hook_func", lambda step, x, logits: x) + generation_logits_hook_func = kwargs.pop("generation_logits_hook_func", lambda step, x, logits: logits) + + # 2. Define model inputs + assert inputs is not None + input_ids = inputs + device = input_ids.device + attention_mask = kwargs.pop("attention_mask", None) + self._prepare_special_tokens(generation_config, device=device) + + # 3. Prepare `max_length`. + input_ids_length = input_ids.shape[-1] + has_default_max_length = kwargs.get("max_length") is None and generation_config.max_length is not None + generation_config = self._prepare_generated_length( + generation_config=generation_config, + has_default_max_length=has_default_max_length, + input_ids_length=input_ids_length, + ) + + self._validate_generated_length(generation_config, input_ids_length, has_default_max_length) + + # 4. Check input_ids + if not is_torchdynamo_compiling() and self.device.type != input_ids.device.type: + warnings.warn( + "You are calling .generate() with the `input_ids` being on a device type different" + f" than your model's device. `input_ids` is on {input_ids.device.type}, whereas the model" + f" is on {self.device.type}. You may experience unexpected behaviors or slower generation." + " Please make sure that you have put `input_ids` to the" + f" correct device by calling for example input_ids = input_ids.to('{self.device.type}') before" + " running `.generate()`.", + UserWarning, + ) + if ( + hasattr(generation_config, "pad_token_id") and + torch.any(input_ids == generation_config.pad_token_id) and + attention_mask is None + ): + warnings.warn( + "Padding was detected but no attention mask is passed here. For correct " + "generation results, please set `attention_mask` when batch-padding inputs.", + UserWarning, + ) + + input_ids, attention_mask = self._expand_inputs_for_generation( + expand_size=generation_config.num_return_sequences, + input_ids=input_ids, + attention_mask=attention_mask + ) + + result = self._sample( + input_ids, + attention_mask=attention_mask, + generation_config=generation_config, + generation_tokens_hook_func=generation_tokens_hook_func, + generation_logits_hook_func=generation_logits_hook_func + ) + return result + + def _sample( + self, + input_ids: torch.LongTensor, + attention_mask: Optional[torch.LongTensor], + generation_config: DreamGenerationConfig, + generation_tokens_hook_func, + generation_logits_hook_func + ) -> Union[DreamModelOutput, torch.LongTensor]: + # init values + output_history = generation_config.output_history + return_dict_in_generate = generation_config.return_dict_in_generate + max_length = generation_config.max_length + mask_token_id = generation_config.mask_token_id + steps = generation_config.steps + eps = generation_config.eps + alg = generation_config.alg + alg_temp = generation_config.alg_temp + temperature = generation_config.temperature + top_p = generation_config.top_p + top_k = generation_config.top_k + + histories = [] if (return_dict_in_generate and output_history) else None + + # pad input_ids to max_length + x = F.pad(input_ids, (0, max_length - input_ids.shape[1]), value=mask_token_id) + + if attention_mask is not None and torch.any(attention_mask == 0.0): + # we do not mask the [MASK] tokens so value = 1.0 + attention_mask = F.pad(attention_mask, (0, max_length - attention_mask.shape[1]), value=1.0) + tok_idx = attention_mask.long().cumsum(-1) - 1 + tok_idx.masked_fill_(attention_mask == 0, 1) + # attention_mask is of shape [B, N] + # broadcast to [B, 1, N, N] + attention_mask = torch.logical_and( + attention_mask.unsqueeze(1).unsqueeze(-2), + attention_mask.unsqueeze(1).unsqueeze(-1), + ) + else: + tok_idx = None + attention_mask = "full" + + timesteps = torch.linspace(1, eps, steps + 1, device=x.device) + + # this allows user-defined token control of the intermediate steps + x = generation_tokens_hook_func(None, x, None) + for i in range(steps): + mask_index = (x == mask_token_id) + logits = self(x, attention_mask, tok_idx).logits + logits = torch.cat([logits[:,:1], logits[:, :-1]], dim=1) + + # this allows user-defined logits control of the intermediate steps + logits = generation_logits_hook_func(i, x, logits) + + mask_logits = logits[mask_index] + t = timesteps[i] + s = timesteps[i + 1] + + if alg == 'origin': + p_transfer = 1 - s / t if i < steps - 1 else 1 + x0 = torch.zeros_like(x[mask_index], device=self.device, dtype=torch.long) + mask_token_id + transfer_index_t_s = torch.rand(*x0.shape, device=self.device) < p_transfer + _, x0[transfer_index_t_s]= sample_tokens(mask_logits[transfer_index_t_s], temperature=temperature, top_p=top_p, top_k=top_k) + x[mask_index] = x0.clone() + else: + if alg == 'maskgit_plus': + confidence, x0 = sample_tokens(mask_logits, temperature=temperature, top_p=top_p, top_k=top_k) + elif alg == 'topk_margin': + confidence, x0 = sample_tokens(mask_logits, temperature=temperature, top_p=top_p, top_k=top_k, margin_confidence=True) + elif alg == 'entropy': + confidence, x0 = sample_tokens(mask_logits, temperature, top_p=top_p, top_k=top_k, neg_entropy=True) + else: + raise RuntimeError(f"Unknown alg: {alg}") + num_mask_token = mask_index.sum() / mask_index.shape[0] + number_transfer_tokens = int(num_mask_token * (1 - s / t)) if i < steps - 1 else int(num_mask_token) + full_confidence = torch.full_like(x, -torch.inf, device=self.device, dtype=logits.dtype) + full_confidence[mask_index] = confidence + if number_transfer_tokens > 0: + if alg_temp is None or alg_temp == 0: + _, transfer_index = torch.topk(full_confidence, number_transfer_tokens) + else: + full_confidence = full_confidence / alg_temp + full_confidence = F.softmax(full_confidence, dim=-1) + transfer_index = torch.multinomial(full_confidence, num_samples=number_transfer_tokens) + x_ = torch.zeros_like(x, device=self.device, dtype=torch.long) + mask_token_id + x_[mask_index] = x0.clone() + row_indices = torch.arange(x.size(0), device=self.device).unsqueeze(1).expand_as(transfer_index) + x[row_indices,transfer_index] = x_[row_indices,transfer_index] + + # this allows user-defined token control of the intermediate steps + x = generation_tokens_hook_func(i, x, logits) + + if histories is not None: + histories.append(x.clone()) + + if return_dict_in_generate: + return DreamModelOutput( + sequences=x, + history=histories, + ) + else: + return x \ No newline at end of file diff --git a/Discrete-Diffusion-Forcing/D2F-eval/model_cache/dream/model_dream.py b/Discrete-Diffusion-Forcing/D2F-eval/model_cache/dream/model_dream.py new file mode 100644 index 0000000000000000000000000000000000000000..058f3b9e939b9fe5dca09809f57c9c19d8a87a20 --- /dev/null +++ b/Discrete-Diffusion-Forcing/D2F-eval/model_cache/dream/model_dream.py @@ -0,0 +1,1029 @@ +# Hugging Face's logo +# Hugging Face +# Models +# Datasets +# Spaces +# Community +# Docs +# Enterprise +# Pricing + + + + +# Dream-org +# / +# Dream-v0-Instruct-7B + +# like +# 94 + +# Follow + +# Dream Org +# 81 +# Feature Extraction +# Transformers +# Safetensors +# Dream +# custom_code + +# License: +# apache-2.0 +# Model card +# Files and versions +# Community +# 2 +# Dream-v0-Instruct-7B +# / +# modeling_dream.py + +# jiacheng-ye's picture +# jiacheng-ye +# Upload model +# 373705a +# verified +# about 2 months ago +# raw + +# Copy download link +# history +# blame +# contribute +# delete + +# 36.8 kB +# # coding=utf-8 +# Copyright 2024 The Dream team, HKUNLP Group and the HuggingFace Inc. team. All rights reserved. +# +# This code is based on EleutherAI's GPT-NeoX library and the GPT-NeoX +# and OPT and Qwen implementations in this library. It has been modified from its +# original forms to accommodate minor architectural differences compared +# to GPT-NeoX and OPT and Qwen used by the Meta AI and Qwen team that trained the model. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""PyTorch Dream model.""" +from transformers import Qwen2Model +from torch.nn.attention.flex_attention import flex_attention +import math +from typing import List, Optional, Tuple, Union +import os +import torch +import torch.utils.checkpoint +from torch import nn + +from transformers.activations import ACT2FN +from transformers.cache_utils import Cache, DynamicCache +from transformers.modeling_outputs import ( + BaseModelOutput, + MaskedLMOutput, + BaseModelOutputWithPast, + CausalLMOutputWithPast +) +from transformers.modeling_rope_utils import ROPE_INIT_FUNCTIONS +from transformers.modeling_utils import PreTrainedModel +from transformers.utils import ( + add_start_docstrings, + add_start_docstrings_to_model_forward, + is_flash_attn_2_available, + is_flash_attn_greater_or_equal_2_10, + logging, +) +from transformers import PretrainedConfig +from model_cache.dream.configuration_dream import DreamConfig +from model_cache.dream.generation_utils import DreamGenerationMixin, DreamGenerationConfig +if is_flash_attn_2_available(): + from transformers.modeling_flash_attention_utils import _flash_attention_forward + + +logger = logging.get_logger(__name__) + +from transformers import Qwen2ForCausalLM +_CHECKPOINT_FOR_DOC = "Dream-7B" +_CONFIG_FOR_DOC = "DreamConfig" + + +# Copied from transformers.models.llama.modeling_llama.LlamaRMSNorm with Llama->Dream +class DreamRMSNorm(nn.Module): + def __init__(self, hidden_size, eps=1e-6): + """ + DreamRMSNorm is equivalent to T5LayerNorm + """ + super().__init__() + self.weight = nn.Parameter(torch.ones(hidden_size)) + self.variance_epsilon = eps + + def forward(self, hidden_states): + input_dtype = hidden_states.dtype + hidden_states = hidden_states.to(torch.float32) + variance = hidden_states.pow(2).mean(-1, keepdim=True) + hidden_states = hidden_states * torch.rsqrt(variance + self.variance_epsilon) + return self.weight * hidden_states.to(input_dtype) + + def extra_repr(self): + return f"{tuple(self.weight.shape)}, eps={self.variance_epsilon}" + + +# Copied from transformers.models.llama.modeling_llama.LlamaRotaryEmbedding with Llama->Dream +class DreamRotaryEmbedding(nn.Module): + def __init__( + self, + dim=None, + max_position_embeddings=2048, + base=10000, + device=None, + scaling_factor=1.0, + rope_type="default", + config: Optional[DreamConfig] = None, + ): + super().__init__() + # TODO (joao): remove the `if` below, only used for BC + self.rope_kwargs = {} + if config is None: + logger.warning_once( + "`DreamRotaryEmbedding` can now be fully parameterized by passing the model config through the " + "`config` argument. All other arguments will be removed in v4.46" + ) + self.rope_kwargs = { + "rope_type": rope_type, + "factor": scaling_factor, + "dim": dim, + "base": base, + "max_position_embeddings": max_position_embeddings, + } + self.rope_type = rope_type + self.max_seq_len_cached = max_position_embeddings + self.original_max_seq_len = max_position_embeddings + else: + # BC: "rope_type" was originally "type" + if config.rope_scaling is not None: + self.rope_type = config.rope_scaling.get("rope_type", config.rope_scaling.get("type")) + else: + self.rope_type = "default" + self.max_seq_len_cached = config.max_position_embeddings + self.original_max_seq_len = config.max_position_embeddings + + self.config = config + self.rope_init_fn = ROPE_INIT_FUNCTIONS[self.rope_type] + + inv_freq, self.attention_scaling = self.rope_init_fn(self.config, device, **self.rope_kwargs) + self.register_buffer("inv_freq", inv_freq, persistent=False) + self.original_inv_freq = self.inv_freq + + def reset_parameters(self): + inv_freq, self.attention_scaling = self.rope_init_fn(self.config, self.inv_freq.device, **self.rope_kwargs) + self.register_buffer("inv_freq", inv_freq, persistent=False) + self.original_inv_freq = self.inv_freq + + + def _dynamic_frequency_update(self, position_ids, device): + """ + dynamic RoPE layers should recompute `inv_freq` in the following situations: + 1 - growing beyond the cached sequence length (allow scaling) + 2 - the current sequence length is in the original scale (avoid losing precision with small sequences) + """ + seq_len = torch.max(position_ids) + 1 + if seq_len > self.max_seq_len_cached: # growth + inv_freq, self.attention_scaling = self.rope_init_fn( + self.config, device, seq_len=seq_len, **self.rope_kwargs + ) + self.register_buffer("inv_freq", inv_freq, persistent=False) # TODO joao: may break with compilation + self.max_seq_len_cached = seq_len + + if seq_len < self.original_max_seq_len and self.max_seq_len_cached > self.original_max_seq_len: # reset + self.register_buffer("inv_freq", self.original_inv_freq, persistent=False) + self.max_seq_len_cached = self.original_max_seq_len + + @torch.no_grad() + def forward(self, x, position_ids): + if "dynamic" in self.rope_type: + self._dynamic_frequency_update(position_ids, device=x.device) + + # Core RoPE block + inv_freq_expanded = self.inv_freq[None, :, None].float().expand(position_ids.shape[0], -1, 1) + position_ids_expanded = position_ids[:, None, :].float() + # Force float32 (see https://github.com/huggingface/transformers/pull/29285) + device_type = x.device.type + device_type = device_type if isinstance(device_type, str) and device_type != "mps" else "cpu" + with torch.autocast(device_type=device_type, enabled=False): + freqs = (inv_freq_expanded.float() @ position_ids_expanded.float()).transpose(1, 2) + emb = torch.cat((freqs, freqs), dim=-1) + cos = emb.cos() + sin = emb.sin() + + # Advanced RoPE types (e.g. yarn) apply a post-processing scaling factor, equivalent to scaling attention + cos = cos * self.attention_scaling + sin = sin * self.attention_scaling + + return cos.to(dtype=x.dtype), sin.to(dtype=x.dtype) + + +# Copied from transformers.models.llama.modeling_llama.rotate_half +def rotate_half(x): + """Rotates half the hidden dims of the input.""" + x1 = x[..., : x.shape[-1] // 2] + x2 = x[..., x.shape[-1] // 2 :] + return torch.cat((-x2, x1), dim=-1) + + +# Copied from transformers.models.llama.modeling_llama.apply_rotary_pos_emb +def apply_rotary_pos_emb(q, k, cos, sin, position_ids=None, unsqueeze_dim=1): + """Applies Rotary Position Embedding to the query and key tensors. + Args: + q (`torch.Tensor`): The query tensor. + k (`torch.Tensor`): The key tensor. + cos (`torch.Tensor`): The cosine part of the rotary embedding. + sin (`torch.Tensor`): The sine part of the rotary embedding. + position_ids (`torch.Tensor`, *optional*): + Deprecated and unused. + unsqueeze_dim (`int`, *optional*, defaults to 1): + The 'unsqueeze_dim' argument specifies the dimension along which to unsqueeze cos[position_ids] and + sin[position_ids] so that they can be properly broadcasted to the dimensions of q and k. For example, note + that cos[position_ids] and sin[position_ids] have the shape [batch_size, seq_len, head_dim]. Then, if q and + k have the shape [batch_size, heads, seq_len, head_dim], then setting unsqueeze_dim=1 makes + cos[position_ids] and sin[position_ids] broadcastable to the shapes of q and k. Similarly, if q and k have + the shape [batch_size, seq_len, heads, head_dim], then set unsqueeze_dim=2. + Returns: + `tuple(torch.Tensor)` comprising of the query and key tensors rotated using the Rotary Position Embedding. + """ + cos = cos.unsqueeze(unsqueeze_dim) + sin = sin.unsqueeze(unsqueeze_dim) + q_embed = (q * cos) + (rotate_half(q) * sin) + k_embed = (k * cos) + (rotate_half(k) * sin) + return q_embed, k_embed + + +# Copied from transformers.models.mistral.modeling_mistral.MistralMLP with Mistral->Dream +class DreamMLP(nn.Module): + def __init__(self, config): + super().__init__() + self.hidden_size = config.hidden_size + self.intermediate_size = config.intermediate_size + self.gate_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=False) + self.up_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=False) + self.down_proj = nn.Linear(self.intermediate_size, self.hidden_size, bias=False) + self.act_fn = ACT2FN[config.hidden_act] + + def forward(self, hidden_state): + return self.down_proj(self.act_fn(self.gate_proj(hidden_state)) * self.up_proj(hidden_state)) + + +# Copied from transformers.models.llama.modeling_llama.repeat_kv +def repeat_kv(hidden_states: torch.Tensor, n_rep: int) -> torch.Tensor: + """ + This is the equivalent of torch.repeat_interleave(x, dim=1, repeats=n_rep). The hidden states go from (batch, + num_key_value_heads, seqlen, head_dim) to (batch, num_attention_heads, seqlen, head_dim) + """ + batch, num_key_value_heads, slen, head_dim = hidden_states.shape + if n_rep == 1: + return hidden_states + hidden_states = hidden_states[:, :, None, :, :].expand(batch, num_key_value_heads, n_rep, slen, head_dim) + return hidden_states.reshape(batch, num_key_value_heads * n_rep, slen, head_dim) + + +class DreamAttention(nn.Module): + """ + Multi-headed attention from 'Attention Is All You Need' paper. Modified to use sliding window attention: Longformer + and "Generating Long Sequences with Sparse Transformers". + """ + + def __init__(self, config: DreamConfig, layer_idx: Optional[int] = None): + super().__init__() + self.config = config + self.layer_idx = layer_idx + if layer_idx is None: + logger.warning_once( + f"Instantiating {self.__class__.__name__} without passing `layer_idx` is not recommended and will " + "to errors during the forward call, if caching is used. Please make sure to provide a `layer_idx` " + "when creating this class." + ) + + self.hidden_size = config.hidden_size + self.num_heads = config.num_attention_heads + self.head_dim = self.hidden_size // self.num_heads + self.num_key_value_heads = config.num_key_value_heads + self.num_key_value_groups = self.num_heads // self.num_key_value_heads + self.max_position_embeddings = config.max_position_embeddings + self.rope_theta = config.rope_theta + self.is_causal = False + self.attention_dropout = config.attention_dropout + + if (self.head_dim * self.num_heads) != self.hidden_size: + raise ValueError( + f"hidden_size must be divisible by num_heads (got `hidden_size`: {self.hidden_size}" + f" and `num_heads`: {self.num_heads})." + ) + self.q_proj = nn.Linear(self.hidden_size, self.num_heads * self.head_dim, bias=True) + self.k_proj = nn.Linear(self.hidden_size, self.num_key_value_heads * self.head_dim, bias=True) + self.v_proj = nn.Linear(self.hidden_size, self.num_key_value_heads * self.head_dim, bias=True) + self.o_proj = nn.Linear(self.num_heads * self.head_dim, self.hidden_size, bias=False) + + self.rotary_emb = DreamRotaryEmbedding(config=self.config) + + def forward( + self, + hidden_states: torch.Tensor, + attention_mask: Optional[torch.Tensor] = None, + position_ids: Optional[torch.LongTensor] = None, + past_key_value: Optional[Cache] = None, + output_attentions: bool = False, + use_cache: bool = False, + cache_position: Optional[torch.LongTensor] = None, + position_embeddings: Optional[Tuple[torch.Tensor, torch.Tensor]] = None, # will become mandatory in v4.46 + ) -> Tuple[torch.Tensor, Optional[torch.Tensor], Optional[Tuple[torch.Tensor]]]: + bsz, q_len, _ = hidden_states.size() + + query_states = self.q_proj(hidden_states) + key_states = self.k_proj(hidden_states) + value_states = self.v_proj(hidden_states) + + query_states = query_states.view(bsz, q_len, self.num_heads, self.head_dim).transpose(1, 2) + key_states = key_states.view(bsz, q_len, self.num_key_value_heads, self.head_dim).transpose(1, 2) + value_states = value_states.view(bsz, q_len, self.num_key_value_heads, self.head_dim).transpose(1, 2) + + if position_embeddings is None: + logger.warning_once( + "The attention layers in this model are transitioning from computing the RoPE embeddings internally " + "through `position_ids` (2D tensor with the indexes of the tokens), to using externally computed " + "`position_embeddings` (Tuple of tensors, containing cos and sin). In v4.46 `position_ids` will be " + "removed and `position_embeddings` will be mandatory." + ) + cos, sin = self.rotary_emb(value_states, position_ids) + else: + cos, sin = position_embeddings + query_states, key_states = apply_rotary_pos_emb(query_states, key_states, cos, sin) + + if past_key_value is not None: + cache_kwargs = {"sin": sin, "cos": cos, "cache_position": cache_position} # Specific to RoPE models + key_states, value_states = past_key_value.update(key_states, value_states, self.layer_idx, cache_kwargs) + + # repeat k/v heads if n_kv_heads < n_heads + key_states = repeat_kv(key_states, self.num_key_value_groups) + value_states = repeat_kv(value_states, self.num_key_value_groups) + + attn_weights = torch.matmul(query_states, key_states.transpose(2, 3)) / math.sqrt(self.head_dim) + if attention_mask is not None: # no matter the length, we just slice it + causal_mask = attention_mask[:, :, :, : key_states.shape[-2]] + attn_weights = attn_weights + causal_mask + + # upcast attention to fp32 + attn_weights = nn.functional.softmax(attn_weights, dim=-1, dtype=torch.float32).to(query_states.dtype) + attn_weights = nn.functional.dropout(attn_weights, p=self.attention_dropout, training=self.training) + attn_output = torch.matmul(attn_weights, value_states) + + if attn_output.size() != (bsz, self.num_heads, q_len, self.head_dim): + raise ValueError( + f"`attn_output` should be of size {(bsz, self.num_heads, q_len, self.head_dim)}, but is" + f" {attn_output.size()}" + ) + + attn_output = attn_output.transpose(1, 2).contiguous() + attn_output = attn_output.reshape(bsz, q_len, self.hidden_size) + + attn_output = self.o_proj(attn_output) + + if not output_attentions: + attn_weights = None + + return attn_output, attn_weights, past_key_value + + +class DreamSdpaAttention(DreamAttention): + """ + Dream attention module using torch.nn.functional.scaled_dot_product_attention. This module inherits from + `DreamAttention` as the weights of the module stays untouched. The only changes are on the forward pass to adapt to + SDPA API. + """ + + # Adapted from DreamAttention.forward + def forward( + self, + hidden_states: torch.Tensor, + attention_mask: Optional[torch.Tensor] = None, + update_kvcache: torch.int32 = None, + position_ids: Optional[torch.LongTensor] = None, + past_key_value: Optional[Cache] = None, + output_attentions: bool = False, + use_cache: bool = False, + cache_position: Optional[torch.LongTensor] = None, + position_embeddings: Optional[Tuple[torch.Tensor, torch.Tensor]] = None, # will become mandatory in v4.46 + ) -> Tuple[torch.Tensor, Optional[torch.Tensor], Optional[Tuple[torch.Tensor]]]: + if output_attentions: + # TODO: Improve this warning with e.g. `model.config.attn_implementation = "manual"` once this is implemented. + logger.warning_once( + "DreamModel is using DreamSdpaAttention, but `torch.nn.functional.scaled_dot_product_attention` does not support `output_attentions=True`. Falling back to the manual attention implementation, " + 'but specifying the manual implementation will be required from Transformers version v5.0.0 onwards. This warning can be removed using the argument `attn_implementation="eager"` when loading the model.' + ) + return super().forward( + hidden_states=hidden_states, + attention_mask=attention_mask, + position_ids=position_ids, + past_key_value=past_key_value, + output_attentions=output_attentions, + use_cache=use_cache, + ) + + bsz, q_len, _ = hidden_states.size() + + query_states = self.q_proj(hidden_states) + key_states = self.k_proj(hidden_states) + value_states = self.v_proj(hidden_states) + + query_states = query_states.view(bsz, q_len, self.num_heads, self.head_dim).transpose(1, 2) + key_states = key_states.view(bsz, q_len, self.num_key_value_heads, self.head_dim).transpose(1, 2) + value_states = value_states.view(bsz, q_len, self.num_key_value_heads, self.head_dim).transpose(1, 2) + + if position_embeddings is None: + logger.warning_once( + "The attention layers in this model are transitioning from computing the RoPE embeddings internally " + "through `position_ids` (2D tensor with the indexes of the tokens), to using externally computed " + "`position_embeddings` (Tuple of tensors, containing cos and sin). In v4.46 `position_ids` will be " + "removed and `position_embeddings` will be mandatory." + ) + cos, sin = self.rotary_emb(value_states, position_ids) + else: + cos, sin = position_embeddings + query_states, key_states = apply_rotary_pos_emb(query_states, key_states, cos, sin) + + if past_key_value is not None: + cache_kwargs = {"sin": sin, "cos": cos, "cache_position": cache_position} # Specific to RoPE models + key_states, value_states = past_key_value.update(key_states, value_states, self.layer_idx, cache_kwargs) + + key_states = repeat_kv(key_states, self.num_key_value_groups) + value_states = repeat_kv(value_states, self.num_key_value_groups) + + # causal_mask = attention_mask + # if attention_mask is not None: # no matter the length, we just slice it + # causal_mask = attention_mask[:, :, :, : key_states.shape[-2]] + + # SDPA with memory-efficient backend is currently (torch==2.1.2) bugged with non-contiguous inputs with custom attn_mask, + # Reference: https://github.com/pytorch/pytorch/issues/112577. + if query_states.device.type == "cuda" and attention_mask is not None: + query_states = query_states.contiguous() + key_states = key_states.contiguous() + value_states = value_states.contiguous() + + # We dispatch to SDPA's Flash Attention or Efficient kernels via this `is_causal` if statement instead of an inline conditional assignment + # in SDPA to support both torch.compile's dynamic shapes and full graph options. An inline conditional prevents dynamic shapes from compiling. + # The q_len > 1 is necessary to match with AttentionMaskConverter.to_causal_4d that does not create a causal mask in case q_len == 1. + # is_causal = True if causal_mask is None and q_len > 1 else False + + attn_output = torch.nn.functional.scaled_dot_product_attention( + query_states, + key_states, + value_states, + attn_mask=attention_mask if attention_mask is not None else None, + dropout_p=self.attention_dropout if self.training else 0.0, + is_causal=False, # hard coded + ) + + attn_output = attn_output.transpose(1, 2).contiguous() + attn_output = attn_output.view(bsz, q_len, self.hidden_size) + + attn_output = self.o_proj(attn_output) + + return attn_output, None, past_key_value +class DreamFlexAttention(DreamAttention): + """ + Dream attention module using torch.nn.functional.scaled_dot_product_attention. This module inherits from + `DreamAttention` as the weights of the module stays untouched. The only changes are on the forward pass to adapt to + SDPA API. + """ + + # Adapted from DreamAttention.forward + def forward( + self, + hidden_states: torch.Tensor, + attention_mask: Optional[torch.Tensor] = None, + update_kvcache: torch.int32 = None, + position_ids: Optional[torch.LongTensor] = None, + past_key_value: Optional[Cache] = None, + output_attentions: bool = False, + use_cache: bool = False, + cache_position: Optional[torch.LongTensor] = None, + position_embeddings: Optional[Tuple[torch.Tensor, torch.Tensor]] = None, # will become mandatory in v4.46 + ) -> Tuple[torch.Tensor, Optional[torch.Tensor], Optional[Tuple[torch.Tensor]]]: + if output_attentions: + # TODO: Improve this warning with e.g. `model.config.attn_implementation = "manual"` once this is implemented. + logger.warning_once( + "DreamModel is using DreamSdpaAttention, but `torch.nn.functional.scaled_dot_product_attention` does not support `output_attentions=True`. Falling back to the manual attention implementation, " + 'but specifying the manual implementation will be required from Transformers version v5.0.0 onwards. This warning can be removed using the argument `attn_implementation="eager"` when loading the model.' + ) + return super().forward( + hidden_states=hidden_states, + attention_mask=attention_mask, + position_ids=position_ids, + past_key_value=past_key_value, + output_attentions=output_attentions, + use_cache=use_cache, + ) + # print("hidden_states",hidden_states) + bsz, q_len, _ = hidden_states.size() + + query_states = self.q_proj(hidden_states) + key_states = self.k_proj(hidden_states) + value_states = self.v_proj(hidden_states) + + query_states = query_states.view(bsz, q_len, self.num_heads, self.head_dim).transpose(1, 2) + key_states = key_states.view(bsz, q_len, self.num_key_value_heads, self.head_dim).transpose(1, 2) + value_states = value_states.view(bsz, q_len, self.num_key_value_heads, self.head_dim).transpose(1, 2) + + if position_embeddings is None: + logger.warning_once( + "The attention layers in this model are transitioning from computing the RoPE embeddings internally " + "through `position_ids` (2D tensor with the indexes of the tokens), to using externally computed " + "`position_embeddings` (Tuple of tensors, containing cos and sin). In v4.46 `position_ids` will be " + "removed and `position_embeddings` will be mandatory." + ) + cos, sin = self.rotary_emb(value_states, position_ids) + else: + cos, sin = position_embeddings + # print(query_states.shape,key_states.shape,cos.shape,sin.shape) + query_states, key_states = apply_rotary_pos_emb(query_states, key_states, cos, sin) + # print("k,v",key_states.shape,value_states.shape,past_key_value) + # print(cos.shape,sin.shape,cache_position.shape) + if past_key_value is not None: + if update_kvcache == 0: + past_key_states, past_value_states = past_key_value[self.layer_idx] + key_states=torch.cat([past_key_states, key_states], dim=2) + value_states=torch.cat([past_value_states, value_states], dim=2) + # Specific to RoPE models + else: + cache_kwargs = {"sin": sin[:,:update_kvcache,:], "cos": cos[:,:update_kvcache,:], "cache_position": cache_position[:update_kvcache]} + # print("update_kvcache",update_kvcache) + new_key_states, new_value_states = past_key_value.update(key_states[:,:,:update_kvcache, :], value_states[:,:,:update_kvcache, : ], self.layer_idx, cache_kwargs) + # print("new_kv",new_key_states.shape,new_value_states.shape) + # print("k,v",new_key_states.shape,new_value_states.shape) + key_states = torch.cat([new_key_states,key_states[:,:,update_kvcache:,:]], dim=2) + value_states = torch.cat([new_value_states,value_states[:,:,update_kvcache:,:]], dim=2) + # print("k,v",key_states.shape,value_states.shape) + # print(key_states.shape,value_states.shape) + key_states = repeat_kv(key_states, self.num_key_value_groups) + value_states = repeat_kv(value_states, self.num_key_value_groups) + + # causal_mask = attention_mask + if attention_mask is not None: # no matter the length, we just slice it + atte_mask = attention_mask[:,:, :, : key_states.shape[-2]].clone() + # print(update_kvcache,attention_mask.shape) + # if attention_mask.shape[3]>86+32: + # if attention_mask.shape[-1]!=attention_mask.shape[-2]: + # atte_mask[:,:,:update_kvcache,-update_kvcache:]=-torch.inf + + # if update_kvcache > 0: + # print("attention_mask中出现过的值",atte_mask.unique()) + # print('tTTTTTTTTT') + # print("-"*20) + # print("attention_mask",attention_mask,update_kvcache) + # print(attention_mask) + # exit() + # print(attention_mask[0,0,:,:],attention_mask[0,0,:,:].shape) + # exit(0) + + # SDPA with memory-efficient backend is currently (torch==2.1.2) bugged with non-contiguous inputs with custom attn_mask, + # Reference: https://github.com/pytorch/pytorch/issues/112577. + if query_states.device.type == "cuda" and attention_mask is not None: + query_states = query_states.contiguous() + key_states = key_states.contiguous() + value_states = value_states.contiguous() + + # We dispatch to SDPA's Flash Attention or Efficient kernels via this `is_causal` if statement instead of an inline conditional assignment + # in SDPA to support both torch.compile's dynamic shapes and full graph options. An inline conditional prevents dynamic shapes from compiling. + # The q_len > 1 is necessary to match with AttentionMaskConverter.to_causal_4d that does not create a causal mask in case q_len == 1. + # is_causal = True if causal_mask is None and q_len > 1 else False + # print(query_states.shape[2], key_states.shape[2]) + # attention_mask=attention_mask[:,:, :key_states.shape[2], :key_states.shape[2]] if attention_mask is not None else None + # attn_output = flex_attention(query_states, key_states, value_states, block_mask= attention_mask ), + # print(query_states.shape, key_states.shape, value_states.shape, attention_mask.shape if attention_mask is not None else None) + # print(query_states.dtype,attention_mask.dtype if attention_mask is not None else None) + # print(self.training) + # print("key_states",key_states[:,:,:84,:]) + # torch.save(key_states,"key_states1.pt") + # torch.save(value_states,"value_states1.pt") + # torch.save(value_states,"query_state1.pt") + # torch.save(attention_mask,"attention_mask1.pt") + # print(atte_mask.shape) + attn_output = torch.nn.functional.scaled_dot_product_attention( + query_states, + key_states, + value_states, + attn_mask=atte_mask if attention_mask is not None else None, + dropout_p=self.attention_dropout if self.training else 0.0, + is_causal=False, # hard coded + ) + # print("attn_output",attn_output[:,:,:84,:],attn_output.shape) + # print(atte_mask[:,:,:84,:84],attenti_mask.shape) + # exit() + # if self.layer_idx==2: + # torch.save(attn_output,"attn_output2.pt") + # exit() + attn_output = attn_output.transpose(1, 2).contiguous() + attn_output = attn_output.view(bsz, q_len, self.hidden_size) + + attn_output = self.o_proj(attn_output) + + return attn_output, None, past_key_value + +class DreamDecoderLayer(nn.Module): + def __init__(self, config: DreamConfig, layer_idx: int): + super().__init__() + self.hidden_size = config.hidden_size + + if config.sliding_window and config._attn_implementation != "flash_attention_2": + logger.warning_once( + f"Sliding Window Attention is enabled but not implemented for `{config._attn_implementation}`; " + "unexpected results may be encountered." + ) + + # self.self_attn = Dream_ATTENTION_CLASSES[config._attn_implementation](config, layer_idx) + self.self_attn = DreamFlexAttention(config, layer_idx) + + self.mlp = DreamMLP(config) + self.input_layernorm = DreamRMSNorm(config.hidden_size, eps=config.rms_norm_eps) + self.post_attention_layernorm = DreamRMSNorm(config.hidden_size, eps=config.rms_norm_eps) + + def forward( + self, + hidden_states: torch.Tensor, + update_kvcache: torch.int32 = None, + attention_mask: Optional[torch.Tensor] = None, + position_ids: Optional[torch.LongTensor] = None, + past_key_value: Optional[Tuple[torch.Tensor]] = None, + output_attentions: Optional[bool] = False, + use_cache: Optional[bool] = False, + cache_position: Optional[torch.LongTensor] = None, + position_embeddings: Optional[Tuple[torch.Tensor, torch.Tensor]] = None, # will become mandatory in v4.46 + **kwargs, + ) -> Tuple[torch.FloatTensor, Optional[Tuple[torch.FloatTensor, torch.FloatTensor]]]: + """ + Args: + hidden_states (`torch.FloatTensor`): input to the layer of shape `(batch, seq_len, embed_dim)` + attention_mask (`torch.FloatTensor`, *optional*): attention mask of size + `(batch, sequence_length)` where padding elements are indicated by 0. + output_attentions (`bool`, *optional*): + Whether or not to return the attentions tensors of all attention layers. See `attentions` under + returned tensors for more detail. + use_cache (`bool`, *optional*): + If set to `True`, `past_key_values` key value states are returned and can be used to speed up decoding + (see `past_key_values`). + past_key_value (`Tuple(torch.FloatTensor)`, *optional*): cached past key and value projection states + cache_position (`torch.LongTensor` of shape `(sequence_length)`, *optional*): + Indices depicting the position of the input sequence tokens in the sequence. + position_embeddings (`Tuple[torch.FloatTensor, torch.FloatTensor]`, *optional*): + Tuple containing the cosine and sine positional embeddings of shape `(batch_size, seq_len, head_dim)`, + with `head_dim` being the embedding dimension of each attention head. + kwargs (`dict`, *optional*): + Arbitrary kwargs to be ignored, used for FSDP and other methods that injects code + into the model + """ + + residual = hidden_states + + hidden_states = self.input_layernorm(hidden_states) + + # Self Attention + hidden_states, self_attn_weights, present_key_value = self.self_attn( + hidden_states=hidden_states, + attention_mask=attention_mask, + update_kvcache=update_kvcache, + position_ids=position_ids, + past_key_value=past_key_value, + output_attentions=output_attentions, + use_cache=use_cache, + cache_position=cache_position, + position_embeddings=position_embeddings, + ) + hidden_states = residual + hidden_states + + # Fully Connected + residual = hidden_states + hidden_states = self.post_attention_layernorm(hidden_states) + hidden_states = self.mlp(hidden_states) + hidden_states = residual + hidden_states + + outputs = (hidden_states,) + + if output_attentions: + outputs += (self_attn_weights,) + + if use_cache: + outputs += (present_key_value,) + + return outputs + +class DreamPreTrainedModel(PreTrainedModel): + config_class = DreamConfig + base_model_prefix = "model" + supports_gradient_checkpointing = True + _no_split_modules = ["DreamDecoderLayer"] + _skip_keys_device_placement = "past_key_values" + _supports_flash_attn_2 = True + _supports_sdpa = True + _supports_cache_class = True + _supports_quantized_cache = True + _supports_static_cache = True + + def _init_weights(self, module): + std = self.config.initializer_range + if isinstance(module, nn.Linear): + module.weight.data.normal_(mean=0.0, std=std) + if module.bias is not None: + module.bias.data.zero_() + elif isinstance(module, nn.Embedding): + module.weight.data.normal_(mean=0.0, std=std) + if module.padding_idx is not None: + module.weight.data[module.padding_idx].zero_() + + @classmethod + def from_pretrained( + cls, + pretrained_model_name_or_path: Optional[Union[str, os.PathLike]], + *model_args, + config: Optional[Union[PretrainedConfig, str, os.PathLike]] = None, + cache_dir: Optional[Union[str, os.PathLike]] = None, + ignore_mismatched_sizes: bool = False, + force_download: bool = False, + local_files_only: bool = False, + token: Optional[Union[str, bool]] = None, + revision: str = "main", + use_safetensors: Optional[bool] = None, + weights_only: bool = True, + **kwargs, + ): + _model = super().from_pretrained( + pretrained_model_name_or_path, + *model_args, + config=config, + cache_dir=cache_dir, + ignore_mismatched_sizes=ignore_mismatched_sizes, + force_download=force_download, + local_files_only=local_files_only, + token=token, + revision=revision, + use_safetensors=use_safetensors, + weights_only=weights_only, + **kwargs, + ) + # NOTE(Lin): we need to override the generation config + # because the generation config loaded in `from_pretrained` + # does not include all the attributes of DreamGenerationConfig + resume_download = kwargs.get("resume_download", None) + proxies = kwargs.get("proxies", None) + subfolder = kwargs.get("subfolder", "") + from_auto_class = kwargs.get("_from_auto", False) + from_pipeline = kwargs.get("_from_pipeline", None) + _model.generation_config = DreamGenerationConfig.from_pretrained( + pretrained_model_name_or_path, + cache_dir=cache_dir, + force_download=force_download, + resume_download=resume_download, + proxies=proxies, + local_files_only=local_files_only, + token=token, + revision=revision, + subfolder=subfolder, + _from_auto=from_auto_class, + _from_pipeline=from_pipeline, + ) + return _model + +class DreamBaseModel(DreamPreTrainedModel): + """ + Transformer decoder consisting of *config.num_hidden_layers* layers. Each layer is a [`DreamDecoderLayer`] + Args: + config: DreamConfig + """ + + def __init__(self, config: DreamConfig): + super().__init__(config) + self.padding_idx = config.pad_token_id + self.vocab_size = config.vocab_size + + self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size, self.padding_idx) + self.layers = nn.ModuleList( + [DreamDecoderLayer(config, layer_idx) for layer_idx in range(config.num_hidden_layers)] + ) + self._attn_implementation = config._attn_implementation + self.norm = DreamRMSNorm(config.hidden_size, eps=config.rms_norm_eps) + self.rotary_emb = DreamRotaryEmbedding(config=config) + + self.gradient_checkpointing = False + # Initialize weights and apply final processing + self.post_init() + + def get_input_embeddings(self): + return self.embed_tokens + + def set_input_embeddings(self, value): + self.embed_tokens = value + + def forward( + self, + input_ids: torch.LongTensor = None, + update_kvcache: torch.int32 = None, + attention_mask: Optional[torch.Tensor] = None, + position_ids: Optional[torch.LongTensor] = None, + past_key_values: Optional[List[torch.FloatTensor]] = None, + inputs_embeds: Optional[torch.FloatTensor] = None, + use_cache: Optional[bool] = None, + output_attentions: Optional[bool] = None, + output_hidden_states: Optional[bool] = None, + return_dict: Optional[bool] = None, + cache_position: Optional[torch.LongTensor] = None, + ) -> Union[Tuple, BaseModelOutputWithPast]: + output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions + output_hidden_states = ( + output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states + ) + use_cache = use_cache if use_cache is not None else self.config.use_cache + + return_dict = return_dict if return_dict is not None else self.config.use_return_dict + + if (input_ids is None) ^ (inputs_embeds is not None): + raise ValueError("You must specify exactly one of input_ids or inputs_embeds") + + if self.gradient_checkpointing and self.training: + if use_cache: + logger.warning_once( + "`use_cache=True` is incompatible with gradient checkpointing. Setting `use_cache=False`..." + ) + use_cache = False + + if inputs_embeds is None: + # past_seen_tokens = past_key_values.get_seq_length() if past_key_values is not None else 0 + # input_ids = input_ids[:, past_seen_tokens:] + inputs_embeds = self.embed_tokens(input_ids) + # print("inputs_embeds",inputs_embeds.shape) + + if use_cache and past_key_values is None: + past_key_values = DynamicCache() + + if cache_position is None: + past_seen_tokens = past_key_values.get_seq_length() if past_key_values is not None else 0 + cache_position = torch.arange( + past_seen_tokens, past_seen_tokens + inputs_embeds.shape[1], device=inputs_embeds.device + ) + + if position_ids is None: + position_ids = cache_position.unsqueeze(0) + + hidden_states = inputs_embeds + + # create position embeddings to be shared across the decoder layers + position_embeddings = self.rotary_emb(hidden_states, position_ids) + + # decoder layers + all_hidden_states = () if output_hidden_states else None + all_self_attns = () if output_attentions else None + + for decoder_layer in self.layers: + if output_hidden_states: + all_hidden_states += (hidden_states,) + + if self.gradient_checkpointing and self.training: + layer_outputs = self._gradient_checkpointing_func( + decoder_layer.__call__, + hidden_states, + attention_mask, + position_ids, + past_key_values, + output_attentions, + use_cache, + cache_position, + position_embeddings, + ) + else: + layer_outputs = decoder_layer( + hidden_states, + attention_mask=attention_mask, + update_kvcache=update_kvcache, + position_ids=position_ids, + past_key_value=past_key_values, + output_attentions=output_attentions, + use_cache=use_cache, + cache_position=cache_position, + position_embeddings=position_embeddings, + ) + + hidden_states = layer_outputs[0] + + if output_attentions: + all_self_attns += (layer_outputs[1],) + + hidden_states = self.norm(hidden_states) + + # add hidden states from the last decoder layer + if output_hidden_states: + all_hidden_states += (hidden_states,) + + if not return_dict: + return tuple(v for v in [hidden_states, all_hidden_states, all_self_attns] if v is not None) + return BaseModelOutputWithPast( + last_hidden_state=hidden_states, + past_key_values=past_key_values if use_cache else None, + hidden_states=all_hidden_states, + attentions=all_self_attns, + ) + + +class DreamModel(DreamGenerationMixin, DreamPreTrainedModel): + _tied_weights_keys = ["lm_head.weight"] + + def __init__(self, config): + super().__init__(config) + self.model = DreamBaseModel(config) + self.vocab_size = config.vocab_size + self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False) + + # Initialize weights and apply final processing + self.post_init() + + def reset_rope_parameters(self): + self.model.rotary_emb.reset_parameters() + for layer in self.model.layers: + layer.self_attn.rotary_emb.reset_parameters() + + def get_input_embeddings(self): + return self.model.embed_tokens + + def set_input_embeddings(self, value): + self.model.embed_tokens = value + + def get_output_embeddings(self): + return self.lm_head + + def set_output_embeddings(self, new_embeddings): + self.lm_head = new_embeddings + + def set_decoder(self, decoder): + self.model = decoder + + def get_decoder(self): + return self.model + + def forward( + self, + input_ids: torch.LongTensor = None, + attention_mask: Optional[torch.Tensor] = None, + update_kvcache: torch.int32 = None, + position_ids: Optional[torch.LongTensor] = None, + past_key_values: Optional[List[torch.FloatTensor]] = None, + inputs_embeds: Optional[torch.FloatTensor] = None, + labels: Optional[torch.LongTensor] = None, + use_cache: Optional[bool] = None, + output_attentions: Optional[bool] = None, + output_hidden_states: Optional[bool] = None, + return_dict: Optional[bool] = None, + cache_position: Optional[torch.LongTensor] = None, + num_logits_to_keep: int = 0, + **loss_kwargs, + ) -> Union[Tuple, MaskedLMOutput]: + output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions + output_hidden_states = ( + output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states + ) + return_dict = return_dict if return_dict is not None else self.config.use_return_dict + + # decoder outputs consists of (dec_features, layer_state, dec_hidden, dec_attn) + outputs = self.model( + input_ids=input_ids, + attention_mask=attention_mask, + update_kvcache=update_kvcache, + position_ids=position_ids, + past_key_values=past_key_values, + inputs_embeds=inputs_embeds, + use_cache=use_cache, + output_attentions=output_attentions, + output_hidden_states=output_hidden_states, + return_dict=return_dict, + cache_position=cache_position, + ) + + hidden_states = outputs[0] + # Only compute necessary logits, and do not upcast them to float if we are not computing the loss + logits = self.lm_head(hidden_states[:, -num_logits_to_keep:, :]) + + loss = None + if labels is not None: + loss = self.loss_function(logits, labels, self.vocab_size, **loss_kwargs) + + if not return_dict: + output = (logits,) + outputs[1:] + return (loss,) + output if loss is not None else output + + return CausalLMOutputWithPast( + loss=loss, + logits=logits, + past_key_values=outputs.past_key_values, + hidden_states=outputs.hidden_states, + attentions=outputs.attentions, + ) diff --git a/Discrete-Diffusion-Forcing/D2F-eval/model_cache/llada/configuration_llada.py b/Discrete-Diffusion-Forcing/D2F-eval/model_cache/llada/configuration_llada.py new file mode 100644 index 0000000000000000000000000000000000000000..3556bdac0bc0b06c6ec606830a28e9b3e6aeed56 --- /dev/null +++ b/Discrete-Diffusion-Forcing/D2F-eval/model_cache/llada/configuration_llada.py @@ -0,0 +1,463 @@ +""" +LLaDA configuration +""" +from transformers import AutoConfig, PretrainedConfig + +from enum import Enum +from os import PathLike +from typing import Union +from dataclasses import asdict, dataclass, field +from glob import glob +from pathlib import Path +from typing import ( + Any, + Dict, + Iterable, + List, + Optional, + Tuple, + Type, + TypeVar, + Union, + cast, +) + + +__all__ = [ + "ActivationType", + "ActivationCheckpointingStrategy", + "BlockType", + "LayerNormType", + "InitFnType", + "ModelConfig", +] + +PathOrStr = Union[str, PathLike] + + +class StrEnum(str, Enum): + """ + This is equivalent to Python's :class:`enum.StrEnum` since version 3.11. + We include this here for compatibility with older version of Python. + """ + + def __str__(self) -> str: + return self.value + + def __repr__(self) -> str: + return f"'{str(self)}'" + + +class LayerNormType(StrEnum): + default = "default" + """ + The default LayerNorm implementation, equivalent to PyTorch's built-in version. + """ + + low_precision = "low_precision" + """ + A low-precision version of the default LayerNorm. + """ + + rms = "rms" + """ + An RMSNorm implementation. When using ``torch.compile`` this is + probably the fastest implementation. + """ + + gemma_rms = "gemma_rms" + """ + An RMSNorm implementation by gemmma. When using ``torch.compile`` this is + probably the fastest implementation. + """ + + amd_compatible = "amd_compatible" + """ + LayerNorm implemented manually to work around an issue with ROCm. + """ + + +class ActivationType(StrEnum): + gelu = "gelu" + relu = "relu" + silu = "silu" + swiglu = "swiglu" + + +class BlockType(StrEnum): + sequential = "sequential" + parallel = "parallel" + + llama = "llama" + """ + A block similar to the sequential block with slightly different + implementations of operations like attention to imitate the behavior of Llama. + """ + + +class InitFnType(StrEnum): + mitchell = "mitchell" + """ + The strategy suggested to us by Mitchell Wortsman from UW. + This uses a truncated normal distribution with an adaptive standard deviation that depends + on the size of the weights as well as the depth of the layer. + """ + + normal = "normal" + """ + All weights are initialized from the same normal distribution. + """ + + kaiming_normal = "kaiming_normal" + """ + All weights are initialized with the Kaiming method from a normal distribution. + Note this currently won't work with FSDP. + """ + + fan_in = "fan_in" + """ + "Fan-in variance scaling", i.e. normal with a standard deviation of ``1/sqrt(d_in)`` where ``d_in`` + is the input dimensionality of the kernel. + """ + + full_megatron = "full_megatron" + """ + This is what metaseq calls "full megatron init". It is the init used for Llama 2. + """ + + +@dataclass +class ModelConfig(): + """ + LLaDA (model) configuration. + """ + + # Note that the defaults for these attributes are equivalent to the base GPT2 model. + + d_model: int = 768 + """ + The hidden size of the model. + """ + + n_heads: int = 12 + """ + The number of self-attention heads. + """ + + n_kv_heads: Optional[int] = None + """ + The number of heads to use for keys and values. Defaults to `n_heads`. + Set this to ``None`` or ``n_heads`` for normal multi-head attention. + Set this to 1 for multi-query attention. + Set it to some in-between value for Llama2-style grouped query attention. + """ + + n_layers: int = 12 + """ + The number of layers/blocks. + """ + + mlp_ratio: int = 4 + """ + The ratio of the inner MLP dimensionality to ``d_model``. + This is only used when ``mlp_hidden_size`` is not set. + """ + + mlp_hidden_size: Optional[int] = None + """ + Set the exact hidden size for the MLP. Otherwise the inner MLP hidden size will be set to `mlp_ratio * d_model`. + """ + + activation_type: ActivationType = ActivationType.swiglu + """ + The activation function to use within the MLP layers. + """ + + block_type: BlockType = BlockType.sequential + """ + The transformer block implementation. + """ + + block_group_size: int = 1 + """ + The number of blocks to group together into a single parent block. + This has no affect on the number of parameters in the model and is only used to wrap groups + of blocks together with a single FSDP wrapper during training. + """ + + alibi: bool = False + """ + If ``True``, use ALiBi embeddings. Mutually exclusive with ``rope``. + """ + + alibi_bias_max: float = 8.0 + """ + Maximum absolute value of ALiBi bias. + """ + + rope: bool = False + """ + Use rotary positional embeddings (RoPE). Mutually exclusive with ``alibi``. + """ + + rope_full_precision: bool = True + """ + If ``True``, apply RoPE embeddings at full precision regardless of the input type. Otherwise, + apply RoPE at the precision of the input. + """ + + flash_attention: bool = False + """ + If ``True``, use ``FlashAttention``. + """ + + attention_dropout: float = 0.1 + """ + The dropout probability within the attention modules. + """ + + multi_query_attention: Optional[bool] = None + """ + Use the Multi-Query formulation of attention used in PaLM. This reduces the number of parameters + and is more efficient during inference. + """ + + attention_layer_norm: bool = False + """ + Apply layer norm to the keys and queries within the attention mechanism. + This can help stabilize training. + """ + + residual_dropout: float = 0.1 + """ + The dropout probability for the MLP and attention output within each block. + """ + + embedding_dropout: float = 0.1 + """ + The dropout probability for embeddings. + """ + + input_emb_norm: bool = False + """ + An input hidden_states norm implementation by gemmma. + """ + + layer_norm_type: LayerNormType = LayerNormType.default + """ + The layernorm implementation to use. + """ + + layer_norm_with_affine: bool = True + """ + Whether to include bias and weight parameters for the layer norms. + This only affects layer norms that are immediately followed by a linear layer in the forward pass, + so everything except QK-norms. To turn off affines for QK norms as well, set :attr:`attention_layer_norm_with_affine` + to ``False``. + """ + + rms_norm_eps: float = 1e-05 + """ + The rms layernorm eps param. + """ + + attention_layer_norm_with_affine: bool = True + """ + Toggle affine transform for the QK norms. + """ + + max_sequence_length: int = 1024 + """ + The maximum input sequence length supported by the model. + """ + + rope_theta: float = 10000.0 + """ + The rope base param. + """ + + include_qkv_bias: Optional[bool] = False + """ + Whether or not to include bias parameters in qkv linear layers. + """ + + include_bias: bool = False + """ + Whether or not to include bias parameters in linear layers. + In PaLM, they got rid of all bias terms because they found that large + models tend to have near 0 bias terms anyway. + """ + + bias_for_layer_norm: Optional[bool] = None + """ + Whether or not to include bias parameters in layer norm. + This is separate from the include_bias parameter, because of a ROCm crash when biases are disabled in + layer norm. + When this is None (the default), it inherits the setting from include_bias. + """ + + scale_logits: bool = False + """ + If ``True``, scale the output logits by ``1 / sqrt(d_model)``. + """ + + vocab_size: int = 50257 + """ + Vocabulary size of the model. + """ + + embedding_size: Optional[int] = 50304 + """ + The number of embeddings, i.e. the number of tokens. If set to ``None`` it will default + to ``vocab_size``. If ``vocab_size`` is not a multiple of 128, setting this to the + next multiple of 128 that's greater than ``vocab_size`` can improve throughput + substantially. + """ + + weight_tying: bool = True + """ + Whether to tie output linear weights to the input embedding. + """ + + eos_token_id: int = 50256 + """ + The ID of the end-of-sentence special token. + """ + + pad_token_id: int = 50256 + """ + The ID of the token to use for padding. Defaults to the ID of the EOS token. + """ + + mask_token_id: Optional[int] = 50256 + """ + The ID of the token to use for mask token. Defaults to the ID of the EOS token. + """ + + init_device: Optional[str] = None + """ + The torch device to use when initializing the model parameters, e.g. "cpu", "cuda:0", "meta". + """ + + init_fn: InitFnType = InitFnType.normal + """ + The weight initialization strategy. + """ + + init_std: float = 0.02 + """ + The standard deviation to use when initializing weights with a "fixed distribution" ``init_fn``, such + as "normal". + """ + + init_cutoff_factor: Optional[float] = None + """ + A positive factor used to scale the cutoff values when initializing weights with a "fixed distribution" ``init_fn``, such + as "normal". Setting this to None means values are not cutoff. + """ + + precision: Optional[str] = None + """ + Precision used to train/evaluate with. You shouldn't set this directly. + See :data:`TrainConfig.precision` instead. + """ + + @property + def effective_n_kv_heads(self) -> int: + if self.n_kv_heads is None: + if self.multi_query_attention is True: + return 1 + else: + return self.n_heads + else: + if self.multi_query_attention is None: + return self.n_kv_heads + if self.multi_query_attention: + n_kv_heads_should_be = 1 + else: + n_kv_heads_should_be = self.n_heads + if self.n_kv_heads == n_kv_heads_should_be: + return n_kv_heads_should_be + else: + raise Exception( + "You can't set `multi_query_attention` and `n_kv_heads` at the same time." + ) + +class ActivationCheckpointingStrategy(StrEnum): + whole_layer = "whole_layer" + """ + Checkpoint every transformer layer. + """ + + one_in_two = "one_in_two" + """ + Checkpoint one in two transformer layers. + """ + + one_in_three = "one_in_three" + """ + Checkpoint one in three transformer layers. + """ + + one_in_four = "one_in_four" + """ + Checkpoint one in four transformer layers. + """ + + two_in_three = "two_in_three" + """ + Checkpoint two out of every three transformer layers. + """ + + three_in_four = "three_in_four" + """ + Checkpoint three out of four of every transformer layers. + """ + + four_in_five = "four_in_five" + """ + Checkpoint four out of five of every transformer layers. + """ + + nine_in_ten = "nine_in_ten" + """ + Checkpoint nine out of ten of every transformer layers. + """ + + fine_grained = "fine_grained" + """ + Focus checkpointing on where it is cheap to recompute and saves most memory. + """ + + +class LLaDAConfig(PretrainedConfig): + model_type = "llada" + keys_to_ignore_at_inference = ["past_key_values"] # TODO: confirm + + def __init__(self, use_cache: bool = False, **kwargs): + model_config = ModelConfig() + all_kwargs = model_config.__dict__ + all_kwargs.update(kwargs) + all_kwargs.update({"use_cache": use_cache}) + all_kwargs.update( + { + "architectures": all_kwargs.get("architectures", ["LLaDAModelLM"]) + } + ) + super().__init__(**all_kwargs) + + @property + def num_attention_heads(self): + return self.n_heads + + @property + def num_hidden_layers(self): + return self.n_layers + + @property + def hidden_size(self): + return self.d_model + + +# Register the config class so that it is available for transformer pipelines, auto-loading etc. +AutoConfig.register("llada", LLaDAConfig) diff --git a/Discrete-Diffusion-Forcing/D2F-eval/model_cache/llada/modeling_llada.py b/Discrete-Diffusion-Forcing/D2F-eval/model_cache/llada/modeling_llada.py new file mode 100644 index 0000000000000000000000000000000000000000..babc3dff9d96879c51362a3ba0a0bd7f477fb559 --- /dev/null +++ b/Discrete-Diffusion-Forcing/D2F-eval/model_cache/llada/modeling_llada.py @@ -0,0 +1,1504 @@ +from __future__ import annotations + +import logging +import math +import sys +from abc import abstractmethod +from collections import defaultdict +from functools import partial +from typing import ( + Callable, + Dict, + Iterable, + List, + NamedTuple, + Optional, + Sequence, + Set, + Tuple, + cast, +) +from dataclasses import fields +from typing import List, Optional, Tuple, Union + +import torch +import torch.backends.cuda +import torch.nn as nn +import torch.nn.functional as F +from torch import einsum +from transformers import PreTrainedModel +from transformers.modeling_outputs import CausalLMOutputWithPast +from transformers.models.auto import AutoModel +from transformers.cache_utils import Cache + +from .configuration_llada import ( + LLaDAConfig, + StrEnum, + InitFnType, + ActivationType, + BlockType, + LayerNormType, + ModelConfig, + ActivationCheckpointingStrategy, +) + +if sys.version_info.minor > 8: + from collections.abc import MutableMapping +elif sys.version_info.minor == 8: + from typing import MutableMapping +else: + raise SystemExit("This script supports Python 3.8 or higher") + +__all__ = [ + "LayerNormBase", + "LayerNorm", + "RMSLayerNorm", + "GemmaRMSLayerNorm", + "RotaryEmbedding", + "Activation", + "GELU", + "ReLU", + "SwiGLU", + "LLaDABlock", + "LLaDASequentialBlock", + "LLaDAModel", + "LLaDAOutput", + "LLaDAGenerateOutput", +] + + +log = logging.getLogger(__name__) + + +class ModuleType(StrEnum): + in_module = "in" + out_module = "out" + emb = "emb" + final_out = "final_out" + + +def init_weights( + config: ModelConfig, + module: Union[nn.Linear, nn.Embedding], + d: Optional[int] = None, + layer_id: Optional[int] = None, + std_factor: float = 1.0, + type_of_module: Optional[ModuleType] = None, +) -> None: + """ + Initialize weights of a linear or embedding module. + + :param config: The model config. + :param module: The linear or embedding submodule to initialize. + :param d: The effective input dimensionality of the weights. This could be smaller than the actual dimensions + for fused layers. + :param layer_id: When set, the standard deviation for the "mitchell" method will be adjusted by + ``1 / sqrt(2 * (layer_id + 1))``. + """ + d = d if d is not None else config.d_model + if config.init_fn == InitFnType.normal: + std = config.init_std * std_factor + if config.init_cutoff_factor is not None: + cutoff_value = config.init_cutoff_factor * std + nn.init.trunc_normal_(module.weight, mean=0.0, std=std, a=-cutoff_value, b=cutoff_value) + else: + nn.init.normal_(module.weight, mean=0.0, std=std) + elif config.init_fn == InitFnType.mitchell: + std = std_factor / math.sqrt(d) + if layer_id is not None: + std = std / math.sqrt(2 * (layer_id + 1)) + nn.init.trunc_normal_(module.weight, mean=0.0, std=std, a=-3 * std, b=3 * std) + elif config.init_fn == InitFnType.kaiming_normal: + nn.init.kaiming_normal_(module.weight, nonlinearity="relu") + elif config.init_fn == InitFnType.fan_in: + std = std_factor / math.sqrt(d) + nn.init.normal_(module.weight, mean=0.0, std=std) + elif config.init_fn == InitFnType.full_megatron: + if type_of_module is None: + raise RuntimeError(f"When using the {InitFnType.full_megatron} init, every module must have a type.") + + cutoff_factor = config.init_cutoff_factor + if cutoff_factor is None: + cutoff_factor = 3 + + if type_of_module == ModuleType.in_module: + # for att_proj (same as QKV), ff_proj + std = config.init_std + elif type_of_module == ModuleType.out_module: + # for attn_out, ff_out + std = config.init_std / math.sqrt(2.0 * config.n_layers) + elif type_of_module == ModuleType.emb: + # positional embeddings (wpe) + # token embeddings (wte) + std = config.init_std + elif type_of_module == ModuleType.final_out: + # final output (ff_out) + std = config.d_model**-0.5 + else: + raise RuntimeError(f"Unknown module type '{type_of_module}'") + nn.init.trunc_normal_( + module.weight, + mean=0.0, + std=std, + a=-cutoff_factor * std, + b=cutoff_factor * std, + ) + else: + raise NotImplementedError(config.init_fn) + + if isinstance(module, nn.Linear): + if module.bias is not None: + nn.init.zeros_(module.bias) + + if config.init_fn == InitFnType.normal and getattr(module, "_is_residual", False): + with torch.no_grad(): + module.weight.div_(math.sqrt(2 * config.n_layers)) + + +def ensure_finite_(x: torch.Tensor, check_neg_inf: bool = True, check_pos_inf: bool = False): + """ + Modify ``x`` in place to replace ``float("-inf")`` with the minimum value of the dtype when ``check_neg_inf`` + is ``True`` and to replace ``float("inf")`` with the maximum value of the dtype when ``check_pos_inf`` is ``True``. + """ + if check_neg_inf: + x.masked_fill_(x == float("-inf"), torch.finfo(x.dtype).min) + if check_pos_inf: + x.masked_fill_(x == float("inf"), torch.finfo(x.dtype).max) + + +def activation_checkpoint_function(cfg: ModelConfig): + preserve_rng_state = ( + (cfg.attention_dropout == 0.0) and (cfg.embedding_dropout == 0.0) and (cfg.residual_dropout == 0.0) + ) + from torch.utils.checkpoint import checkpoint + + return partial( + checkpoint, + preserve_rng_state=preserve_rng_state, + use_reentrant=False, + ) + + +class BufferCache(dict, MutableMapping[str, torch.Tensor]): + """ + Cache for attention biases and other things that would normally be stored as buffers. + We avoid using buffers because we've run into various issues doing so with FSDP. + In general it appears the way FSDP handles buffers is not well-defined. + It doesn't shard them but apparently it does synchronize them across processes, which we want to avoid + since (A) it isn't necessary, and (B) we sometimes have `-inf` in these biases which might get turned into + NaNs when they're synchronized due to casting or some other issue. + """ + + +def _non_meta_init_device(config: ModelConfig) -> torch.device: + if config.init_device is not None and config.init_device != "meta": + return torch.device(config.init_device) + else: + return torch.device("cuda" if torch.cuda.is_available() else "cpu") + + +class Dropout(nn.Dropout): + def forward(self, input: torch.Tensor) -> torch.Tensor: + if self.p == 0.0: + return input + else: + return F.dropout(input, self.p, self.training, self.inplace) + + +class LayerNormBase(nn.Module): + def __init__( + self, + config: ModelConfig, + *, + size: Optional[int] = None, + elementwise_affine: Optional[bool] = True, + eps: float = 1e-05, + ): + super().__init__() + self.config = config + self.eps = eps + self.normalized_shape = (size or config.d_model,) + if elementwise_affine or (elementwise_affine is None and self.config.layer_norm_with_affine): + self.weight = nn.Parameter(torch.ones(self.normalized_shape, device=config.init_device)) + use_bias = self.config.bias_for_layer_norm + if use_bias is None: + use_bias = self.config.include_bias + if use_bias: + self.bias = nn.Parameter(torch.zeros(self.normalized_shape, device=config.init_device)) + else: + self.register_parameter("bias", None) + else: + self.register_parameter("bias", None) + self.register_parameter("weight", None) + + @abstractmethod + def forward(self, x: torch.Tensor) -> torch.Tensor: + raise NotImplementedError + + @classmethod + def build(cls, config: ModelConfig, size: Optional[int] = None, **kwargs) -> LayerNormBase: + if config.layer_norm_type == LayerNormType.default: + return LayerNorm(config, size=size, low_precision=False, **kwargs) + elif config.layer_norm_type == LayerNormType.low_precision: + return LayerNorm(config, size=size, low_precision=True, **kwargs) + elif config.layer_norm_type == LayerNormType.rms: + return RMSLayerNorm(config, size=size, **kwargs) + elif config.layer_norm_type == LayerNormType.gemma_rms: + return GemmaRMSLayerNorm(config, size=size, **kwargs) + else: + raise NotImplementedError(f"Unknown LayerNorm type: '{config.layer_norm_type}'") + + def _cast_if_autocast_enabled(self, tensor: torch.Tensor, dtype: Optional[torch.dtype] = None) -> torch.Tensor: + # NOTE: `is_autocast_enabled()` only checks for CUDA autocast, so we use the separate function + # `is_autocast_cpu_enabled()` for CPU autocast. + # See https://github.com/pytorch/pytorch/issues/110966. + if tensor.device.type == "cuda" and torch.is_autocast_enabled(): + return tensor.to(dtype=dtype if dtype is not None else torch.get_autocast_gpu_dtype()) + elif tensor.device.type == "cpu" and torch.is_autocast_cpu_enabled(): + return tensor.to(dtype=dtype if dtype is not None else torch.get_autocast_cpu_dtype()) + else: + return tensor + + def reset_parameters(self): + if self.weight is not None: + torch.nn.init.ones_(self.weight) # type: ignore + if self.bias is not None: + torch.nn.init.zeros_(self.bias) # type: ignore + + +class LayerNorm(LayerNormBase): + """ + The default :class:`LayerNorm` implementation which can optionally run in low precision. + """ + + def __init__( + self, + config: ModelConfig, + size: Optional[int] = None, + low_precision: bool = False, + elementwise_affine: Optional[bool] = None, + eps: float = 1e-05, + ): + super().__init__(config, size=size, elementwise_affine=elementwise_affine, eps=eps) + self.low_precision = low_precision + + def forward(self, x: torch.Tensor) -> torch.Tensor: + if self.low_precision: + module_device = x.device + downcast_x = self._cast_if_autocast_enabled(x) + downcast_weight = ( + self._cast_if_autocast_enabled(self.weight) if self.weight is not None else self.weight + ) + downcast_bias = self._cast_if_autocast_enabled(self.bias) if self.bias is not None else self.bias + with torch.autocast(enabled=False, device_type=module_device.type): + return F.layer_norm( + downcast_x, self.normalized_shape, weight=downcast_weight, bias=downcast_bias, eps=self.eps + ) + else: + return F.layer_norm(x, self.normalized_shape, weight=self.weight, bias=self.bias, eps=self.eps) + + +class RMSLayerNorm(LayerNormBase): + """ + RMS layer norm, a simplified :class:`LayerNorm` implementation + """ + + def __init__( + self, + config: ModelConfig, + size: Optional[int] = None, + elementwise_affine: Optional[bool] = None, + eps: float = 1e-5, + ): + super().__init__(config, size=size, elementwise_affine=elementwise_affine, eps=config.rms_norm_eps) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + # with torch.autocast(enabled=False, device_type=x.device.type): + og_dtype = x.dtype + x = x.to(torch.float32) + # print(x.dtype,x.shape) + variance = x*x + # print(variance) + variance = variance.mean(dim=-1,keepdim=True) + x = x * torch.rsqrt(variance + self.eps) + x = x.to(og_dtype) + + if self.weight is not None: + if self.bias is not None: + return self.weight * x + self.bias + else: + return self.weight * x + else: + return x + + +class GemmaRMSLayerNorm(LayerNormBase): + """ + Gemma RMS layer norm, a simplified :class:`LayerNorm` implementation + """ + + def __init__( + self, + config: ModelConfig, + size: Optional[int] = None, + elementwise_affine: Optional[bool] = None, + eps: float = 1e-5, + ): + super().__init__(config, size=size, elementwise_affine=elementwise_affine, eps=config.rms_norm_eps) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + with torch.autocast(enabled=False, device_type=x.device.type): + og_dtype = x.dtype + x = x.to(torch.float32) + variance = x.pow(2).mean(-1, keepdim=True) + x = x * torch.rsqrt(variance + self.eps) + x = x.to(og_dtype) + + if self.weight is not None: + if self.bias is not None: + return x * (1 + self.weight) + self.bias + else: + return x * (1 + self.weight) + else: + return x + + +class RotaryEmbedding(nn.Module): + """ + [Rotary positional embeddings (RoPE)](https://arxiv.org/abs/2104.09864). + """ + + def __init__(self, config: ModelConfig, cache: BufferCache): + super().__init__() + self.config = config + self.__cache = cache + # Warm up cache. + self.rope_theta = config.rope_theta + self.get_rotary_embedding(config.max_sequence_length, _non_meta_init_device(config)) + + def get_rotary_embedding(self, seq_len: int, device: torch.device) -> Tuple[torch.Tensor, torch.Tensor]: + if ( + (pos_sin := self.__cache.get("rope_pos_sin")) is not None + and (pos_cos := self.__cache.get("rope_pos_cos")) is not None + and pos_sin.shape[-2] >= seq_len + and pos_cos.shape[-2] >= seq_len + ): + if pos_sin.device != device: + pos_sin = pos_sin.to(device) + self.__cache["rope_pos_sin"] = pos_sin + if pos_cos.device != device: + pos_cos = pos_cos.to(device) + self.__cache["rope_pos_cos"] = pos_cos + return pos_sin[:, :, :seq_len, :], pos_cos[:, :, :seq_len, :] + + with torch.autocast(device.type, enabled=False): + dim = self.config.d_model // self.config.n_heads + inv_freq = 1.0 / (self.rope_theta ** (torch.arange(0, dim, 2, device=device, dtype=torch.float) / dim)) + seq = torch.arange(seq_len, device=device, dtype=torch.float) + freqs = einsum("i , j -> i j", seq, inv_freq) + positions = torch.cat((freqs, freqs), dim=-1) + pos_sin, pos_cos = positions.sin()[None, None, :, :], positions.cos()[None, None, :, :] + self.__cache["rope_pos_sin"] = pos_sin + self.__cache["rope_pos_cos"] = pos_cos + return pos_sin, pos_cos + + def rotate_half(self, x: torch.Tensor) -> torch.Tensor: + B, nh, T, hs = x.size() + x = x.view(B, nh, T, 2, hs // 2) + x1, x2 = x.unbind(dim=-2) + return torch.cat((-x2, x1), dim=-1) + + def apply_rotary_pos_emb(self, pos_sin: torch.Tensor, pos_cos: torch.Tensor, t: torch.Tensor) -> torch.Tensor: + return ((t * pos_cos) + (self.rotate_half(t) * pos_sin)).to(t.dtype) + + def forward(self, q: torch.Tensor, k: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]: + if self.config.rope_full_precision: + q_, k_ = q.float(), k.float() + else: + q_, k_ = q, k + + with torch.autocast(q.device.type, enabled=False): + query_len, key_len = q_.shape[-2], k_.shape[-2] # could be different if layer_past not None + pos_sin, pos_cos = self.get_rotary_embedding(key_len, q_.device) + pos_sin = pos_sin.type_as(q_) + pos_cos = pos_cos.type_as(q_) + q_ = self.apply_rotary_pos_emb( + pos_sin[:, :, key_len - query_len : key_len, :], + pos_cos[:, :, key_len - query_len : key_len, :], + q_, + ) + k_ = self.apply_rotary_pos_emb(pos_sin, pos_cos, k_) + return q_.type_as(q), k_.type_as(k) + + +class Activation(nn.Module): + def __init__(self, config: ModelConfig): + super().__init__() + self.config = config + + @abstractmethod + def forward(self, x: torch.Tensor) -> torch.Tensor: + raise NotImplementedError + + @property + @abstractmethod + def output_multiplier(self) -> float: + raise NotImplementedError + + @classmethod + def build(cls, config: ModelConfig) -> Activation: + if config.activation_type == ActivationType.gelu: + return cast(Activation, GELU(approximate="none")) + elif config.activation_type == ActivationType.relu: + return cast(Activation, ReLU(inplace=False)) + elif config.activation_type == ActivationType.silu: + return cast(Activation, SiLU(inplace=False)) + elif config.activation_type == ActivationType.swiglu: + return SwiGLU(config) + else: + raise NotImplementedError(f"Unknown activation: '{config.activation_type}'") + + +class GELU(nn.GELU): + @property + def output_multiplier(self) -> float: + return 1.0 + + +class ReLU(nn.ReLU): + @property + def output_multiplier(self) -> float: + return 1.0 + +class SiLU(nn.SiLU): + @property + def output_multiplier(self) -> float: + return 1.0 + +class SwiGLU(Activation): + def forward(self, x: torch.Tensor) -> torch.Tensor: + x, gate = x.chunk(2, dim=-1) + return F.silu(gate) * x + + @property + def output_multiplier(self) -> float: + return 0.5 + + +def causal_attention_bias(seq_len: int, device: torch.device) -> torch.FloatTensor: + att_bias = torch.triu( + torch.ones(seq_len, seq_len, device=device, dtype=torch.float), + diagonal=1, + ) + att_bias.masked_fill_(att_bias == 1, torch.finfo(att_bias.dtype).min) + return att_bias.view(1, 1, seq_len, seq_len) # type: ignore + + +def get_causal_attention_bias(cache: BufferCache, seq_len: int, device: torch.device) -> torch.Tensor: + if (causal_bias := cache.get("causal_attention_bias")) is not None and causal_bias.shape[-1] >= seq_len: + if causal_bias.device != device: + causal_bias = causal_bias.to(device) + cache["causal_attention_bias"] = causal_bias + return causal_bias + with torch.autocast(device.type, enabled=False): + causal_bias = causal_attention_bias(seq_len, device) + cache["causal_attention_bias"] = causal_bias + return causal_bias + + +def alibi_attention_bias(seq_len: int, config: ModelConfig, device: torch.device) -> torch.FloatTensor: + alibi_bias = torch.arange(1 - seq_len, 1, dtype=torch.float, device=device).view(1, 1, 1, seq_len) + + # shape: (1, 1, seq_len, seq_len) + alibi_bias = alibi_bias - torch.arange(1 - seq_len, 1, dtype=torch.float, device=device).view(1, 1, seq_len, 1) + alibi_bias.abs_().mul_(-1) + + # shape: (n_heads,) + m = torch.arange(1, config.n_heads + 1, dtype=torch.float, device=device) + m.mul_(config.alibi_bias_max / config.n_heads) + + # shape: (1, n_heads, seq_len, seq_len) + return alibi_bias * (1.0 / (2 ** m.view(1, config.n_heads, 1, 1))) # type: ignore + + +class LLaDABlock(nn.Module): + """ + A base class for transformer block implementations. + """ + + def __init__(self, layer_id: int, config: ModelConfig, cache: BufferCache): + super().__init__() + self.layer_id = layer_id + self.config = config + self.hidden_size = ( + config.mlp_hidden_size if config.mlp_hidden_size is not None else config.mlp_ratio * config.d_model + ) + self.__cache = cache + assert config.d_model % config.n_heads == 0 + + self._activation_checkpoint_fn = None + + # Dropout. + self.dropout = Dropout(config.residual_dropout) + + # Layer norms. + self.k_norm: Optional[LayerNormBase] = None + self.q_norm: Optional[LayerNormBase] = None + if config.attention_layer_norm: + self.k_norm = LayerNormBase.build( + config, + size=(config.d_model // config.n_heads) * config.effective_n_kv_heads, + elementwise_affine=config.attention_layer_norm_with_affine, + ) + self.q_norm = LayerNormBase.build(config, elementwise_affine=config.attention_layer_norm_with_affine) + + # Activation function. + self.act = Activation.build(config) + assert (self.act.output_multiplier * self.hidden_size) % 1 == 0 + + # Attention output projection. + self.attn_out = nn.Linear( + config.d_model, config.d_model, bias=config.include_bias, device=config.init_device + ) + + # Feed-forward output projection. + self.ff_out = nn.Linear( + int(self.act.output_multiplier * self.hidden_size), + config.d_model, + bias=config.include_bias, + device=config.init_device, + ) + self.ff_out._is_residual = True # type: ignore + + # Rotary embeddings. + if self.config.rope: + self.rotary_emb = RotaryEmbedding(config, self.__cache) + + self.flash_attn_func = None + if config.flash_attention: + try: + from flash_attn import flash_attn_func # type: ignore + + self.flash_attn_func = flash_attn_func + except ModuleNotFoundError: + pass + + def reset_parameters(self): + if self.k_norm is not None: + self.k_norm.reset_parameters() + if self.q_norm is not None: + self.q_norm.reset_parameters() + init_weights( + self.config, + self.attn_out, + d=self.config.d_model, + layer_id=self.layer_id, + type_of_module=ModuleType.out_module, + ) + init_weights( + self.config, + self.ff_out, + d=self.ff_out.in_features, + layer_id=self.layer_id, + type_of_module=ModuleType.out_module, + ) + + def set_activation_checkpointing(self, strategy: Optional[ActivationCheckpointingStrategy]): + if strategy == ActivationCheckpointingStrategy.fine_grained: + self._activation_checkpoint_fn = activation_checkpoint_function(self.config) + else: + self._activation_checkpoint_fn = None + + @classmethod + def _cast_attn_bias(cls, bias: torch.Tensor, input_dtype: torch.dtype) -> torch.Tensor: + target_dtype = input_dtype + # NOTE: `is_autocast_enabled()` only checks for CUDA autocast, so we use the separate function + # `is_autocast_cpu_enabled()` for CPU autocast. + # See https://github.com/pytorch/pytorch/issues/110966. + if bias.device.type == "cuda" and torch.is_autocast_enabled(): + target_dtype = torch.get_autocast_gpu_dtype() + elif bias.device.type == "cpu" and torch.is_autocast_cpu_enabled(): + target_dtype = torch.get_autocast_cpu_dtype() + if bias.dtype != target_dtype: + bias = bias.to(target_dtype) + ensure_finite_(bias, check_neg_inf=True, check_pos_inf=False) + return bias + + def _scaled_dot_product_attention( + self, + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + attn_mask: Optional[torch.Tensor] = None, + dropout_p: float = 0.0, + is_causal: bool = False, + ) -> torch.Tensor: + """ + Computes scaled dot product attention on query, key and value tensors, using an optional + attention mask if passed, and applying dropout if a probability greater than 0.0 is specified. + """ + if self.flash_attn_func is not None and attn_mask is None: + r = self.flash_attn_func( + q.transpose(1, 2), k.transpose(1, 2), v.transpose(1, 2), dropout_p=dropout_p, causal=False + ) + return r.transpose(1, 2) + else: + # torch's sdpa doesn't support GQA, so we're doing this + assert k.size(1) == v.size(1) + num_kv_heads = k.size(1) + num_q_heads = q.size(1) + if num_q_heads != num_kv_heads: + assert num_q_heads % num_kv_heads == 0 + k = k.repeat_interleave(num_q_heads // num_kv_heads, dim=1, output_size=num_q_heads) + v = v.repeat_interleave(num_q_heads // num_kv_heads, dim=1, output_size=num_q_heads) + # Modify: MDM set causal to False, and with no attn_mask. + return F.scaled_dot_product_attention( + q, + k, + v, + attn_mask=attn_mask, + dropout_p=dropout_p, + is_causal=False, + ) + + def attention( + self, + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + attention_bias: Optional[torch.Tensor] = None, + layer_past: Optional[Tuple[torch.Tensor, torch.Tensor]] = None, + use_cache: bool = False, + ) -> Tuple[torch.Tensor, Optional[Tuple[torch.Tensor, torch.Tensor]]]: + B, T, C = q.size() # batch size, sequence length, d_model + dtype = k.dtype + + # Optionally apply layer norm to keys and queries. + if self.q_norm is not None and self.k_norm is not None: + q = self.q_norm(q).to(dtype=dtype) + k = self.k_norm(k).to(dtype=dtype) + + # Move head forward to be next to the batch dim. + # shape: (B, nh, T, hs) + q = q.view(B, T, self.config.n_heads, C // self.config.n_heads).transpose(1, 2) + # shape: (B, n_kv_h, T, hs) + k = k.view(B, T, self.config.effective_n_kv_heads, C // self.config.n_heads).transpose(1, 2) + # shape: (B, n_kv_h, T, hs) + v = v.view(B, T, self.config.effective_n_kv_heads, C // self.config.n_heads).transpose(1, 2) + + if layer_past is not None: + past_key, past_value = layer_past + k = torch.cat((past_key, k), dim=-2) + v = torch.cat((past_value, v), dim=-2) + + present = (k, v) if use_cache else None + query_len, key_len = q.shape[-2], k.shape[-2] # could be different if layer_past not None + + if self.config.rope: + # Apply rotary embeddings. + q, k = self.rotary_emb(q, k) + + # if attention_bias is not None: + # # Resize and cast attention bias. + # # The current dtype of the attention bias might not match the dtype that the SDP attn function will + # # run in if AMP is enabled, and this can be a problem if some tokens are masked out due to padding + # # as down-casting the attention bias to the autocast precision will result in -infs, which will + # # cause the SDP attn function to produce NaNs. + # attention_bias = self._cast_attn_bias( + # attention_bias[:, :, key_len - query_len : key_len, :key_len], dtype + # ) + + # Get the attention scores. + # shape: (B, nh, T, hs) + att = self._scaled_dot_product_attention( + q, + k, + v, + attn_mask=attention_bias, + dropout_p=0.0 if not self.training else self.config.attention_dropout, + is_causal=False, + ) + + # Re-assemble all head outputs side-by-side. + att = att.transpose(1, 2).contiguous().view(B, T, C) + + # Apply output projection. + return self.attn_out(att), present + + @abstractmethod + def forward( + self, + x: torch.Tensor, + attention_bias: Optional[torch.FloatTensor] = None, + layer_past: Optional[Tuple[torch.Tensor, torch.Tensor]] = None, + use_cache: bool = False, + ) -> Tuple[torch.Tensor, Optional[Tuple[torch.Tensor, torch.Tensor]]]: + raise NotImplementedError + + @classmethod + def build(cls, layer_id: int, config: ModelConfig, cache: BufferCache) -> LLaDABlock: + if config.block_type == BlockType.sequential: + return LLaDASequentialBlock(layer_id, config, cache) + elif config.block_type == BlockType.llama: + return LLaDALlamaBlock(layer_id, config, cache) + else: + raise NotImplementedError(f"Unknown block type: '{config.block_type}'") + + +class LLaDASequentialBlock(LLaDABlock): + """ + This is a typical transformer block where the output is computed as ``MLP(LN(x + Attention(LN(x))))`` + (plus another skip connection). + """ + + def __init__(self, layer_id: int, config: ModelConfig, cache: BufferCache): + super().__init__(layer_id, config, cache) + # Layer norms. + self.attn_norm = LayerNorm.build(config) + self.ff_norm = LayerNorm.build(config) + # Attention input projection. Projects x -> (q, k, v) + head_dim = config.d_model // config.n_heads + self.fused_dims = ( + config.d_model, + config.effective_n_kv_heads * head_dim, + config.effective_n_kv_heads * head_dim, + ) + self.att_proj = nn.Linear( + config.d_model, sum(self.fused_dims), bias=config.include_bias | config.include_qkv_bias, device=config.init_device + ) + # Feed-forward input projection. + self.ff_proj = nn.Linear( + config.d_model, self.hidden_size, bias=config.include_bias, device=config.init_device + ) + + def reset_parameters(self): + super().reset_parameters() + self.attn_norm.reset_parameters() + self.ff_norm.reset_parameters() + # NOTE: the standard deviation for these weights does not depend on the layer. + init_weights( + self.config, self.att_proj, d=self.config.d_model, layer_id=None, type_of_module=ModuleType.in_module + ) + init_weights( + self.config, self.ff_proj, d=self.config.d_model, layer_id=None, type_of_module=ModuleType.in_module + ) + + def forward( + self, + x: torch.Tensor, + attention_bias: Optional[torch.Tensor] = None, + layer_past: Optional[Tuple[torch.Tensor, torch.Tensor]] = None, + use_cache: bool = False, + ) -> Tuple[torch.Tensor, Optional[Tuple[torch.Tensor, torch.Tensor]]]: + # Get query, key, value projections. + # shape: + # - for regular attn q, k, v: (batch_size, seq_len, d_model) + # - for multi-query attn q: (batch_size, seq_len, d_model) + # k, v: (batch_size, seq_len, d_model // n_heads) + # - for group query attn q: (batch_size, seq_len, d_model) + # k, v: (batch_size, seq_len, d_model // n_kv_heads) + if self._activation_checkpoint_fn is not None: + q, k, v = self.att_proj(self._activation_checkpoint_fn(self.attn_norm, x)).split( + self.fused_dims, dim=-1 + ) + else: + q, k, v = self.att_proj(self.attn_norm(x)).split(self.fused_dims, dim=-1) + + # Get attention scores. + if self._activation_checkpoint_fn is not None: + att, cache = self._activation_checkpoint_fn( # type: ignore + self.attention, q, k, v, attention_bias, layer_past=layer_past, use_cache=use_cache + ) + else: + att, cache = self.attention(q, k, v, attention_bias, layer_past=layer_past, use_cache=use_cache) + + # Add attention scores. + # shape: (B, T, C) + x = x + self.dropout(att) + + # Add feed-forward projection. + # shape: (batch_size, seq_len, d_model) + og_x = x + if self._activation_checkpoint_fn is not None: + x = self._activation_checkpoint_fn(self.ff_norm, x) # type: ignore + else: + x = self.ff_norm(x) + x = self.ff_proj(x) + if self._activation_checkpoint_fn is not None: + x = self._activation_checkpoint_fn(self.act, x) # type: ignore + else: + x = self.act(x) + x = self.ff_out(x) + x = self.dropout(x) + x = og_x + x + + return x, cache + + +class LLaDALlamaBlock(LLaDABlock): + """ + This is a transformer block where the output is computed as ``MLP(LN(x + Attention(LN(x))))`` + (plus another skip connection). This block is similar to `LLaDASequentialBlock` + but some operations have slightly different implementations to imitate the + behavior of Llama. + """ + + def __init__(self, layer_id: int, config: ModelConfig, cache: BufferCache): + super().__init__(layer_id, config, cache) + # Layer norms. + self.attn_norm = LayerNorm.build(config) + self.ff_norm = LayerNorm.build(config) + self.__cache = cache + + # Attention input projection. Projects x -> (q, k, v) + head_dim = config.d_model // config.n_heads + q_proj_out_dim = config.d_model + k_proj_out_dim = config.effective_n_kv_heads * head_dim + v_proj_out_dim = config.effective_n_kv_heads * head_dim + self.q_proj = nn.Linear( + config.d_model, q_proj_out_dim, bias=config.include_bias | config.include_qkv_bias, device=config.init_device + ) + self.k_proj = nn.Linear( + config.d_model, k_proj_out_dim, bias=config.include_bias | config.include_qkv_bias, device=config.init_device + ) + self.v_proj = nn.Linear( + config.d_model, v_proj_out_dim, bias=config.include_bias | config.include_qkv_bias, device=config.init_device + ) + + # Feed-forward input projection. + self.ff_proj = nn.Linear( + config.d_model, self.hidden_size, bias=config.include_bias, device=config.init_device + ) + # new add + self.up_proj = nn.Linear( + config.d_model, self.hidden_size, bias=config.include_bias, device=config.init_device + ) + + def reset_parameters(self): + super().reset_parameters() + self.attn_norm.reset_parameters() + self.ff_norm.reset_parameters() + # NOTE: the standard deviation for these weights does not depend on the layer. + init_weights(self.config, self.q_proj, d=self.config.d_model, layer_id=None) + init_weights(self.config, self.k_proj, d=self.config.d_model, layer_id=None) + init_weights(self.config, self.v_proj, d=self.config.d_model, layer_id=None) + init_weights(self.config, self.ff_proj, d=self.config.d_model, layer_id=None) + init_weights(self.config, self.up_proj, d=self.config.d_model, layer_id=None) # new add + + def forward( + self, + x: torch.Tensor, + attention_bias: Optional[torch.Tensor] = None, + layer_past: Optional[Tuple[torch.Tensor, torch.Tensor]] = None, + use_cache: bool = False, + ) -> Tuple[torch.Tensor, Optional[Tuple[torch.Tensor, torch.Tensor]]]: + # Get query, key, value projections. + # shape: + # - for regular attn q, k, v: (batch_size, seq_len, d_model) + # - for multi-query attn q: (batch_size, seq_len, d_model) + # k, v: (batch_size, seq_len, d_model // n_heads) + # - for group query attn q: (batch_size, seq_len, d_model) + # k, v: (batch_size, seq_len, d_model // n_kv_heads) + # print(x) + x_normed = self.attn_norm(x) + q = self.q_proj(x_normed) + k = self.k_proj(x_normed) + v = self.v_proj(x_normed) + + # Get attention scores. + if self._activation_checkpoint_fn is not None: + att, cache = self._activation_checkpoint_fn( # type: ignore + self.attention, q, k, v, attention_bias, layer_past=layer_past, use_cache=use_cache + ) + else: + att, cache = self.attention(q, k, v, attention_bias, layer_past=layer_past, use_cache=use_cache) + + # Add attention scores. + # shape: (B, T, C) + x = x + self.dropout(att) + + # Add feed-forward projection. + # shape: (batch_size, seq_len, d_model) + og_x = x + if self._activation_checkpoint_fn is not None: + x = self._activation_checkpoint_fn(self.ff_norm, x) # type: ignore + else: + x = self.ff_norm(x) + x, x_up = self.ff_proj(x), self.up_proj(x) # new add + if self._activation_checkpoint_fn is not None: + x = self._activation_checkpoint_fn(self.act, x) # type: ignore + else: + x = self.act(x) + x = x * x_up # new add + x = self.ff_out(x) + x = self.dropout(x) + x = og_x + x + + return x, cache + + +class LLaDAOutput(NamedTuple): + logits: torch.FloatTensor + """ + A tensor of shape `(batch_size, seq_len, vocab_size)` representing the log probabilities + for the next token *before* normalization via (log) softmax. + """ + + attn_key_values: Optional[List[Tuple[torch.Tensor, torch.Tensor]]] + """ + Attention keys and values from each block. + """ + + hidden_states: Optional[Tuple[torch.Tensor]] + """ + Hidden states from each block. + """ + + +class LLaDAGenerateOutput(NamedTuple): + token_ids: torch.LongTensor + """ + The generated token IDs, a tensor of shape `(batch_size, beam_size, max_steps)`. + These do *not* include the original input IDs. + """ + + scores: torch.FloatTensor + """ + The scores of the generated sequences, a tensor of shape `(batch_size, beam_size)`. + """ + + +class LLaDABlockGroup(nn.ModuleList): + def __init__(self, config: ModelConfig, layer_offset: int, modules: Optional[Iterable[nn.Module]] = None): + super().__init__(modules) + self.config = config + self.layer_offset = layer_offset + self.activation_checkpointing_strategy: Optional[ActivationCheckpointingStrategy] = None + self._activation_checkpoint_fn = activation_checkpoint_function(self.config) + + def forward( + self, + x: torch.Tensor, + attention_bias: Optional[torch.FloatTensor] = None, + layers_past: Optional[List[Tuple[torch.Tensor, torch.Tensor]]] = None, + use_cache: bool = False, + ) -> Tuple[torch.Tensor, Optional[List[Tuple[torch.Tensor, torch.Tensor]]]]: + attn_key_values: Optional[List[Tuple[torch.Tensor, torch.Tensor]]] = [] if use_cache else None + for block_idx, block in enumerate(self): + layer_past = None if layers_past is None else layers_past[block_idx] + block_idx += self.layer_offset + if ( + (self.activation_checkpointing_strategy == ActivationCheckpointingStrategy.whole_layer) + or ( + self.activation_checkpointing_strategy == ActivationCheckpointingStrategy.one_in_two + and block_idx % 2 == 0 + ) + or ( + self.activation_checkpointing_strategy == ActivationCheckpointingStrategy.one_in_three + and block_idx % 3 == 0 + ) + or ( + self.activation_checkpointing_strategy == ActivationCheckpointingStrategy.one_in_four + and block_idx % 4 == 0 + ) + ): + # shape: (batch_size, seq_len, d_model) + x, cache = self._activation_checkpoint_fn( # type: ignore + block, x, attention_bias=attention_bias, layer_past=layer_past, use_cache=use_cache + ) + else: + # shape: (batch_size, seq_len, d_model) + x, cache = block(x, attention_bias=attention_bias, layer_past=layer_past, use_cache=use_cache) + if attn_key_values is not None: + assert cache is not None + attn_key_values.append(cache) + return x, attn_key_values + + def reset_parameters(self): + for block in self: + block.reset_parameters() + + def set_activation_checkpointing(self, strategy: Optional[ActivationCheckpointingStrategy]): + self.activation_checkpointing_strategy = strategy + for block in self: + block.set_activation_checkpointing(strategy) + + +class LLaDAModel(nn.Module): + def __init__(self, config: ModelConfig, init_params: bool = True): + super().__init__() + self.config = config + self.__cache = BufferCache() + + # Validate config. + if self.config.alibi and self.config.flash_attention: + raise Exception("ALiBi is currently not supported with FlashAttention") + + if self.config.alibi and self.config.rope: + raise Exception("ALiBi and RoPE are mutually exclusive") + + if self.config.embedding_size is not None and self.config.embedding_size != self.config.vocab_size: + if self.config.embedding_size < self.config.vocab_size: + raise Exception("embedding size should be at least as big as vocab size") + elif self.config.embedding_size % 128 != 0: + import warnings + + warnings.warn( + "Embedding size is not a multiple of 128! This could hurt throughput performance.", UserWarning + ) + + self.activation_checkpointing_strategy: Optional[ActivationCheckpointingStrategy] = None + self._activation_checkpoint_fn: Callable = activation_checkpoint_function(self.config) + + if not ( + 0 < self.config.block_group_size <= self.config.n_layers + and self.config.n_layers % self.config.block_group_size == 0 + ): + raise Exception("n layers must be divisible by block group size") + + torch.backends.cuda.enable_flash_sdp(True) + torch.backends.cuda.enable_mem_efficient_sdp(False) # this is super slow so make sure torch won't use it + + self.transformer = nn.ModuleDict( + dict( + wte=nn.Embedding( + config.embedding_size or config.vocab_size, config.d_model, device=config.init_device + ), + emb_drop=Dropout(config.embedding_dropout), + ln_f=LayerNorm.build(config), + ) + ) + + blocks = [LLaDABlock.build(i, config, self.__cache) for i in range(config.n_layers)] + if self.config.block_group_size > 1: + block_groups = [ + LLaDABlockGroup(config, i, blocks[i : i + config.block_group_size]) + for i in range(0, config.n_layers, config.block_group_size) + ] + self.transformer.update({"block_groups": nn.ModuleList(block_groups)}) + else: + self.transformer.update({"blocks": nn.ModuleList(blocks)}) + + if not (self.config.alibi or self.config.rope): + self.transformer.update( + {"wpe": nn.Embedding(config.max_sequence_length, config.d_model, device=config.init_device)} + ) + if not config.weight_tying: + self.transformer.update( + { + "ff_out": nn.Linear( + config.d_model, + config.embedding_size or config.vocab_size, + bias=config.include_bias, + device=config.init_device, + ) + } + ) + # When `init_device="meta"` FSDP will call `reset_parameters()` to initialize weights. + if init_params and self.config.init_device != "meta": + self.reset_parameters() + self.__num_fwd_flops: Optional[int] = None + + # Warm up cache. + if self.config.alibi: + get_causal_attention_bias(self.__cache, config.max_sequence_length, _non_meta_init_device(config)) + self.get_alibi_attention_bias(config.max_sequence_length, _non_meta_init_device(config)) + + def set_activation_checkpointing(self, strategy: Optional[ActivationCheckpointingStrategy]): + self.activation_checkpointing_strategy = strategy + if self.config.block_group_size != 1: + for block_group in self.transformer.block_groups: + block_group.set_activation_checkpointing(strategy) + else: + for block in self.transformer.blocks: + block.set_activation_checkpointing(strategy) + + @property + def device(self) -> torch.device: + device: torch.device = self.transformer.wte.weight.device # type: ignore + if device.type == "meta": + return _non_meta_init_device(self.config) + else: + return device + + def reset_parameters(self): + log.info("Initializing model parameters...") + # Top-level embeddings / linear layers. + init_weights( + self.config, + self.transformer.wte, # type: ignore + std_factor=(0.5 * math.sqrt(self.config.d_model)) if self.config.scale_logits else 1.0, + type_of_module=ModuleType.emb, + ) + if hasattr(self.transformer, "wpe"): + init_weights(self.config, self.transformer.wpe, type_of_module=ModuleType.emb) # type: ignore + + # Top-level layer norm. + self.transformer.ln_f.reset_parameters() # type: ignore + + # Output weights. + if hasattr(self.transformer, "ff_out"): + init_weights(self.config, self.transformer.ff_out, type_of_module=ModuleType.final_out) # type: ignore + + # Let the blocks handle themselves. + if self.config.block_group_size == 1: + for block in self.transformer.blocks: + block.reset_parameters() + else: + for block_group in self.transformer.block_groups: + block_group.reset_parameters() + + def get_alibi_attention_bias(self, seq_len: int, device: torch.device) -> torch.Tensor: + if (alibi_bias := self.__cache.get("alibi_attention_bias")) is not None and alibi_bias.shape[ + -1 + ] >= seq_len: + if alibi_bias.device != device: + alibi_bias = alibi_bias.to(device) + self.__cache["alibi_attention_bias"] = alibi_bias + return alibi_bias + with torch.autocast(device.type, enabled=False): + alibi_bias = alibi_attention_bias(seq_len, self.config, device) + self.__cache["alibi_attention_bias"] = alibi_bias + return alibi_bias + + def forward( + self, + input_ids: torch.LongTensor, + input_embeddings: Optional[torch.FloatTensor] = None, + attention_mask: Optional[torch.Tensor] = None, + attention_bias: Optional[torch.Tensor] = None, + past_key_values: Optional[Sequence[Tuple[torch.Tensor, torch.Tensor]]] = None, + use_cache: bool = False, + update_kvcache: bool = False, + last_logits_only: bool = False, + output_hidden_states: Optional[bool] = None, + ) -> LLaDAOutput: + """ + :param input_ids: A tensor of shape `(batch_size, seq_len)`. + :param input_embeddings: A tensor of shape `(batch_size, seq_len, d_model)` with input + embeddings. When provided, it is treated as the output of the input embedding layer. + :param attention_mask: A tensor of shape `(batch_size, seq_len)` that indicates + which input IDs are masked. A `1` value in the mask means that + the corresponding input ID should *not* be ignored. A `0` means + that the corresponding input ID is masked. + + This has the same meaning as the `attention_mask` in HuggingFace's `transformers` + library. + :param attention_bias: A tensor of shape `(batch_size, 1, seq_len, seq_len)`, + `(1, 1, seq_len, seq_len)`, or `(seq_len, seq_len)`. This is used + to introduce causal or other biases. + + If the tensor is a bool or byte tensor, a `True` or `1` at `attention_bias[:, :, i, j]` + indicates that the i-th element in the sequence is allowed to attend to the j-th + element in the sequence. + + If the tensor is a float tensor, it will just be added to the attention + scores before the softmax. + + The default is causal, which corresponds to a lower-diagonal byte matrix of ones. + :param past_key_values: Pre-computed keys and values for each attention block. + Can be used to speed up sequential decoding. The `input_ids` which have + their past given to this model should not be passed as `input_ids` as they have already been computed. + :param use_cache: If `True`, return key and value tensors for each block. + :param last_logits_only: If `True`, only compute the logits for the last token of each sequence. + This can speed up decoding when you only care about the next token. + """ + # Add Basic MDM Model config check + # print(input_ids.dtype) + assert not self.config.alibi, "Alibi length extrapolation is not supported for MDM." + assert self.config.rope, "Rope must be used in Llama-Encoder for MDM." + # assert (past_key_values is None and not use_cache), "The kvcache is not suppotred for MDM." + + output_hidden_states = output_hidden_states if output_hidden_states is not None else False + + if past_key_values: + assert len(past_key_values) == self.config.n_layers + + batch_size, seq_len = input_ids.size() if input_embeddings is None else input_embeddings.size()[:2] + if past_key_values is None: + past_length = 0 + else: + past_length = past_key_values[0][0].size(-2) + + # Get embeddings of input. + # shape: (batch_size, seq_len, d_model) + # print(input_ids.dtype,"wte") + x = self.transformer.wte(input_ids) if input_embeddings is None else input_embeddings # type: ignore + + if self.config.input_emb_norm: + x = x * (self.config.d_model**0.5) + + if not (self.config.alibi or self.config.rope): + # Get positional embeddings. + # shape: (1, seq_len) + pos = torch.arange(past_length, past_length + seq_len, dtype=torch.long, device=x.device).unsqueeze(0) + # shape: (1, seq_len, d_model) + pos_emb = self.transformer.wpe(pos) # type: ignore + x = pos_emb + x + + # Add input + positional embeddings and apply dropout. + # shape: (batch_size, seq_len, d_model) + x = self.transformer.emb_drop(x) # type: ignore + + # Transform the attention mask into what the blocks expect. + if attention_mask is not None and 0.0 in attention_mask: + # shape: (batch_size, 1, 1, seq_len) + attention_mask = attention_mask.to(dtype=torch.float).view(batch_size, -1)[:, None, None, :] + attention_mask = (1.0 - attention_mask) * torch.finfo(attention_mask.dtype).min + else: + attention_mask = None + + # Merge attention mask with attention bias. + if ( + attention_bias is not None + or attention_mask is not None + or self.config.alibi + # NOTE (epwalsh): we need to initialize the attn bias in order for attn to work properly + # with key+value cache. Otherwise `F.scaled_dot_product_attention()` doesn't seem to compute + # scores correctly. + or past_key_values is not None + ): + if attention_bias is None and self.config.alibi: + attention_bias = get_causal_attention_bias( + self.__cache, past_length + seq_len, x.device + ) + self.get_alibi_attention_bias(past_length + seq_len, x.device) + elif attention_bias is None: + attention_bias = get_causal_attention_bias(self.__cache, past_length + seq_len, x.device) + elif attention_bias.dtype in (torch.int8, torch.bool): + attention_bias = attention_bias.to(dtype=torch.float) + attention_bias.masked_fill_(attention_bias == 0.0, torch.finfo(attention_bias.dtype).min) + + # Transform to the right shape and data type. + mask_len = seq_len + if attention_mask is not None: + mask_len = attention_mask.shape[-1] + elif past_key_values is not None: + mask_len = past_key_values[0][0].shape[-2] + seq_len + attention_bias = attention_bias[:, :, :mask_len, :mask_len].to(dtype=torch.float) + + # Add in the masking bias. + if attention_mask is not None: + attention_bias = attention_bias + attention_mask + # Might get -infs after adding attention mask, since dtype.min + dtype.min = -inf. + # `F.scaled_dot_product_attention()` doesn't handle -inf like you'd expect, instead + # it can produce NaNs. + ensure_finite_(attention_bias, check_neg_inf=True, check_pos_inf=False) + + attn_key_values: Optional[List[Tuple[torch.Tensor, torch.Tensor]]] = [] if use_cache else None + + # decoder layers + all_hidden_states = [] + + # Apply blocks one-by-one. + if self.config.block_group_size == 1: + for block_idx, block in enumerate(self.transformer.blocks): + if output_hidden_states: + # add hidden states + all_hidden_states.append(x) + + layer_past = None if past_key_values is None else past_key_values[block_idx] + if ( + (self.activation_checkpointing_strategy == ActivationCheckpointingStrategy.whole_layer) + or ( + self.activation_checkpointing_strategy == ActivationCheckpointingStrategy.one_in_two + and block_idx % 2 == 0 + ) + or ( + self.activation_checkpointing_strategy == ActivationCheckpointingStrategy.one_in_three + and block_idx % 3 == 0 + ) + or ( + self.activation_checkpointing_strategy == ActivationCheckpointingStrategy.one_in_four + and block_idx % 4 == 0 + ) + ): + # shape: (batch_size, seq_len, d_model) + x, cache = self._activation_checkpoint_fn( + block, x, attention_bias=attention_bias, layer_past=layer_past, use_cache=use_cache + ) + else: + # shape: (batch_size, seq_len, d_model) + x, cache = block(x, attention_bias=attention_bias, layer_past=layer_past, use_cache=use_cache) + if attn_key_values is not None: + if update_kvcache: + cache = (cache[0][:,:,:update_kvcache],cache[1][:,:,:update_kvcache,:]) + # print("True") + attn_key_values.append(cache) + else: + for group_idx, block_group in enumerate(self.transformer.block_groups): + if output_hidden_states: + # add hidden states + all_hidden_states.append(x) + + layers_past = ( + None + if past_key_values is None + else past_key_values[ + group_idx * self.config.block_group_size : (group_idx + 1) * self.config.block_group_size + ] + ) + x, cache = block_group( + x, attention_bias=attention_bias, layers_past=layers_past, use_cache=use_cache + ) + if attn_key_values is not None: + assert cache is not None + attn_key_values.extend(cache) + + if last_logits_only: + # shape: (batch_size, 1, d_model) + x = x[:, -1, :].unsqueeze(1) + + # Apply final layer norm. + # shape: (batch_size, seq_len or 1, d_model) + x = self.transformer.ln_f(x) # type: ignore + if output_hidden_states: + # add final hidden state post-final-layernorm, following HuggingFace's convention + all_hidden_states.append(x) + + # Get logits. + # shape: (batch_size, seq_len or 1, vocab_size) + if self.config.weight_tying: + logits = F.linear(x, self.transformer.wte.weight, None) # type: ignore + else: + logits = self.transformer.ff_out(x) # type: ignore + if self.config.scale_logits: + logits.mul_(1 / math.sqrt(self.config.d_model)) + if use_cache == True and update_kvcache == False: + attn_key_values=past_key_values + return LLaDAOutput(logits=logits, attn_key_values=attn_key_values, hidden_states=tuple(all_hidden_states) if output_hidden_states else None) # type: ignore[arg-type] + + +def create_model_config_from_pretrained_config(config: LLaDAConfig): + """ + Utility function + """ + + kwargs = {} + for field in fields(ModelConfig): + kwargs[field.name] = getattr(config, field.name) + + model_config = ModelConfig(**kwargs) + return model_config + + +class LLaDAModelLM(PreTrainedModel): + """ + Extremely barebones HF model wrapper. + """ + + config_class = LLaDAConfig + base_model_prefix = "model" + _no_split_modules = ["LLaDABlock", "LLaDASequentialBlock", "LLaDALlamaBlock"] + + def __init__(self, config: LLaDAConfig, model: Optional[LLaDAModel] = None, init_params: bool = False): + super().__init__(config) + + if not model: + model_config = create_model_config_from_pretrained_config(config) + # Initialize model (always on CPU to start with so we don't run out of GPU memory). + model_config.init_device = "cpu" + self.model = LLaDAModel(model_config, init_params=init_params) + else: + self.model = model + + def forward( + self, + input_ids: torch.LongTensor = None, + inputs_embeds: Optional[torch.FloatTensor] = None, + attention_mask: Optional[torch.Tensor] = None, + attention_bias: Optional[torch.Tensor] = None, + past_key_values: Optional[List[torch.FloatTensor]] = None, + labels: Optional[torch.LongTensor] = None, + use_cache: Optional[bool] = None, + update_kvcache: Optional[bool] = False, + output_attentions: Optional[bool] = None, + output_hidden_states: Optional[bool] = None, + return_dict: Optional[bool] = None, + cache_position: Optional[Cache] = None, # This is a hack mitigation of an issue in transformers `4.39.x` + ) -> Union[Tuple, CausalLMOutputWithPast]: + if use_cache is None: + use_cache = self.config.use_cache + + if output_attentions: + raise ValueError("output_attentions is not yet supported in LLaDA") + + return_dict = return_dict if return_dict is not None else self.config.use_return_dict + + # decoder outputs consists of (dec_features, layer_state, dec_hidden, dec_attn) + outputs = self.model.forward( + input_ids=input_ids, + input_embeddings=inputs_embeds, + attention_mask=attention_mask, + attention_bias=attention_bias, + past_key_values=past_key_values, + use_cache=use_cache, + update_kvcache=update_kvcache, + output_hidden_states=output_hidden_states, + ) + + logits = outputs.logits + hidden_states = outputs.hidden_states + + loss = None + if labels is not None: + import warnings + warnings.warn("Note that for LLaDA, you cannot calculate the loss here.", UserWarning) + if not return_dict: + output = (logits,) + outputs[1:] + return (loss,) + output if loss is not None else output + + return CausalLMOutputWithPast( + logits=logits, + past_key_values=outputs.attn_key_values, + hidden_states=hidden_states, + ) + + def can_generate(self) -> bool: + return True + + def prepare_inputs_for_generation( + self, input_ids: torch.LongTensor, past_key_values: Optional[List[Tuple]] = None, **kwargs + ): + if past_key_values: + # This is because we want the model to only process the last generated token. + input_ids = input_ids[:, -1:] + model_inputs = {"input_ids": input_ids, "past_key_values": past_key_values} + + model_inputs.update(kwargs) + model_inputs["use_cache"] = kwargs.pop("use_cache", self.config.use_cache) + return model_inputs + + # TODO: these are required to make the implementation complete. + # def resize_position_embeddings(self, new_num_position_embeddings: int): + # pass + # + # def get_position_embeddings(self) -> Union[nn.Embedding, Tuple[nn.Embedding]]: + # pass + # + # def _reorder_cache(self, past_key_values, beam_idx): + # pass + + def get_input_embeddings(self) -> torch.nn.Module: + return self.model.transformer.wte + + def set_input_embeddings(self, value: torch.nn.Module): + self.model.transformer.wte = value + + def get_output_embeddings(self): + if self.config.weight_tying: + return self.model.transformer.wte + else: + return self.model.transformer.ff_out + + def set_output_embeddings(self, value: torch.nn.Module): + if self.config.weight_tying: + self.model.transformer.wte = value + else: + self.model.transformer.ff_out = value + + def tie_weights(self): + if self.config.weight_tying: + self.model.transformer.ff_out = self.model.transformer.wte + +# Register the model so that it is available for transformer pipelines, auto-loading, etc. +AutoModel.register(LLaDAConfig, LLaDAModelLM) \ No newline at end of file diff --git a/Discrete-Diffusion-Forcing/D2F-eval/postprocess_code.py b/Discrete-Diffusion-Forcing/D2F-eval/postprocess_code.py new file mode 100644 index 0000000000000000000000000000000000000000..69f732e2b3a14c5bc6d3886652ea6b689046b1e8 --- /dev/null +++ b/Discrete-Diffusion-Forcing/D2F-eval/postprocess_code.py @@ -0,0 +1,62 @@ +# Copyright 2025 NVIDIA CORPORATION & AFFILIATES +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# Modified from Dream repos: https://github.com/HKUNLP/Dream + +import evaluate as hf_evaluate +import os +import sys +from sanitize import sanitize + +os.environ["HF_ALLOW_CODE_EVAL"] = "1" +pass_at_k = hf_evaluate.load("code_eval") + +def pass_at_1(references, predictions): + return pass_at_k.compute( + references=references, + predictions=predictions, + k=[1], + )[0]["pass@1"] + +import json + + +def read_jsonl(file_path): + data = [] + with open(file_path, 'r') as file: + for line in file: + data.append(json.loads(line)) + return data + +file_path = sys.argv[1] +data = read_jsonl(file_path) + +references = [sample['target'] for sample in data] + +predictions = [[sanitize(sample['doc']['prompt'] + "\n" + sample['resps'][0][0].split('```python\n', 1)[-1].split('```')[0], + sample['doc']["entry_point"])] + for sample in data] + +pass_at_1s = [pass_at_1([reference], [prediction]) for reference, prediction in zip(references, predictions)] +print(sum(pass_at_1s)/len(pass_at_1s)) + +def write_jsonl(data, file_path): + with open(file_path, 'w') as file: + for item in data: + file.write(json.dumps(item) + '\n') + +res = [{"task_id": sample['doc']['task_id'], "completion": pred, "pass_at_1": res} + for sample, pred, res in zip(data, predictions, pass_at_1s)] +write_jsonl(res, file_path+'.cleaned') \ No newline at end of file diff --git a/Discrete-Diffusion-Forcing/D2F-eval/sanitize.py b/Discrete-Diffusion-Forcing/D2F-eval/sanitize.py new file mode 100644 index 0000000000000000000000000000000000000000..7c91494b4d1d99edf2c5bde8ba1316db0f3fddfe --- /dev/null +++ b/Discrete-Diffusion-Forcing/D2F-eval/sanitize.py @@ -0,0 +1,147 @@ +# Copyright 2025 NVIDIA CORPORATION & AFFILIATES +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# Modified from Dream repos: https://github.com/HKUNLP/Dream + +"""Post-processing LLM-generated Python code implemented using tree-sitter.""" + +import os +import sys +import pathlib + +ROOT = os.path.dirname(os.path.abspath(__file__)) +sys.path.extend([os.path.dirname(ROOT), os.path.dirname(os.path.dirname(ROOT))]) + +import ast +import traceback + +from typing import Dict, List, Optional, Set, Tuple + +def refine_text(text: str) -> str: + text = text.replace("\t", " ") + text = text.replace("\r\n", "\n").replace("\r", "\n") + return text.strip() + "\n" + +def syntax_check(code, verbose = False): + try: + ast.parse(code) + return True + except (SyntaxError, MemoryError): + if verbose: + traceback.print_exc() + return False + +def extract_longest_valid_code(text: str) -> str: + lines = text.splitlines() + + if len(lines) > 100: + lines = lines[:100] + max_valid_lines = 0 + max_valid_snippet = "" + + for i in range(len(lines)): + for j in range(i, len(lines)): + current_snippet = "\n".join(lines[i:j+1]) + if syntax_check(current_snippet): + valid_line_count = sum(1 for line in lines[i:j+1] if line.strip()) + if valid_line_count > max_valid_lines: + max_valid_lines = valid_line_count + max_valid_snippet = current_snippet + + return max_valid_snippet + +def get_deps(nodes: List[Tuple[str, ast.AST]]) -> Dict[str, Set[str]]: + name2deps = {} + for name, node in nodes: + deps = set() + stack = [node] + while stack: + current = stack.pop() + for child in ast.iter_child_nodes(current): + if isinstance(child, ast.Name): + deps.add(child.id) + elif isinstance(child, ast.Attribute): + deps.add(child.attr) + else: + stack.append(child) + name2deps[name] = deps + return name2deps + +def get_function_dependency(entrypoint: str, call_graph: Dict[str, Set[str]]) -> Set[str]: + visited = set() + to_visit = [entrypoint] + + while to_visit: + current = to_visit.pop(0) + if current not in visited: + visited.add(current) + to_visit.extend(call_graph.get(current, set()) - visited) + + return visited + +def get_definition_name(node: ast.AST) -> Optional[str]: + if isinstance(node, (ast.FunctionDef, ast.ClassDef)): + return node.name + elif isinstance(node, ast.Assign): + targets = node.targets + if targets and isinstance(targets[0], ast.Name): + return targets[0].id + return None + +def has_return_statement(node: ast.AST) -> bool: + return any(isinstance(n, ast.Return) for n in ast.walk(node)) + +def sanitize(text: str, entrypoint: Optional[str] = None) -> str: + + text = refine_text(text) + + # text = python_extract(text) + + code = extract_longest_valid_code(text) + tree = ast.parse(code) + + definitions = {} + + imports = [] + + for node in tree.body: + if isinstance(node, (ast.Import, ast.ImportFrom)): + imports.append(node) + elif isinstance(node, ast.ClassDef): + name = node.name + definitions[name] = ('class', node) + elif isinstance(node, ast.FunctionDef): + name = node.name + if has_return_statement(node): + definitions[name] = ('function', node) + elif isinstance(node, ast.Assign): + name = get_definition_name(node) + if name: + definitions[name] = ('variable', node) + + if entrypoint: + name2deps = get_deps([(name, node) for name, (_, node) in definitions.items()]) + reachable = get_function_dependency(entrypoint, name2deps) + + sanitized_output = [] + + for node in imports: + sanitized_output.append(ast.unparse(node)) + + for name, (_, node) in definitions.items(): + if not entrypoint or name in reachable: + sanitized_output.append(ast.unparse(node)) + + return "\n".join(sanitized_output) \ No newline at end of file diff --git a/Discrete-Diffusion-Forcing/D2F-train/config/acc_config b/Discrete-Diffusion-Forcing/D2F-train/config/acc_config new file mode 100644 index 0000000000000000000000000000000000000000..fb6e2c95b6c8123fcc1c340d653bae80aa426909 --- /dev/null +++ b/Discrete-Diffusion-Forcing/D2F-train/config/acc_config @@ -0,0 +1,23 @@ +compute_environment: LOCAL_MACHINE +debug: false +deepspeed_config: + gradient_accumulation_steps: 3 + gradient_clipping: 1.0 + offload_optimizer_device: none + offload_param_device: none + zero3_init_flag: False + zero_stage: 2 +distributed_type: DEEPSPEED +downcast_bf16: 'no' +enable_cpu_affinity: True +machine_rank: 0 +main_training_function: main +mixed_precision: fp16 +num_machines: 1 +num_processes: 4 +rdzv_backend: static +same_network: false +tpu_env: [] +tpu_use_cluster: false +tpu_use_sudo: false +use_cpu: True diff --git a/Discrete-Diffusion-Forcing/D2F-train/config/dream_eagle.yaml b/Discrete-Diffusion-Forcing/D2F-train/config/dream_eagle.yaml new file mode 100644 index 0000000000000000000000000000000000000000..9bfb3862aceb4064b022359ca8c5229801491c8e --- /dev/null +++ b/Discrete-Diffusion-Forcing/D2F-train/config/dream_eagle.yaml @@ -0,0 +1,59 @@ +# Training mode configuration +training_mode: 'dream' # 'llada' or 'dream' + +# Model and data path configuration +paths: + model: 'Dream-org/Dream-v0-Base-7B' + experiment: 'ckpt_dream_base' + data: + bs: 'Lansechen/bs17k_collection_filtered_hard_maxlength600' + bs_easy: 'Lansechen/bs17k_collection_filtered_easy_maxlength600' + +denoiser: + encoder: + name: 'dream' + mask_id: 151666 + + decoder: + wiinit: true + name: 'eagle_rope' + num_blocks: 1 + seq_len: &seq_len 1024 + input_dim: 3584 + hidden_dim: &dim 3584 + vocab_size: 152064 + block: + seq_len: *seq_len + hidden_dim: *dim + num_heads: 32 + +train: + # Will use paths.experiment path + decoder_resume_path: + head_resume_path: + skipped_keys: + global_step: + exp_name: &exp_name 'ddt_test' + wandb_proj: *exp_name + output_dir: 'ddt_test' + logging_dir: 'logs' + mixed_precision: 'fp16' + gradient_accumulation_steps: 5 + report_to: 'wandb' + block_size: 16 + + lr: 5e-6 + num_iters: 50000 + eval_every: 100000 + save_every: 1000 + + enable_shift: true + share_steps: 2 + self_align: true + feature_align: false + self_step: true + +data: + name: 'bs17k' #['numinamath', 'bs17k'] + batch_size: 1 + max_length: *seq_len diff --git a/Discrete-Diffusion-Forcing/D2F-train/config/llada.yaml b/Discrete-Diffusion-Forcing/D2F-train/config/llada.yaml new file mode 100644 index 0000000000000000000000000000000000000000..0f8e10d99f27f01b197d54235e4063d671045e49 --- /dev/null +++ b/Discrete-Diffusion-Forcing/D2F-train/config/llada.yaml @@ -0,0 +1,59 @@ +# Training mode configuration +training_mode: 'llada' # 'llada' or 'dream' + +# Model and data path configuration +paths: + model: 'GSAI-ML/LLaDA-8B-Instruct' + experiment: 'ckpt_llada_instruct' + data: + bs: 'Lansechen/bs17k_collection_filtered_hard_maxlength600' + bs_easy: 'Lansechen/bs17k_collection_filtered_easy_maxlength600' + +denoiser: + encoder: + name: 'dream' + mask_id: 151666 + + decoder: + wiinit: true + name: 'eagle_rope' + num_blocks: 1 + seq_len: &seq_len 1024 + input_dim: 3584 + hidden_dim: &dim 3584 + vocab_size: 152064 + block: + seq_len: *seq_len + hidden_dim: *dim + num_heads: 32 + +train: + # Will use paths.experiment path + decoder_resume_path: + head_resume_path: + skipped_keys: + global_step: + exp_name: &exp_name 'llada_ddt_maskteacher' + wandb_proj: *exp_name + output_dir: 'ddt_test' + logging_dir: 'logs' + mixed_precision: 'fp16' + gradient_accumulation_steps: 5 + report_to: 'wandb' + block_size: 16 + + lr: 1e-5 + num_iters: 50000 + eval_every: 100000 + save_every: 1000 + + enable_shift: true + share_steps: 2 + self_align: true + feature_align: false + self_step: true + +data: + name: 'bs17k' #['numinamath', 'bs17k'] + batch_size: 1 + max_length: *seq_len diff --git a/Discrete-Diffusion-Forcing/D2F-train/model/configuration_llada.py b/Discrete-Diffusion-Forcing/D2F-train/model/configuration_llada.py new file mode 100644 index 0000000000000000000000000000000000000000..3556bdac0bc0b06c6ec606830a28e9b3e6aeed56 --- /dev/null +++ b/Discrete-Diffusion-Forcing/D2F-train/model/configuration_llada.py @@ -0,0 +1,463 @@ +""" +LLaDA configuration +""" +from transformers import AutoConfig, PretrainedConfig + +from enum import Enum +from os import PathLike +from typing import Union +from dataclasses import asdict, dataclass, field +from glob import glob +from pathlib import Path +from typing import ( + Any, + Dict, + Iterable, + List, + Optional, + Tuple, + Type, + TypeVar, + Union, + cast, +) + + +__all__ = [ + "ActivationType", + "ActivationCheckpointingStrategy", + "BlockType", + "LayerNormType", + "InitFnType", + "ModelConfig", +] + +PathOrStr = Union[str, PathLike] + + +class StrEnum(str, Enum): + """ + This is equivalent to Python's :class:`enum.StrEnum` since version 3.11. + We include this here for compatibility with older version of Python. + """ + + def __str__(self) -> str: + return self.value + + def __repr__(self) -> str: + return f"'{str(self)}'" + + +class LayerNormType(StrEnum): + default = "default" + """ + The default LayerNorm implementation, equivalent to PyTorch's built-in version. + """ + + low_precision = "low_precision" + """ + A low-precision version of the default LayerNorm. + """ + + rms = "rms" + """ + An RMSNorm implementation. When using ``torch.compile`` this is + probably the fastest implementation. + """ + + gemma_rms = "gemma_rms" + """ + An RMSNorm implementation by gemmma. When using ``torch.compile`` this is + probably the fastest implementation. + """ + + amd_compatible = "amd_compatible" + """ + LayerNorm implemented manually to work around an issue with ROCm. + """ + + +class ActivationType(StrEnum): + gelu = "gelu" + relu = "relu" + silu = "silu" + swiglu = "swiglu" + + +class BlockType(StrEnum): + sequential = "sequential" + parallel = "parallel" + + llama = "llama" + """ + A block similar to the sequential block with slightly different + implementations of operations like attention to imitate the behavior of Llama. + """ + + +class InitFnType(StrEnum): + mitchell = "mitchell" + """ + The strategy suggested to us by Mitchell Wortsman from UW. + This uses a truncated normal distribution with an adaptive standard deviation that depends + on the size of the weights as well as the depth of the layer. + """ + + normal = "normal" + """ + All weights are initialized from the same normal distribution. + """ + + kaiming_normal = "kaiming_normal" + """ + All weights are initialized with the Kaiming method from a normal distribution. + Note this currently won't work with FSDP. + """ + + fan_in = "fan_in" + """ + "Fan-in variance scaling", i.e. normal with a standard deviation of ``1/sqrt(d_in)`` where ``d_in`` + is the input dimensionality of the kernel. + """ + + full_megatron = "full_megatron" + """ + This is what metaseq calls "full megatron init". It is the init used for Llama 2. + """ + + +@dataclass +class ModelConfig(): + """ + LLaDA (model) configuration. + """ + + # Note that the defaults for these attributes are equivalent to the base GPT2 model. + + d_model: int = 768 + """ + The hidden size of the model. + """ + + n_heads: int = 12 + """ + The number of self-attention heads. + """ + + n_kv_heads: Optional[int] = None + """ + The number of heads to use for keys and values. Defaults to `n_heads`. + Set this to ``None`` or ``n_heads`` for normal multi-head attention. + Set this to 1 for multi-query attention. + Set it to some in-between value for Llama2-style grouped query attention. + """ + + n_layers: int = 12 + """ + The number of layers/blocks. + """ + + mlp_ratio: int = 4 + """ + The ratio of the inner MLP dimensionality to ``d_model``. + This is only used when ``mlp_hidden_size`` is not set. + """ + + mlp_hidden_size: Optional[int] = None + """ + Set the exact hidden size for the MLP. Otherwise the inner MLP hidden size will be set to `mlp_ratio * d_model`. + """ + + activation_type: ActivationType = ActivationType.swiglu + """ + The activation function to use within the MLP layers. + """ + + block_type: BlockType = BlockType.sequential + """ + The transformer block implementation. + """ + + block_group_size: int = 1 + """ + The number of blocks to group together into a single parent block. + This has no affect on the number of parameters in the model and is only used to wrap groups + of blocks together with a single FSDP wrapper during training. + """ + + alibi: bool = False + """ + If ``True``, use ALiBi embeddings. Mutually exclusive with ``rope``. + """ + + alibi_bias_max: float = 8.0 + """ + Maximum absolute value of ALiBi bias. + """ + + rope: bool = False + """ + Use rotary positional embeddings (RoPE). Mutually exclusive with ``alibi``. + """ + + rope_full_precision: bool = True + """ + If ``True``, apply RoPE embeddings at full precision regardless of the input type. Otherwise, + apply RoPE at the precision of the input. + """ + + flash_attention: bool = False + """ + If ``True``, use ``FlashAttention``. + """ + + attention_dropout: float = 0.1 + """ + The dropout probability within the attention modules. + """ + + multi_query_attention: Optional[bool] = None + """ + Use the Multi-Query formulation of attention used in PaLM. This reduces the number of parameters + and is more efficient during inference. + """ + + attention_layer_norm: bool = False + """ + Apply layer norm to the keys and queries within the attention mechanism. + This can help stabilize training. + """ + + residual_dropout: float = 0.1 + """ + The dropout probability for the MLP and attention output within each block. + """ + + embedding_dropout: float = 0.1 + """ + The dropout probability for embeddings. + """ + + input_emb_norm: bool = False + """ + An input hidden_states norm implementation by gemmma. + """ + + layer_norm_type: LayerNormType = LayerNormType.default + """ + The layernorm implementation to use. + """ + + layer_norm_with_affine: bool = True + """ + Whether to include bias and weight parameters for the layer norms. + This only affects layer norms that are immediately followed by a linear layer in the forward pass, + so everything except QK-norms. To turn off affines for QK norms as well, set :attr:`attention_layer_norm_with_affine` + to ``False``. + """ + + rms_norm_eps: float = 1e-05 + """ + The rms layernorm eps param. + """ + + attention_layer_norm_with_affine: bool = True + """ + Toggle affine transform for the QK norms. + """ + + max_sequence_length: int = 1024 + """ + The maximum input sequence length supported by the model. + """ + + rope_theta: float = 10000.0 + """ + The rope base param. + """ + + include_qkv_bias: Optional[bool] = False + """ + Whether or not to include bias parameters in qkv linear layers. + """ + + include_bias: bool = False + """ + Whether or not to include bias parameters in linear layers. + In PaLM, they got rid of all bias terms because they found that large + models tend to have near 0 bias terms anyway. + """ + + bias_for_layer_norm: Optional[bool] = None + """ + Whether or not to include bias parameters in layer norm. + This is separate from the include_bias parameter, because of a ROCm crash when biases are disabled in + layer norm. + When this is None (the default), it inherits the setting from include_bias. + """ + + scale_logits: bool = False + """ + If ``True``, scale the output logits by ``1 / sqrt(d_model)``. + """ + + vocab_size: int = 50257 + """ + Vocabulary size of the model. + """ + + embedding_size: Optional[int] = 50304 + """ + The number of embeddings, i.e. the number of tokens. If set to ``None`` it will default + to ``vocab_size``. If ``vocab_size`` is not a multiple of 128, setting this to the + next multiple of 128 that's greater than ``vocab_size`` can improve throughput + substantially. + """ + + weight_tying: bool = True + """ + Whether to tie output linear weights to the input embedding. + """ + + eos_token_id: int = 50256 + """ + The ID of the end-of-sentence special token. + """ + + pad_token_id: int = 50256 + """ + The ID of the token to use for padding. Defaults to the ID of the EOS token. + """ + + mask_token_id: Optional[int] = 50256 + """ + The ID of the token to use for mask token. Defaults to the ID of the EOS token. + """ + + init_device: Optional[str] = None + """ + The torch device to use when initializing the model parameters, e.g. "cpu", "cuda:0", "meta". + """ + + init_fn: InitFnType = InitFnType.normal + """ + The weight initialization strategy. + """ + + init_std: float = 0.02 + """ + The standard deviation to use when initializing weights with a "fixed distribution" ``init_fn``, such + as "normal". + """ + + init_cutoff_factor: Optional[float] = None + """ + A positive factor used to scale the cutoff values when initializing weights with a "fixed distribution" ``init_fn``, such + as "normal". Setting this to None means values are not cutoff. + """ + + precision: Optional[str] = None + """ + Precision used to train/evaluate with. You shouldn't set this directly. + See :data:`TrainConfig.precision` instead. + """ + + @property + def effective_n_kv_heads(self) -> int: + if self.n_kv_heads is None: + if self.multi_query_attention is True: + return 1 + else: + return self.n_heads + else: + if self.multi_query_attention is None: + return self.n_kv_heads + if self.multi_query_attention: + n_kv_heads_should_be = 1 + else: + n_kv_heads_should_be = self.n_heads + if self.n_kv_heads == n_kv_heads_should_be: + return n_kv_heads_should_be + else: + raise Exception( + "You can't set `multi_query_attention` and `n_kv_heads` at the same time." + ) + +class ActivationCheckpointingStrategy(StrEnum): + whole_layer = "whole_layer" + """ + Checkpoint every transformer layer. + """ + + one_in_two = "one_in_two" + """ + Checkpoint one in two transformer layers. + """ + + one_in_three = "one_in_three" + """ + Checkpoint one in three transformer layers. + """ + + one_in_four = "one_in_four" + """ + Checkpoint one in four transformer layers. + """ + + two_in_three = "two_in_three" + """ + Checkpoint two out of every three transformer layers. + """ + + three_in_four = "three_in_four" + """ + Checkpoint three out of four of every transformer layers. + """ + + four_in_five = "four_in_five" + """ + Checkpoint four out of five of every transformer layers. + """ + + nine_in_ten = "nine_in_ten" + """ + Checkpoint nine out of ten of every transformer layers. + """ + + fine_grained = "fine_grained" + """ + Focus checkpointing on where it is cheap to recompute and saves most memory. + """ + + +class LLaDAConfig(PretrainedConfig): + model_type = "llada" + keys_to_ignore_at_inference = ["past_key_values"] # TODO: confirm + + def __init__(self, use_cache: bool = False, **kwargs): + model_config = ModelConfig() + all_kwargs = model_config.__dict__ + all_kwargs.update(kwargs) + all_kwargs.update({"use_cache": use_cache}) + all_kwargs.update( + { + "architectures": all_kwargs.get("architectures", ["LLaDAModelLM"]) + } + ) + super().__init__(**all_kwargs) + + @property + def num_attention_heads(self): + return self.n_heads + + @property + def num_hidden_layers(self): + return self.n_layers + + @property + def hidden_size(self): + return self.d_model + + +# Register the config class so that it is available for transformer pipelines, auto-loading etc. +AutoConfig.register("llada", LLaDAConfig) diff --git a/Discrete-Diffusion-Forcing/D2F-train/model/modeling_llada.py b/Discrete-Diffusion-Forcing/D2F-train/model/modeling_llada.py new file mode 100644 index 0000000000000000000000000000000000000000..4d9c4eb22e7eafa6cb93166202f4f470e18b3796 --- /dev/null +++ b/Discrete-Diffusion-Forcing/D2F-train/model/modeling_llada.py @@ -0,0 +1,1500 @@ +from __future__ import annotations + +import logging +import math +import sys +from abc import abstractmethod +from collections import defaultdict +from functools import partial +from typing import ( + Callable, + Dict, + Iterable, + List, + NamedTuple, + Optional, + Sequence, + Set, + Tuple, + cast, +) +from dataclasses import fields +from typing import List, Optional, Tuple, Union + +import torch +import torch.backends.cuda +import torch.nn as nn +import torch.nn.functional as F +from torch import einsum +from transformers import PreTrainedModel +from transformers.modeling_outputs import CausalLMOutputWithPast +from transformers.models.auto import AutoModel +from transformers.cache_utils import Cache + +from .configuration_llada import ( + LLaDAConfig, + StrEnum, + InitFnType, + ActivationType, + BlockType, + LayerNormType, + ModelConfig, + ActivationCheckpointingStrategy, +) + +if sys.version_info.minor > 8: + from collections.abc import MutableMapping +elif sys.version_info.minor == 8: + from typing import MutableMapping +else: + raise SystemExit("This script supports Python 3.8 or higher") + +__all__ = [ + "LayerNormBase", + "LayerNorm", + "RMSLayerNorm", + "GemmaRMSLayerNorm", + "RotaryEmbedding", + "Activation", + "GELU", + "ReLU", + "SwiGLU", + "LLaDABlock", + "LLaDASequentialBlock", + "LLaDAModel", + "LLaDAOutput", + "LLaDAGenerateOutput", +] + + +log = logging.getLogger(__name__) + + +class ModuleType(StrEnum): + in_module = "in" + out_module = "out" + emb = "emb" + final_out = "final_out" + + +def init_weights( + config: ModelConfig, + module: Union[nn.Linear, nn.Embedding], + d: Optional[int] = None, + layer_id: Optional[int] = None, + std_factor: float = 1.0, + type_of_module: Optional[ModuleType] = None, +) -> None: + """ + Initialize weights of a linear or embedding module. + + :param config: The model config. + :param module: The linear or embedding submodule to initialize. + :param d: The effective input dimensionality of the weights. This could be smaller than the actual dimensions + for fused layers. + :param layer_id: When set, the standard deviation for the "mitchell" method will be adjusted by + ``1 / sqrt(2 * (layer_id + 1))``. + """ + d = d if d is not None else config.d_model + if config.init_fn == InitFnType.normal: + std = config.init_std * std_factor + if config.init_cutoff_factor is not None: + cutoff_value = config.init_cutoff_factor * std + nn.init.trunc_normal_(module.weight, mean=0.0, std=std, a=-cutoff_value, b=cutoff_value) + else: + nn.init.normal_(module.weight, mean=0.0, std=std) + elif config.init_fn == InitFnType.mitchell: + std = std_factor / math.sqrt(d) + if layer_id is not None: + std = std / math.sqrt(2 * (layer_id + 1)) + nn.init.trunc_normal_(module.weight, mean=0.0, std=std, a=-3 * std, b=3 * std) + elif config.init_fn == InitFnType.kaiming_normal: + nn.init.kaiming_normal_(module.weight, nonlinearity="relu") + elif config.init_fn == InitFnType.fan_in: + std = std_factor / math.sqrt(d) + nn.init.normal_(module.weight, mean=0.0, std=std) + elif config.init_fn == InitFnType.full_megatron: + if type_of_module is None: + raise RuntimeError(f"When using the {InitFnType.full_megatron} init, every module must have a type.") + + cutoff_factor = config.init_cutoff_factor + if cutoff_factor is None: + cutoff_factor = 3 + + if type_of_module == ModuleType.in_module: + # for att_proj (same as QKV), ff_proj + std = config.init_std + elif type_of_module == ModuleType.out_module: + # for attn_out, ff_out + std = config.init_std / math.sqrt(2.0 * config.n_layers) + elif type_of_module == ModuleType.emb: + # positional embeddings (wpe) + # token embeddings (wte) + std = config.init_std + elif type_of_module == ModuleType.final_out: + # final output (ff_out) + std = config.d_model**-0.5 + else: + raise RuntimeError(f"Unknown module type '{type_of_module}'") + nn.init.trunc_normal_( + module.weight, + mean=0.0, + std=std, + a=-cutoff_factor * std, + b=cutoff_factor * std, + ) + else: + raise NotImplementedError(config.init_fn) + + if isinstance(module, nn.Linear): + if module.bias is not None: + nn.init.zeros_(module.bias) + + if config.init_fn == InitFnType.normal and getattr(module, "_is_residual", False): + with torch.no_grad(): + module.weight.div_(math.sqrt(2 * config.n_layers)) + + +def ensure_finite_(x: torch.Tensor, check_neg_inf: bool = True, check_pos_inf: bool = False): + """ + Modify ``x`` in place to replace ``float("-inf")`` with the minimum value of the dtype when ``check_neg_inf`` + is ``True`` and to replace ``float("inf")`` with the maximum value of the dtype when ``check_pos_inf`` is ``True``. + """ + if check_neg_inf: + x.masked_fill_(x == float("-inf"), torch.finfo(x.dtype).min) + if check_pos_inf: + x.masked_fill_(x == float("inf"), torch.finfo(x.dtype).max) + + +def activation_checkpoint_function(cfg: ModelConfig): + preserve_rng_state = ( + (cfg.attention_dropout == 0.0) and (cfg.embedding_dropout == 0.0) and (cfg.residual_dropout == 0.0) + ) + from torch.utils.checkpoint import checkpoint + + return partial( + checkpoint, + preserve_rng_state=preserve_rng_state, + use_reentrant=False, + ) + + +class BufferCache(dict, MutableMapping[str, torch.Tensor]): + """ + Cache for attention biases and other things that would normally be stored as buffers. + We avoid using buffers because we've run into various issues doing so with FSDP. + In general it appears the way FSDP handles buffers is not well-defined. + It doesn't shard them but apparently it does synchronize them across processes, which we want to avoid + since (A) it isn't necessary, and (B) we sometimes have `-inf` in these biases which might get turned into + NaNs when they're synchronized due to casting or some other issue. + """ + + +def _non_meta_init_device(config: ModelConfig) -> torch.device: + if config.init_device is not None and config.init_device != "meta": + return torch.device(config.init_device) + else: + return torch.device("cuda" if torch.cuda.is_available() else "cpu") + + +class Dropout(nn.Dropout): + def forward(self, input: torch.Tensor) -> torch.Tensor: + if self.p == 0.0: + return input + else: + return F.dropout(input, self.p, self.training, self.inplace) + + +class LayerNormBase(nn.Module): + def __init__( + self, + config: ModelConfig, + *, + size: Optional[int] = None, + elementwise_affine: Optional[bool] = True, + eps: float = 1e-05, + ): + super().__init__() + self.config = config + self.eps = eps + self.normalized_shape = (size or config.d_model,) + if elementwise_affine or (elementwise_affine is None and self.config.layer_norm_with_affine): + self.weight = nn.Parameter(torch.ones(self.normalized_shape, device=config.init_device)) + use_bias = self.config.bias_for_layer_norm + if use_bias is None: + use_bias = self.config.include_bias + if use_bias: + self.bias = nn.Parameter(torch.zeros(self.normalized_shape, device=config.init_device)) + else: + self.register_parameter("bias", None) + else: + self.register_parameter("bias", None) + self.register_parameter("weight", None) + + @abstractmethod + def forward(self, x: torch.Tensor) -> torch.Tensor: + raise NotImplementedError + + @classmethod + def build(cls, config: ModelConfig, size: Optional[int] = None, **kwargs) -> LayerNormBase: + if config.layer_norm_type == LayerNormType.default: + return LayerNorm(config, size=size, low_precision=False, **kwargs) + elif config.layer_norm_type == LayerNormType.low_precision: + return LayerNorm(config, size=size, low_precision=True, **kwargs) + elif config.layer_norm_type == LayerNormType.rms: + return RMSLayerNorm(config, size=size, **kwargs) + elif config.layer_norm_type == LayerNormType.gemma_rms: + return GemmaRMSLayerNorm(config, size=size, **kwargs) + else: + raise NotImplementedError(f"Unknown LayerNorm type: '{config.layer_norm_type}'") + + def _cast_if_autocast_enabled(self, tensor: torch.Tensor, dtype: Optional[torch.dtype] = None) -> torch.Tensor: + # NOTE: `is_autocast_enabled()` only checks for CUDA autocast, so we use the separate function + # `is_autocast_cpu_enabled()` for CPU autocast. + # See https://github.com/pytorch/pytorch/issues/110966. + if tensor.device.type == "cuda" and torch.is_autocast_enabled(): + return tensor.to(dtype=dtype if dtype is not None else torch.get_autocast_gpu_dtype()) + elif tensor.device.type == "cpu" and torch.is_autocast_cpu_enabled(): + return tensor.to(dtype=dtype if dtype is not None else torch.get_autocast_cpu_dtype()) + else: + return tensor + + def reset_parameters(self): + if self.weight is not None: + torch.nn.init.ones_(self.weight) # type: ignore + if self.bias is not None: + torch.nn.init.zeros_(self.bias) # type: ignore + + +class LayerNorm(LayerNormBase): + """ + The default :class:`LayerNorm` implementation which can optionally run in low precision. + """ + + def __init__( + self, + config: ModelConfig, + size: Optional[int] = None, + low_precision: bool = False, + elementwise_affine: Optional[bool] = None, + eps: float = 1e-05, + ): + super().__init__(config, size=size, elementwise_affine=elementwise_affine, eps=eps) + self.low_precision = low_precision + + def forward(self, x: torch.Tensor) -> torch.Tensor: + if self.low_precision: + module_device = x.device + downcast_x = self._cast_if_autocast_enabled(x) + downcast_weight = ( + self._cast_if_autocast_enabled(self.weight) if self.weight is not None else self.weight + ) + downcast_bias = self._cast_if_autocast_enabled(self.bias) if self.bias is not None else self.bias + with torch.autocast(enabled=False, device_type=module_device.type): + return F.layer_norm( + downcast_x, self.normalized_shape, weight=downcast_weight, bias=downcast_bias, eps=self.eps + ) + else: + return F.layer_norm(x, self.normalized_shape, weight=self.weight, bias=self.bias, eps=self.eps) + + +class RMSLayerNorm(LayerNormBase): + """ + RMS layer norm, a simplified :class:`LayerNorm` implementation + """ + + def __init__( + self, + config: ModelConfig, + size: Optional[int] = None, + elementwise_affine: Optional[bool] = None, + eps: float = 1e-5, + ): + super().__init__(config, size=size, elementwise_affine=elementwise_affine, eps=config.rms_norm_eps) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + # with torch.autocast(enabled=False, device_type=x.device.type): + og_dtype = x.dtype + x = x.to(torch.float32) + # print(x.dtype,x.shape) + variance = x*x + # print(variance) + variance = variance.mean(dim=-1,keepdim=True) + x = x * torch.rsqrt(variance + self.eps) + x = x.to(og_dtype) + + if self.weight is not None: + if self.bias is not None: + return self.weight * x + self.bias + else: + return self.weight * x + else: + return x + + +class GemmaRMSLayerNorm(LayerNormBase): + """ + Gemma RMS layer norm, a simplified :class:`LayerNorm` implementation + """ + + def __init__( + self, + config: ModelConfig, + size: Optional[int] = None, + elementwise_affine: Optional[bool] = None, + eps: float = 1e-5, + ): + super().__init__(config, size=size, elementwise_affine=elementwise_affine, eps=config.rms_norm_eps) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + with torch.autocast(enabled=False, device_type=x.device.type): + og_dtype = x.dtype + x = x.to(torch.float32) + variance = x.pow(2).mean(-1, keepdim=True) + x = x * torch.rsqrt(variance + self.eps) + x = x.to(og_dtype) + + if self.weight is not None: + if self.bias is not None: + return x * (1 + self.weight) + self.bias + else: + return x * (1 + self.weight) + else: + return x + + +class RotaryEmbedding(nn.Module): + """ + [Rotary positional embeddings (RoPE)](https://arxiv.org/abs/2104.09864). + """ + + def __init__(self, config: ModelConfig, cache: BufferCache): + super().__init__() + self.config = config + self.__cache = cache + # Warm up cache. + self.rope_theta = config.rope_theta + self.get_rotary_embedding(config.max_sequence_length, _non_meta_init_device(config)) + + def get_rotary_embedding(self, seq_len: int, device: torch.device) -> Tuple[torch.Tensor, torch.Tensor]: + if ( + (pos_sin := self.__cache.get("rope_pos_sin")) is not None + and (pos_cos := self.__cache.get("rope_pos_cos")) is not None + and pos_sin.shape[-2] >= seq_len + and pos_cos.shape[-2] >= seq_len + ): + if pos_sin.device != device: + pos_sin = pos_sin.to(device) + self.__cache["rope_pos_sin"] = pos_sin + if pos_cos.device != device: + pos_cos = pos_cos.to(device) + self.__cache["rope_pos_cos"] = pos_cos + return pos_sin[:, :, :seq_len, :], pos_cos[:, :, :seq_len, :] + + with torch.autocast(device.type, enabled=False): + dim = self.config.d_model // self.config.n_heads + inv_freq = 1.0 / (self.rope_theta ** (torch.arange(0, dim, 2, device=device, dtype=torch.float) / dim)) + seq = torch.arange(seq_len, device=device, dtype=torch.float) + freqs = einsum("i , j -> i j", seq, inv_freq) + positions = torch.cat((freqs, freqs), dim=-1) + pos_sin, pos_cos = positions.sin()[None, None, :, :], positions.cos()[None, None, :, :] + self.__cache["rope_pos_sin"] = pos_sin + self.__cache["rope_pos_cos"] = pos_cos + return pos_sin, pos_cos + + def rotate_half(self, x: torch.Tensor) -> torch.Tensor: + B, nh, T, hs = x.size() + x = x.view(B, nh, T, 2, hs // 2) + x1, x2 = x.unbind(dim=-2) + return torch.cat((-x2, x1), dim=-1) + + def apply_rotary_pos_emb(self, pos_sin: torch.Tensor, pos_cos: torch.Tensor, t: torch.Tensor) -> torch.Tensor: + return ((t * pos_cos) + (self.rotate_half(t) * pos_sin)).to(t.dtype) + + def forward(self, q: torch.Tensor, k: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]: + if self.config.rope_full_precision: + q_, k_ = q.float(), k.float() + else: + q_, k_ = q, k + + with torch.autocast(q.device.type, enabled=False): + query_len, key_len = q_.shape[-2], k_.shape[-2] # could be different if layer_past not None + pos_sin, pos_cos = self.get_rotary_embedding(key_len, q_.device) + pos_sin = pos_sin.type_as(q_) + pos_cos = pos_cos.type_as(q_) + q_ = self.apply_rotary_pos_emb( + pos_sin[:, :, key_len - query_len : key_len, :], + pos_cos[:, :, key_len - query_len : key_len, :], + q_, + ) + k_ = self.apply_rotary_pos_emb(pos_sin, pos_cos, k_) + return q_.type_as(q), k_.type_as(k) + + +class Activation(nn.Module): + def __init__(self, config: ModelConfig): + super().__init__() + self.config = config + + @abstractmethod + def forward(self, x: torch.Tensor) -> torch.Tensor: + raise NotImplementedError + + @property + @abstractmethod + def output_multiplier(self) -> float: + raise NotImplementedError + + @classmethod + def build(cls, config: ModelConfig) -> Activation: + if config.activation_type == ActivationType.gelu: + return cast(Activation, GELU(approximate="none")) + elif config.activation_type == ActivationType.relu: + return cast(Activation, ReLU(inplace=False)) + elif config.activation_type == ActivationType.silu: + return cast(Activation, SiLU(inplace=False)) + elif config.activation_type == ActivationType.swiglu: + return SwiGLU(config) + else: + raise NotImplementedError(f"Unknown activation: '{config.activation_type}'") + + +class GELU(nn.GELU): + @property + def output_multiplier(self) -> float: + return 1.0 + + +class ReLU(nn.ReLU): + @property + def output_multiplier(self) -> float: + return 1.0 + +class SiLU(nn.SiLU): + @property + def output_multiplier(self) -> float: + return 1.0 + +class SwiGLU(Activation): + def forward(self, x: torch.Tensor) -> torch.Tensor: + x, gate = x.chunk(2, dim=-1) + return F.silu(gate) * x + + @property + def output_multiplier(self) -> float: + return 0.5 + + +def causal_attention_bias(seq_len: int, device: torch.device) -> torch.FloatTensor: + att_bias = torch.triu( + torch.ones(seq_len, seq_len, device=device, dtype=torch.float), + diagonal=1, + ) + att_bias.masked_fill_(att_bias == 1, torch.finfo(att_bias.dtype).min) + return att_bias.view(1, 1, seq_len, seq_len) # type: ignore + + +def get_causal_attention_bias(cache: BufferCache, seq_len: int, device: torch.device) -> torch.Tensor: + if (causal_bias := cache.get("causal_attention_bias")) is not None and causal_bias.shape[-1] >= seq_len: + if causal_bias.device != device: + causal_bias = causal_bias.to(device) + cache["causal_attention_bias"] = causal_bias + return causal_bias + with torch.autocast(device.type, enabled=False): + causal_bias = causal_attention_bias(seq_len, device) + cache["causal_attention_bias"] = causal_bias + return causal_bias + + +def alibi_attention_bias(seq_len: int, config: ModelConfig, device: torch.device) -> torch.FloatTensor: + alibi_bias = torch.arange(1 - seq_len, 1, dtype=torch.float, device=device).view(1, 1, 1, seq_len) + + # shape: (1, 1, seq_len, seq_len) + alibi_bias = alibi_bias - torch.arange(1 - seq_len, 1, dtype=torch.float, device=device).view(1, 1, seq_len, 1) + alibi_bias.abs_().mul_(-1) + + # shape: (n_heads,) + m = torch.arange(1, config.n_heads + 1, dtype=torch.float, device=device) + m.mul_(config.alibi_bias_max / config.n_heads) + + # shape: (1, n_heads, seq_len, seq_len) + return alibi_bias * (1.0 / (2 ** m.view(1, config.n_heads, 1, 1))) # type: ignore + + +class LLaDABlock(nn.Module): + """ + A base class for transformer block implementations. + """ + + def __init__(self, layer_id: int, config: ModelConfig, cache: BufferCache): + super().__init__() + self.layer_id = layer_id + self.config = config + self.hidden_size = ( + config.mlp_hidden_size if config.mlp_hidden_size is not None else config.mlp_ratio * config.d_model + ) + self.__cache = cache + assert config.d_model % config.n_heads == 0 + + self._activation_checkpoint_fn = None + + # Dropout. + self.dropout = Dropout(config.residual_dropout) + + # Layer norms. + self.k_norm: Optional[LayerNormBase] = None + self.q_norm: Optional[LayerNormBase] = None + if config.attention_layer_norm: + self.k_norm = LayerNormBase.build( + config, + size=(config.d_model // config.n_heads) * config.effective_n_kv_heads, + elementwise_affine=config.attention_layer_norm_with_affine, + ) + self.q_norm = LayerNormBase.build(config, elementwise_affine=config.attention_layer_norm_with_affine) + + # Activation function. + self.act = Activation.build(config) + assert (self.act.output_multiplier * self.hidden_size) % 1 == 0 + + # Attention output projection. + self.attn_out = nn.Linear( + config.d_model, config.d_model, bias=config.include_bias, device=config.init_device + ) + + # Feed-forward output projection. + self.ff_out = nn.Linear( + int(self.act.output_multiplier * self.hidden_size), + config.d_model, + bias=config.include_bias, + device=config.init_device, + ) + self.ff_out._is_residual = True # type: ignore + + # Rotary embeddings. + if self.config.rope: + self.rotary_emb = RotaryEmbedding(config, self.__cache) + + self.flash_attn_func = None + if config.flash_attention: + try: + from flash_attn import flash_attn_func # type: ignore + + self.flash_attn_func = flash_attn_func + except ModuleNotFoundError: + pass + + def reset_parameters(self): + if self.k_norm is not None: + self.k_norm.reset_parameters() + if self.q_norm is not None: + self.q_norm.reset_parameters() + init_weights( + self.config, + self.attn_out, + d=self.config.d_model, + layer_id=self.layer_id, + type_of_module=ModuleType.out_module, + ) + init_weights( + self.config, + self.ff_out, + d=self.ff_out.in_features, + layer_id=self.layer_id, + type_of_module=ModuleType.out_module, + ) + + def set_activation_checkpointing(self, strategy: Optional[ActivationCheckpointingStrategy]): + if strategy == ActivationCheckpointingStrategy.fine_grained: + self._activation_checkpoint_fn = activation_checkpoint_function(self.config) + else: + self._activation_checkpoint_fn = None + + @classmethod + def _cast_attn_bias(cls, bias: torch.Tensor, input_dtype: torch.dtype) -> torch.Tensor: + target_dtype = input_dtype + # NOTE: `is_autocast_enabled()` only checks for CUDA autocast, so we use the separate function + # `is_autocast_cpu_enabled()` for CPU autocast. + # See https://github.com/pytorch/pytorch/issues/110966. + if bias.device.type == "cuda" and torch.is_autocast_enabled(): + target_dtype = torch.get_autocast_gpu_dtype() + elif bias.device.type == "cpu" and torch.is_autocast_cpu_enabled(): + target_dtype = torch.get_autocast_cpu_dtype() + if bias.dtype != target_dtype: + bias = bias.to(target_dtype) + ensure_finite_(bias, check_neg_inf=True, check_pos_inf=False) + return bias + + def _scaled_dot_product_attention( + self, + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + attn_mask: Optional[torch.Tensor] = None, + dropout_p: float = 0.0, + is_causal: bool = False, + ) -> torch.Tensor: + """ + Computes scaled dot product attention on query, key and value tensors, using an optional + attention mask if passed, and applying dropout if a probability greater than 0.0 is specified. + """ + if self.flash_attn_func is not None and attn_mask is None: + r = self.flash_attn_func( + q.transpose(1, 2), k.transpose(1, 2), v.transpose(1, 2), dropout_p=dropout_p, causal=False + ) + return r.transpose(1, 2) + else: + # torch's sdpa doesn't support GQA, so we're doing this + assert k.size(1) == v.size(1) + num_kv_heads = k.size(1) + num_q_heads = q.size(1) + if num_q_heads != num_kv_heads: + assert num_q_heads % num_kv_heads == 0 + k = k.repeat_interleave(num_q_heads // num_kv_heads, dim=1, output_size=num_q_heads) + v = v.repeat_interleave(num_q_heads // num_kv_heads, dim=1, output_size=num_q_heads) + # Modify: MDM set causal to False, and with no attn_mask. + return F.scaled_dot_product_attention( + q, + k, + v, + attn_mask=attn_mask, + dropout_p=dropout_p, + is_causal=False, + ) + + def attention( + self, + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + attention_bias: Optional[torch.Tensor] = None, + layer_past: Optional[Tuple[torch.Tensor, torch.Tensor]] = None, + use_cache: bool = False, + ) -> Tuple[torch.Tensor, Optional[Tuple[torch.Tensor, torch.Tensor]]]: + B, T, C = q.size() # batch size, sequence length, d_model + dtype = k.dtype + + # Optionally apply layer norm to keys and queries. + if self.q_norm is not None and self.k_norm is not None: + q = self.q_norm(q).to(dtype=dtype) + k = self.k_norm(k).to(dtype=dtype) + + # Move head forward to be next to the batch dim. + # shape: (B, nh, T, hs) + q = q.view(B, T, self.config.n_heads, C // self.config.n_heads).transpose(1, 2) + # shape: (B, n_kv_h, T, hs) + k = k.view(B, T, self.config.effective_n_kv_heads, C // self.config.n_heads).transpose(1, 2) + # shape: (B, n_kv_h, T, hs) + v = v.view(B, T, self.config.effective_n_kv_heads, C // self.config.n_heads).transpose(1, 2) + + if layer_past is not None: + past_key, past_value = layer_past + k = torch.cat((past_key, k), dim=-2) + v = torch.cat((past_value, v), dim=-2) + + present = (k, v) if use_cache else None + query_len, key_len = q.shape[-2], k.shape[-2] # could be different if layer_past not None + + if self.config.rope: + # Apply rotary embeddings. + q, k = self.rotary_emb(q, k) + + # if attention_bias is not None: + # # Resize and cast attention bias. + # # The current dtype of the attention bias might not match the dtype that the SDP attn function will + # # run in if AMP is enabled, and this can be a problem if some tokens are masked out due to padding + # # as down-casting the attention bias to the autocast precision will result in -infs, which will + # # cause the SDP attn function to produce NaNs. + # attention_bias = self._cast_attn_bias( + # attention_bias[:, :, key_len - query_len : key_len, :key_len], dtype + # ) + + # Get the attention scores. + # shape: (B, nh, T, hs) + att = self._scaled_dot_product_attention( + q, + k, + v, + attn_mask=attention_bias, + dropout_p=0.0 if not self.training else self.config.attention_dropout, + is_causal=False, + ) + + # Re-assemble all head outputs side-by-side. + att = att.transpose(1, 2).contiguous().view(B, T, C) + + # Apply output projection. + return self.attn_out(att), present + + @abstractmethod + def forward( + self, + x: torch.Tensor, + attention_bias: Optional[torch.FloatTensor] = None, + layer_past: Optional[Tuple[torch.Tensor, torch.Tensor]] = None, + use_cache: bool = False, + ) -> Tuple[torch.Tensor, Optional[Tuple[torch.Tensor, torch.Tensor]]]: + raise NotImplementedError + + @classmethod + def build(cls, layer_id: int, config: ModelConfig, cache: BufferCache) -> LLaDABlock: + if config.block_type == BlockType.sequential: + return LLaDASequentialBlock(layer_id, config, cache) + elif config.block_type == BlockType.llama: + return LLaDALlamaBlock(layer_id, config, cache) + else: + raise NotImplementedError(f"Unknown block type: '{config.block_type}'") + + +class LLaDASequentialBlock(LLaDABlock): + """ + This is a typical transformer block where the output is computed as ``MLP(LN(x + Attention(LN(x))))`` + (plus another skip connection). + """ + + def __init__(self, layer_id: int, config: ModelConfig, cache: BufferCache): + super().__init__(layer_id, config, cache) + # Layer norms. + self.attn_norm = LayerNorm.build(config) + self.ff_norm = LayerNorm.build(config) + # Attention input projection. Projects x -> (q, k, v) + head_dim = config.d_model // config.n_heads + self.fused_dims = ( + config.d_model, + config.effective_n_kv_heads * head_dim, + config.effective_n_kv_heads * head_dim, + ) + self.att_proj = nn.Linear( + config.d_model, sum(self.fused_dims), bias=config.include_bias | config.include_qkv_bias, device=config.init_device + ) + # Feed-forward input projection. + self.ff_proj = nn.Linear( + config.d_model, self.hidden_size, bias=config.include_bias, device=config.init_device + ) + + def reset_parameters(self): + super().reset_parameters() + self.attn_norm.reset_parameters() + self.ff_norm.reset_parameters() + # NOTE: the standard deviation for these weights does not depend on the layer. + init_weights( + self.config, self.att_proj, d=self.config.d_model, layer_id=None, type_of_module=ModuleType.in_module + ) + init_weights( + self.config, self.ff_proj, d=self.config.d_model, layer_id=None, type_of_module=ModuleType.in_module + ) + + def forward( + self, + x: torch.Tensor, + attention_bias: Optional[torch.Tensor] = None, + layer_past: Optional[Tuple[torch.Tensor, torch.Tensor]] = None, + use_cache: bool = False, + ) -> Tuple[torch.Tensor, Optional[Tuple[torch.Tensor, torch.Tensor]]]: + # Get query, key, value projections. + # shape: + # - for regular attn q, k, v: (batch_size, seq_len, d_model) + # - for multi-query attn q: (batch_size, seq_len, d_model) + # k, v: (batch_size, seq_len, d_model // n_heads) + # - for group query attn q: (batch_size, seq_len, d_model) + # k, v: (batch_size, seq_len, d_model // n_kv_heads) + if self._activation_checkpoint_fn is not None: + q, k, v = self.att_proj(self._activation_checkpoint_fn(self.attn_norm, x)).split( + self.fused_dims, dim=-1 + ) + else: + q, k, v = self.att_proj(self.attn_norm(x)).split(self.fused_dims, dim=-1) + + # Get attention scores. + if self._activation_checkpoint_fn is not None: + att, cache = self._activation_checkpoint_fn( # type: ignore + self.attention, q, k, v, attention_bias, layer_past=layer_past, use_cache=use_cache + ) + else: + att, cache = self.attention(q, k, v, attention_bias, layer_past=layer_past, use_cache=use_cache) + + # Add attention scores. + # shape: (B, T, C) + x = x + self.dropout(att) + + # Add feed-forward projection. + # shape: (batch_size, seq_len, d_model) + og_x = x + if self._activation_checkpoint_fn is not None: + x = self._activation_checkpoint_fn(self.ff_norm, x) # type: ignore + else: + x = self.ff_norm(x) + x = self.ff_proj(x) + if self._activation_checkpoint_fn is not None: + x = self._activation_checkpoint_fn(self.act, x) # type: ignore + else: + x = self.act(x) + x = self.ff_out(x) + x = self.dropout(x) + x = og_x + x + + return x, cache + + +class LLaDALlamaBlock(LLaDABlock): + """ + This is a transformer block where the output is computed as ``MLP(LN(x + Attention(LN(x))))`` + (plus another skip connection). This block is similar to `LLaDASequentialBlock` + but some operations have slightly different implementations to imitate the + behavior of Llama. + """ + + def __init__(self, layer_id: int, config: ModelConfig, cache: BufferCache): + super().__init__(layer_id, config, cache) + # Layer norms. + self.attn_norm = LayerNorm.build(config) + self.ff_norm = LayerNorm.build(config) + self.__cache = cache + + # Attention input projection. Projects x -> (q, k, v) + head_dim = config.d_model // config.n_heads + q_proj_out_dim = config.d_model + k_proj_out_dim = config.effective_n_kv_heads * head_dim + v_proj_out_dim = config.effective_n_kv_heads * head_dim + self.q_proj = nn.Linear( + config.d_model, q_proj_out_dim, bias=config.include_bias | config.include_qkv_bias, device=config.init_device + ) + self.k_proj = nn.Linear( + config.d_model, k_proj_out_dim, bias=config.include_bias | config.include_qkv_bias, device=config.init_device + ) + self.v_proj = nn.Linear( + config.d_model, v_proj_out_dim, bias=config.include_bias | config.include_qkv_bias, device=config.init_device + ) + + # Feed-forward input projection. + self.ff_proj = nn.Linear( + config.d_model, self.hidden_size, bias=config.include_bias, device=config.init_device + ) + # new add + self.up_proj = nn.Linear( + config.d_model, self.hidden_size, bias=config.include_bias, device=config.init_device + ) + + def reset_parameters(self): + super().reset_parameters() + self.attn_norm.reset_parameters() + self.ff_norm.reset_parameters() + # NOTE: the standard deviation for these weights does not depend on the layer. + init_weights(self.config, self.q_proj, d=self.config.d_model, layer_id=None) + init_weights(self.config, self.k_proj, d=self.config.d_model, layer_id=None) + init_weights(self.config, self.v_proj, d=self.config.d_model, layer_id=None) + init_weights(self.config, self.ff_proj, d=self.config.d_model, layer_id=None) + init_weights(self.config, self.up_proj, d=self.config.d_model, layer_id=None) # new add + + def forward( + self, + x: torch.Tensor, + attention_bias: Optional[torch.Tensor] = None, + layer_past: Optional[Tuple[torch.Tensor, torch.Tensor]] = None, + use_cache: bool = False, + ) -> Tuple[torch.Tensor, Optional[Tuple[torch.Tensor, torch.Tensor]]]: + # Get query, key, value projections. + # shape: + # - for regular attn q, k, v: (batch_size, seq_len, d_model) + # - for multi-query attn q: (batch_size, seq_len, d_model) + # k, v: (batch_size, seq_len, d_model // n_heads) + # - for group query attn q: (batch_size, seq_len, d_model) + # k, v: (batch_size, seq_len, d_model // n_kv_heads) + # print(x) + x_normed = self.attn_norm(x) + q = self.q_proj(x_normed) + k = self.k_proj(x_normed) + v = self.v_proj(x_normed) + + # Get attention scores. + if self._activation_checkpoint_fn is not None: + att, cache = self._activation_checkpoint_fn( # type: ignore + self.attention, q, k, v, attention_bias, layer_past=layer_past, use_cache=use_cache + ) + else: + att, cache = self.attention(q, k, v, attention_bias, layer_past=layer_past, use_cache=use_cache) + + # Add attention scores. + # shape: (B, T, C) + x = x + self.dropout(att) + + # Add feed-forward projection. + # shape: (batch_size, seq_len, d_model) + og_x = x + if self._activation_checkpoint_fn is not None: + x = self._activation_checkpoint_fn(self.ff_norm, x) # type: ignore + else: + x = self.ff_norm(x) + x, x_up = self.ff_proj(x), self.up_proj(x) # new add + if self._activation_checkpoint_fn is not None: + x = self._activation_checkpoint_fn(self.act, x) # type: ignore + else: + x = self.act(x) + x = x * x_up # new add + x = self.ff_out(x) + x = self.dropout(x) + x = og_x + x + + return x, cache + + +class LLaDAOutput(NamedTuple): + logits: torch.FloatTensor + """ + A tensor of shape `(batch_size, seq_len, vocab_size)` representing the log probabilities + for the next token *before* normalization via (log) softmax. + """ + + attn_key_values: Optional[List[Tuple[torch.Tensor, torch.Tensor]]] + """ + Attention keys and values from each block. + """ + + hidden_states: Optional[Tuple[torch.Tensor]] + """ + Hidden states from each block. + """ + + +class LLaDAGenerateOutput(NamedTuple): + token_ids: torch.LongTensor + """ + The generated token IDs, a tensor of shape `(batch_size, beam_size, max_steps)`. + These do *not* include the original input IDs. + """ + + scores: torch.FloatTensor + """ + The scores of the generated sequences, a tensor of shape `(batch_size, beam_size)`. + """ + + +class LLaDABlockGroup(nn.ModuleList): + def __init__(self, config: ModelConfig, layer_offset: int, modules: Optional[Iterable[nn.Module]] = None): + super().__init__(modules) + self.config = config + self.layer_offset = layer_offset + self.activation_checkpointing_strategy: Optional[ActivationCheckpointingStrategy] = None + self._activation_checkpoint_fn = activation_checkpoint_function(self.config) + + def forward( + self, + x: torch.Tensor, + attention_bias: Optional[torch.FloatTensor] = None, + layers_past: Optional[List[Tuple[torch.Tensor, torch.Tensor]]] = None, + use_cache: bool = False, + ) -> Tuple[torch.Tensor, Optional[List[Tuple[torch.Tensor, torch.Tensor]]]]: + attn_key_values: Optional[List[Tuple[torch.Tensor, torch.Tensor]]] = [] if use_cache else None + for block_idx, block in enumerate(self): + layer_past = None if layers_past is None else layers_past[block_idx] + block_idx += self.layer_offset + if ( + (self.activation_checkpointing_strategy == ActivationCheckpointingStrategy.whole_layer) + or ( + self.activation_checkpointing_strategy == ActivationCheckpointingStrategy.one_in_two + and block_idx % 2 == 0 + ) + or ( + self.activation_checkpointing_strategy == ActivationCheckpointingStrategy.one_in_three + and block_idx % 3 == 0 + ) + or ( + self.activation_checkpointing_strategy == ActivationCheckpointingStrategy.one_in_four + and block_idx % 4 == 0 + ) + ): + # shape: (batch_size, seq_len, d_model) + x, cache = self._activation_checkpoint_fn( # type: ignore + block, x, attention_bias=attention_bias, layer_past=layer_past, use_cache=use_cache + ) + else: + # shape: (batch_size, seq_len, d_model) + x, cache = block(x, attention_bias=attention_bias, layer_past=layer_past, use_cache=use_cache) + if attn_key_values is not None: + assert cache is not None + attn_key_values.append(cache) + return x, attn_key_values + + def reset_parameters(self): + for block in self: + block.reset_parameters() + + def set_activation_checkpointing(self, strategy: Optional[ActivationCheckpointingStrategy]): + self.activation_checkpointing_strategy = strategy + for block in self: + block.set_activation_checkpointing(strategy) + + +class LLaDAModel(nn.Module): + def __init__(self, config: ModelConfig, init_params: bool = True): + super().__init__() + self.config = config + self.__cache = BufferCache() + + # Validate config. + if self.config.alibi and self.config.flash_attention: + raise Exception("ALiBi is currently not supported with FlashAttention") + + if self.config.alibi and self.config.rope: + raise Exception("ALiBi and RoPE are mutually exclusive") + + if self.config.embedding_size is not None and self.config.embedding_size != self.config.vocab_size: + if self.config.embedding_size < self.config.vocab_size: + raise Exception("embedding size should be at least as big as vocab size") + elif self.config.embedding_size % 128 != 0: + import warnings + + warnings.warn( + "Embedding size is not a multiple of 128! This could hurt throughput performance.", UserWarning + ) + + self.activation_checkpointing_strategy: Optional[ActivationCheckpointingStrategy] = None + self._activation_checkpoint_fn: Callable = activation_checkpoint_function(self.config) + + if not ( + 0 < self.config.block_group_size <= self.config.n_layers + and self.config.n_layers % self.config.block_group_size == 0 + ): + raise Exception("n layers must be divisible by block group size") + + torch.backends.cuda.enable_flash_sdp(True) + torch.backends.cuda.enable_mem_efficient_sdp(False) # this is super slow so make sure torch won't use it + + self.transformer = nn.ModuleDict( + dict( + wte=nn.Embedding( + config.embedding_size or config.vocab_size, config.d_model, device=config.init_device + ), + emb_drop=Dropout(config.embedding_dropout), + ln_f=LayerNorm.build(config), + ) + ) + + blocks = [LLaDABlock.build(i, config, self.__cache) for i in range(config.n_layers)] + if self.config.block_group_size > 1: + block_groups = [ + LLaDABlockGroup(config, i, blocks[i : i + config.block_group_size]) + for i in range(0, config.n_layers, config.block_group_size) + ] + self.transformer.update({"block_groups": nn.ModuleList(block_groups)}) + else: + self.transformer.update({"blocks": nn.ModuleList(blocks)}) + + if not (self.config.alibi or self.config.rope): + self.transformer.update( + {"wpe": nn.Embedding(config.max_sequence_length, config.d_model, device=config.init_device)} + ) + if not config.weight_tying: + self.transformer.update( + { + "ff_out": nn.Linear( + config.d_model, + config.embedding_size or config.vocab_size, + bias=config.include_bias, + device=config.init_device, + ) + } + ) + # When `init_device="meta"` FSDP will call `reset_parameters()` to initialize weights. + if init_params and self.config.init_device != "meta": + self.reset_parameters() + self.__num_fwd_flops: Optional[int] = None + + # Warm up cache. + if self.config.alibi: + get_causal_attention_bias(self.__cache, config.max_sequence_length, _non_meta_init_device(config)) + self.get_alibi_attention_bias(config.max_sequence_length, _non_meta_init_device(config)) + + def set_activation_checkpointing(self, strategy: Optional[ActivationCheckpointingStrategy]): + self.activation_checkpointing_strategy = strategy + if self.config.block_group_size != 1: + for block_group in self.transformer.block_groups: + block_group.set_activation_checkpointing(strategy) + else: + for block in self.transformer.blocks: + block.set_activation_checkpointing(strategy) + + @property + def device(self) -> torch.device: + device: torch.device = self.transformer.wte.weight.device # type: ignore + if device.type == "meta": + return _non_meta_init_device(self.config) + else: + return device + + def reset_parameters(self): + log.info("Initializing model parameters...") + # Top-level embeddings / linear layers. + init_weights( + self.config, + self.transformer.wte, # type: ignore + std_factor=(0.5 * math.sqrt(self.config.d_model)) if self.config.scale_logits else 1.0, + type_of_module=ModuleType.emb, + ) + if hasattr(self.transformer, "wpe"): + init_weights(self.config, self.transformer.wpe, type_of_module=ModuleType.emb) # type: ignore + + # Top-level layer norm. + self.transformer.ln_f.reset_parameters() # type: ignore + + # Output weights. + if hasattr(self.transformer, "ff_out"): + init_weights(self.config, self.transformer.ff_out, type_of_module=ModuleType.final_out) # type: ignore + + # Let the blocks handle themselves. + if self.config.block_group_size == 1: + for block in self.transformer.blocks: + block.reset_parameters() + else: + for block_group in self.transformer.block_groups: + block_group.reset_parameters() + + def get_alibi_attention_bias(self, seq_len: int, device: torch.device) -> torch.Tensor: + if (alibi_bias := self.__cache.get("alibi_attention_bias")) is not None and alibi_bias.shape[ + -1 + ] >= seq_len: + if alibi_bias.device != device: + alibi_bias = alibi_bias.to(device) + self.__cache["alibi_attention_bias"] = alibi_bias + return alibi_bias + with torch.autocast(device.type, enabled=False): + alibi_bias = alibi_attention_bias(seq_len, self.config, device) + self.__cache["alibi_attention_bias"] = alibi_bias + return alibi_bias + + def forward( + self, + input_ids: torch.LongTensor, + input_embeddings: Optional[torch.FloatTensor] = None, + attention_mask: Optional[torch.Tensor] = None, + attention_bias: Optional[torch.Tensor] = None, + past_key_values: Optional[Sequence[Tuple[torch.Tensor, torch.Tensor]]] = None, + use_cache: bool = False, + update_kvcache: bool = False, + last_logits_only: bool = False, + output_hidden_states: Optional[bool] = None, + ) -> LLaDAOutput: + """ + :param input_ids: A tensor of shape `(batch_size, seq_len)`. + :param input_embeddings: A tensor of shape `(batch_size, seq_len, d_model)` with input + embeddings. When provided, it is treated as the output of the input embedding layer. + :param attention_mask: A tensor of shape `(batch_size, seq_len)` that indicates + which input IDs are masked. A `1` value in the mask means that + the corresponding input ID should *not* be ignored. A `0` means + that the corresponding input ID is masked. + + This has the same meaning as the `attention_mask` in HuggingFace's `transformers` + library. + :param attention_bias: A tensor of shape `(batch_size, 1, seq_len, seq_len)`, + `(1, 1, seq_len, seq_len)`, or `(seq_len, seq_len)`. This is used + to introduce causal or other biases. + + If the tensor is a bool or byte tensor, a `True` or `1` at `attention_bias[:, :, i, j]` + indicates that the i-th element in the sequence is allowed to attend to the j-th + element in the sequence. + + If the tensor is a float tensor, it will just be added to the attention + scores before the softmax. + + The default is causal, which corresponds to a lower-diagonal byte matrix of ones. + :param past_key_values: Pre-computed keys and values for each attention block. + Can be used to speed up sequential decoding. The `input_ids` which have + their past given to this model should not be passed as `input_ids` as they have already been computed. + :param use_cache: If `True`, return key and value tensors for each block. + :param last_logits_only: If `True`, only compute the logits for the last token of each sequence. + This can speed up decoding when you only care about the next token. + """ + # Add Basic MDM Model config check + # print(input_ids.dtype) + assert not self.config.alibi, "Alibi length extrapolation is not supported for MDM." + assert self.config.rope, "Rope must be used in Llama-Encoder for MDM." + # assert (past_key_values is None and not use_cache), "The kvcache is not suppotred for MDM." + + output_hidden_states = output_hidden_states if output_hidden_states is not None else False + + if past_key_values: + assert len(past_key_values) == self.config.n_layers + + batch_size, seq_len = input_ids.size() if input_embeddings is None else input_embeddings.size()[:2] + if past_key_values is None: + past_length = 0 + else: + past_length = past_key_values[0][0].size(-2) + + # Get embeddings of input. + # shape: (batch_size, seq_len, d_model) + # print(input_ids.dtype,"wte") + x = self.transformer.wte(input_ids) if input_embeddings is None else input_embeddings # type: ignore + + if self.config.input_emb_norm: + x = x * (self.config.d_model**0.5) + + if not (self.config.alibi or self.config.rope): + # Get positional embeddings. + # shape: (1, seq_len) + pos = torch.arange(past_length, past_length + seq_len, dtype=torch.long, device=x.device).unsqueeze(0) + # shape: (1, seq_len, d_model) + pos_emb = self.transformer.wpe(pos) # type: ignore + x = pos_emb + x + + # Add input + positional embeddings and apply dropout. + # shape: (batch_size, seq_len, d_model) + x = self.transformer.emb_drop(x) # type: ignore + + # Transform the attention mask into what the blocks expect. + if attention_mask is not None and 0.0 in attention_mask: + # shape: (batch_size, 1, 1, seq_len) + attention_mask = attention_mask.to(dtype=torch.float).view(batch_size, -1)[:, None, None, :] + attention_mask = (1.0 - attention_mask) * torch.finfo(attention_mask.dtype).min + else: + attention_mask = None + + # Merge attention mask with attention bias. + if ( + attention_bias is not None + or attention_mask is not None + or self.config.alibi + # NOTE (epwalsh): we need to initialize the attn bias in order for attn to work properly + # with key+value cache. Otherwise `F.scaled_dot_product_attention()` doesn't seem to compute + # scores correctly. + or past_key_values is not None + ): + if attention_bias is None and self.config.alibi: + attention_bias = get_causal_attention_bias( + self.__cache, past_length + seq_len, x.device + ) + self.get_alibi_attention_bias(past_length + seq_len, x.device) + elif attention_bias is None: + attention_bias = get_causal_attention_bias(self.__cache, past_length + seq_len, x.device) + elif attention_bias.dtype in (torch.int8, torch.bool): + attention_bias = attention_bias.to(dtype=torch.float) + attention_bias.masked_fill_(attention_bias == 0.0, torch.finfo(attention_bias.dtype).min) + + # Transform to the right shape and data type. + mask_len = seq_len + if attention_mask is not None: + mask_len = attention_mask.shape[-1] + elif past_key_values is not None: + mask_len = past_key_values[0][0].shape[-2] + seq_len + attention_bias = attention_bias[:, :, :mask_len, :mask_len].to(dtype=torch.float) + + # Add in the masking bias. + if attention_mask is not None: + attention_bias = attention_bias + attention_mask + # Might get -infs after adding attention mask, since dtype.min + dtype.min = -inf. + # `F.scaled_dot_product_attention()` doesn't handle -inf like you'd expect, instead + # it can produce NaNs. + ensure_finite_(attention_bias, check_neg_inf=True, check_pos_inf=False) + + attn_key_values: Optional[List[Tuple[torch.Tensor, torch.Tensor]]] = [] if use_cache else None + + # decoder layers + all_hidden_states = [] + + # Apply blocks one-by-one. + if self.config.block_group_size == 1: + for block_idx, block in enumerate(self.transformer.blocks): + if output_hidden_states: + # add hidden states + all_hidden_states.append(x) + + layer_past = None if past_key_values is None else past_key_values[block_idx] + if ( + (self.activation_checkpointing_strategy == ActivationCheckpointingStrategy.whole_layer) + or ( + self.activation_checkpointing_strategy == ActivationCheckpointingStrategy.one_in_two + and block_idx % 2 == 0 + ) + or ( + self.activation_checkpointing_strategy == ActivationCheckpointingStrategy.one_in_three + and block_idx % 3 == 0 + ) + or ( + self.activation_checkpointing_strategy == ActivationCheckpointingStrategy.one_in_four + and block_idx % 4 == 0 + ) + ): + # shape: (batch_size, seq_len, d_model) + x, cache = self._activation_checkpoint_fn( + block, x, attention_bias=attention_bias, layer_past=layer_past, use_cache=use_cache + ) + else: + # shape: (batch_size, seq_len, d_model) + x, cache = block(x, attention_bias=attention_bias, layer_past=layer_past, use_cache=use_cache) + if attn_key_values is not None: + if update_kvcache == True: + attn_key_values.append(cache) + else: + for group_idx, block_group in enumerate(self.transformer.block_groups): + if output_hidden_states: + # add hidden states + all_hidden_states.append(x) + + layers_past = ( + None + if past_key_values is None + else past_key_values[ + group_idx * self.config.block_group_size : (group_idx + 1) * self.config.block_group_size + ] + ) + x, cache = block_group( + x, attention_bias=attention_bias, layers_past=layers_past, use_cache=use_cache + ) + if attn_key_values is not None: + assert cache is not None + attn_key_values.extend(cache) + + if last_logits_only: + # shape: (batch_size, 1, d_model) + x = x[:, -1, :].unsqueeze(1) + + # Apply final layer norm. + # shape: (batch_size, seq_len or 1, d_model) + x = self.transformer.ln_f(x) # type: ignore + if output_hidden_states: + # add final hidden state post-final-layernorm, following HuggingFace's convention + all_hidden_states.append(x) + + # Get logits. + # shape: (batch_size, seq_len or 1, vocab_size) + if self.config.weight_tying: + logits = F.linear(x, self.transformer.wte.weight, None) # type: ignore + else: + logits = self.transformer.ff_out(x) # type: ignore + if self.config.scale_logits: + logits.mul_(1 / math.sqrt(self.config.d_model)) + if use_cache == True and update_kvcache == False: + attn_key_values=past_key_values + return LLaDAOutput(logits=logits, attn_key_values=attn_key_values, hidden_states=tuple(all_hidden_states) if output_hidden_states else None) # type: ignore[arg-type] + + +def create_model_config_from_pretrained_config(config: LLaDAConfig): + """ + Utility function + """ + + kwargs = {} + for field in fields(ModelConfig): + kwargs[field.name] = getattr(config, field.name) + + model_config = ModelConfig(**kwargs) + return model_config + + +class LLaDAModelLM(PreTrainedModel): + """ + Extremely barebones HF model wrapper. + """ + + config_class = LLaDAConfig + base_model_prefix = "model" + _no_split_modules = ["LLaDABlock", "LLaDASequentialBlock", "LLaDALlamaBlock"] + + def __init__(self, config: LLaDAConfig, model: Optional[LLaDAModel] = None, init_params: bool = False): + super().__init__(config) + + if not model: + model_config = create_model_config_from_pretrained_config(config) + # Initialize model (always on CPU to start with so we don't run out of GPU memory). + model_config.init_device = "cpu" + self.model = LLaDAModel(model_config, init_params=init_params) + else: + self.model = model + + def forward( + self, + input_ids: torch.LongTensor = None, + inputs_embeds: Optional[torch.FloatTensor] = None, + attention_mask: Optional[torch.Tensor] = None, + attention_bias: Optional[torch.Tensor] = None, + past_key_values: Optional[List[torch.FloatTensor]] = None, + labels: Optional[torch.LongTensor] = None, + use_cache: Optional[bool] = None, + output_attentions: Optional[bool] = None, + output_hidden_states: Optional[bool] = None, + return_dict: Optional[bool] = None, + cache_position: Optional[Cache] = None, # This is a hack mitigation of an issue in transformers `4.39.x` + ) -> Union[Tuple, CausalLMOutputWithPast]: + if use_cache is None: + use_cache = self.config.use_cache + + if output_attentions: + raise ValueError("output_attentions is not yet supported in LLaDA") + + return_dict = return_dict if return_dict is not None else self.config.use_return_dict + + # decoder outputs consists of (dec_features, layer_state, dec_hidden, dec_attn) + outputs = self.model.forward( + input_ids=input_ids, + input_embeddings=inputs_embeds, + attention_mask=attention_mask, + attention_bias=attention_bias, + past_key_values=past_key_values, + use_cache=use_cache, + output_hidden_states=output_hidden_states, + ) + + logits = outputs.logits + hidden_states = outputs.hidden_states + + loss = None + if labels is not None: + import warnings + warnings.warn("Note that for LLaDA, you cannot calculate the loss here.", UserWarning) + if not return_dict: + output = (logits,) + outputs[1:] + return (loss,) + output if loss is not None else output + + return CausalLMOutputWithPast( + logits=logits, + past_key_values=outputs.attn_key_values, + hidden_states=hidden_states, + ) + + def can_generate(self) -> bool: + return True + + def prepare_inputs_for_generation( + self, input_ids: torch.LongTensor, past_key_values: Optional[List[Tuple]] = None, **kwargs + ): + if past_key_values: + # This is because we want the model to only process the last generated token. + input_ids = input_ids[:, -1:] + model_inputs = {"input_ids": input_ids, "past_key_values": past_key_values} + + model_inputs.update(kwargs) + model_inputs["use_cache"] = kwargs.pop("use_cache", self.config.use_cache) + return model_inputs + + # TODO: these are required to make the implementation complete. + # def resize_position_embeddings(self, new_num_position_embeddings: int): + # pass + # + # def get_position_embeddings(self) -> Union[nn.Embedding, Tuple[nn.Embedding]]: + # pass + # + # def _reorder_cache(self, past_key_values, beam_idx): + # pass + + def get_input_embeddings(self) -> torch.nn.Module: + return self.model.transformer.wte + + def set_input_embeddings(self, value: torch.nn.Module): + self.model.transformer.wte = value + + def get_output_embeddings(self): + if self.config.weight_tying: + return self.model.transformer.wte + else: + return self.model.transformer.ff_out + + def set_output_embeddings(self, value: torch.nn.Module): + if self.config.weight_tying: + self.model.transformer.wte = value + else: + self.model.transformer.ff_out = value + + def tie_weights(self): + if self.config.weight_tying: + self.model.transformer.ff_out = self.model.transformer.wte + +# Register the model so that it is available for transformer pipelines, auto-loading, etc. +AutoModel.register(LLaDAConfig, LLaDAModelLM) \ No newline at end of file diff --git a/Discrete-Diffusion-Forcing/D2F-train/train.py b/Discrete-Diffusion-Forcing/D2F-train/train.py new file mode 100644 index 0000000000000000000000000000000000000000..6c144312486930a4ee88b4177fa0d44304f428d9 --- /dev/null +++ b/Discrete-Diffusion-Forcing/D2F-train/train.py @@ -0,0 +1,215 @@ +# from peft import PeftModel, PeftConfig, get_peft_model +from datasets import load_dataset +from torch.utils.data import DataLoader +from peft import PeftModel, PeftConfig, get_peft_model +from utils.util import flatten_dict,shift_logits +from utils.data import get_bs17k_dataloader,get_llada_bs17k_dataloader,get_dataloader_by_config +from utils.model import get_model,get_llada,get_model_by_config +from utils.loss import compute_loss,compute_llada_loss,compute_normal_loss,compute_loss_by_config +from utils.generation import sample_tokens +# import dataloader + +import os +import torch +import argparse +import torch.distributed as dist +from omegaconf import OmegaConf +from tqdm import tqdm +from accelerate import Accelerator +from accelerate.utils import ProjectConfiguration + +os.environ['TOKENIZERS_PARALLELISM'] = 'false' + +def get_accelerator(config, global_config): + # Select experiment path based on config + if hasattr(global_config, 'paths') and hasattr(global_config.paths, 'experiment'): + root_path = global_config.paths.experiment + else: + root_path = config.root if hasattr(config, 'root') else '/tmp/experiment' + + output_dir = os.path.join(root_path, config.exp_name, config.output_dir) + os.makedirs(output_dir, exist_ok=True) + logging_dir = os.path.join(output_dir, config.logging_dir) + project_config = ProjectConfiguration(project_dir=config.output_dir, logging_dir=logging_dir) + accelerator = Accelerator( + log_with=None if config.report_to == 'no' else config.report_to, + mixed_precision=config.mixed_precision, + project_config=project_config, + gradient_accumulation_steps=config.gradient_accumulation_steps, + ) + + return accelerator, output_dir + +def main(args): + config = OmegaConf.load(args.config) + accelerator, output_dir = get_accelerator(config.train, config) + + # Use unified model and data loading functions + denoiser, tokenizer = get_model_by_config(config) + dataloader = get_dataloader_by_config(tokenizer, config.data, config) + + if config.train.decoder_resume_path is not None: + ckpt = torch.load(config.train.decoder_resume_path, map_location='cpu', weights_only=True) + if config.train.skipped_keys: + ckpt = {k: v for k, v in ckpt.items() if k not in config.train.skipped_keys} + m, u = denoiser.load_state_dict(ckpt, strict=False) + if accelerator.is_main_process: + print(f'model ckpt loaded from {config.train.decoder_resume_path}') + + # ckpt = torch.load(config.train.head_resume_path, map_location='cpu', weights_only=True) + # if config.train.skipped_keys: + # ckpt = {k: v for k, v in ckpt.items() if k not in config.train.skipped_keys} + # m, u = denoiser.lm_head.load_state_dict(ckpt, strict=False) + # if accelerator.is_main_process: + # print(f'model ckpt loaded from {config.train.head_resume_path}') + + global_step = config.train.global_step if config.train.global_step is not None else 0 + params_to_learn = list(param for param in denoiser.parameters() if param.requires_grad) + optimizer = torch.optim.AdamW( + params_to_learn, + lr = config.train.lr, + betas = (0.9, 0.95), + weight_decay = 5e-2, + eps = 1e-8, + ) + + denoiser, dataloader, optimizer = accelerator.prepare(denoiser, dataloader, optimizer) + + config.device_count = accelerator.num_processes + if accelerator.is_main_process: + accelerator.init_trackers(config.train.wandb_proj, config=flatten_dict(config)) + + training_done = False + epoch = 0 + progress_bar = tqdm( + total = config.train.num_iters, + initial = global_step, + desc = 'Steps', + disable = not accelerator.is_local_main_process, + ) + + if accelerator.is_main_process: + print(f'Learnable parameters: {sum(p.numel() for p in params_to_learn if p.requires_grad) / 1e9} B') + + while not training_done: + if accelerator.is_main_process: + print(f'Epoch: {epoch}') + for batch in dataloader: + with accelerator.accumulate([denoiser]): + denoiser.train() + input_ids = batch['data'] + # print("input_ids",input_ids.dtype) + question_length = batch['question_length'] + + # Use unified loss function selection + losses = compute_loss_by_config( + input_ids, + denoiser, + question_length, + block_size = config.train.block_size, + mask_id = config.denoiser.encoder.mask_id, + enable_shift = config.train.enable_shift, + share_steps = config.train.share_steps, + self_align = config.train.self_align, + feature_align = config.train.feature_align, + self_step = config.train.self_step, + eos_id = tokenizer.eos_token_id, + config = config + ) + + if config.train.share_steps > 1: + loss_tgt = losses['loss'] + # loss_1 = losses['loss_1'] + # loss_2 = losses['loss_2'] + else: + raise NotImplementedError + torch.cuda.empty_cache() + accelerator.backward(loss_tgt) + if accelerator.sync_gradients: + accelerator.clip_grad_norm_(params_to_learn, 1.0) + + optimizer.step() + optimizer.zero_grad() + + if accelerator.sync_gradients: + global_step += 1 + progress_bar.update(1) + logs = dict() + loss_tgt = accelerator.gather(loss_tgt.detach()).mean().item() + logs['loss'] = loss_tgt + # if config.train.share_steps > 1: + # loss_1 = accelerator.gather(loss_1.detach()).mean().item() + # loss_2 = accelerator.gather(loss_2.detach()).mean().item() + # logs['loss_1'] = loss_1 + # logs['loss_2'] = loss_2 + + accelerator.log(logs, step=global_step) + progress_bar.set_postfix(**logs) + + if global_step > 0 and global_step % config.train.eval_every == 0 and accelerator.is_main_process: + denoiser.eval(); + question = 'Henry made two stops during his 60-mile bike trip. He first stopped after 20 miles. His second stop was 15 miles before the end of the trip. How many miles did he travel between his first and second stops?' + # prompt = tokenizer(question)['input_ids'] + # prompt = torch.tensor(prompt).to(accelerator.device).unsqueeze(0) + messages = [ + {"role": "user", "content": question} + ] + prompt = tokenizer.apply_chat_template( + messages, return_tensors="pt", return_dict=True, add_generation_prompt=True + ).input_ids + prompt = prompt.to(accelerator.device) + + mask_id = 151666 + gen_len = 512 - prompt.shape[1] + temperature = 0.2 + top_p = 0.95 + + x_t = torch.cat([prompt, torch.tensor([[mask_id]*gen_len]).to(accelerator.device)], dim=1) + with torch.inference_mode(): + for i in range(gen_len): + mask_index = (x_t == mask_id) + if i % 2 == 0: + z_t = denoiser.module.encoder(x_t, output_hidden_states=True).hidden_states[-1] + hidden_state = denoiser.module.decoder(x_t, z_t) + logits = denoiser.module.encoder.lm_head(hidden_state) + else: + hidden_state = denoiser.module.decoder(x_t, z_t) + logits = denoiser.module.lm_head(hidden_state) + + if config.train.enable_shift: + logits = shift_logits(logits) + + mask_logits = logits[mask_index] + confidence, x0 = sample_tokens(mask_logits, temperature, top_p=top_p, top_k=None, neg_entropy=True) + + number_transfer_tokens = 1 + _, transfer_index = torch.topk(confidence, number_transfer_tokens) + x0_ = torch.zeros_like(x0, device=accelerator.device, dtype=torch.long) + mask_id + x0_[transfer_index] = x0[transfer_index].clone() + x_t[mask_index] = x0_ + + answer = tokenizer.batch_decode(x_t[:, prompt.shape[1]:], skip_special_tokens=True)[0] + print(answer) + + accelerator.wait_for_everyone() + + if global_step > 0 and global_step % config.train.save_every == 0 and accelerator.is_main_process: + denoiser.eval() + decoder_state_dict = accelerator.unwrap_model(denoiser).save_pretrained(os.path.join(output_dir, f"Decoder-{config.train.exp_name}-{global_step // 1000}k")) + # lmhead_state_dict = accelerator.unwrap_model(denoiser).lm_head.state_dict() + # torch.save(lmhead_state_dict, os.path.join(output_dir, f"LMhead-{config.train.exp_name}-{global_step // 1000}k")) + accelerator.wait_for_everyone() + if global_step >= config.train.num_iters: + training_done = True + break + epoch += 1 + accelerator.end_training() + if dist.is_initialized(): + dist.destroy_process_group() + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--config', type=str, default='config/dream.yaml') + args = parser.parse_args() + main(args) \ No newline at end of file diff --git a/Discrete-Diffusion-Forcing/D2F-train/train.sh b/Discrete-Diffusion-Forcing/D2F-train/train.sh new file mode 100644 index 0000000000000000000000000000000000000000..8d65365f0815f27d8d96cf8e04da53d819055ac1 --- /dev/null +++ b/Discrete-Diffusion-Forcing/D2F-train/train.sh @@ -0,0 +1,4 @@ +# export CUDA_LAUNCH_BLOCKING=1 +CUDA_VISIBLE_DEVICES=4 accelerate launch --config_file config/acc_config --num_processes 1 --main_process_port 29577 train.py --config config/llada.yaml + +CUDA_VISIBLE_DEVICES=4 accelerate launch --config_file config/acc_config --num_processes 1 --main_process_port 29577 train.py --config config/dream_eagle.yaml diff --git a/Discrete-Diffusion-Forcing/D2F-train/utils/__init__.py b/Discrete-Diffusion-Forcing/D2F-train/utils/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/Discrete-Diffusion-Forcing/D2F-train/utils/data.py b/Discrete-Diffusion-Forcing/D2F-train/utils/data.py new file mode 100644 index 0000000000000000000000000000000000000000..32b0ecc51e849589836045693a06c121adbaca4d --- /dev/null +++ b/Discrete-Diffusion-Forcing/D2F-train/utils/data.py @@ -0,0 +1,313 @@ +from datasets import load_dataset +from torch.utils.data import DataLoader,Dataset +from peft import PeftModel, PeftConfig, get_peft_model +# from modelscope.msdatasets import MsDataset +import torch +import json +import re +def extract_answer(text): + pattern = r"<\|begin_of_solution\|>(.*?)<\|end_of_solution\|>" + match = re.search(pattern, text, re.DOTALL) + + if match: + solution_content = match.group(1).strip() + # print("Extracted content:\n") + # print(solution_content) + return solution_content + else: + # print("No matching content found.") + return None +def collate_fn(batch, tokenizer, max_length): + """ + batch: list of raw text samples (str) + tokenizer: huggingface tokenizer + max_length: maximum length to pad to (int) + """ + encoded_batch = [] + for text in batch: + # Encode text, return dictionary, note no automatic padding + enc = tokenizer(text["text"], add_special_tokens=False, return_tensors="pt") + input_ids = enc["input_ids"].squeeze(0) # (seq_len,) + + # Add eos_token_id + eos_id = tokenizer.eos_token_id + if eos_id is None: + raise ValueError("tokenizer does not have eos_token_id") + + input_ids = torch.cat([input_ids, torch.tensor([eos_id], device=input_ids.device)]) + + # Padding to max_length + pad_id = tokenizer.pad_token_id + if pad_id is None: + raise ValueError("tokenizer does not have pad_token_id") + + seq_len = input_ids.size(0) + if seq_len > max_length: + # Truncate if too long + input_ids = input_ids[:max_length] + else: + # Pad right side if not long enough + pad_len = max_length - seq_len + padding = torch.full((pad_len,), pad_id, device=input_ids.device, dtype=input_ids.dtype) + input_ids = torch.cat([input_ids, padding]) + + encoded_batch.append(input_ids) + + return torch.stack(encoded_batch) + +def prepare_dataloader(data, tokenizer, batch_size, max_length): + dataset = CustomDataset(data) + dataloader = DataLoader( + dataset, + batch_size = batch_size, + collate_fn = lambda x: collate_fn(x, tokenizer, max_length=max_length), + num_workers = 0, + shuffle = True, + pin_memory = True, + ) + + return dataloader + +def read_math(): + math_data = [] + dataset = load_dataset("microsoft/orca-math-word-problems-200k", split="train") + for item in dataset: + math_data.append({"question": item['question'], "answer": item['answer']}) + return math_data + +def read_python(): + python_data = [] + dataset = load_dataset("microsoft/orca-math-word-problems-200k", split="train") + for item in dataset: + python_data.append({"question": item['question'], "answer": item['answer']}) + return python_data + +def read_numinamath(): + math_data = read_math() + python_data = read_python() + return math_data + python_data + +def read_bs(config=None): + data=[] + # Get path from config, use default path if no config + if config and hasattr(config, 'paths') and hasattr(config.paths, 'data') and hasattr(config.paths.data, 'bs'): + dataset_path = config.paths.data.bs + else: + dataset_path = "/data1/xck/dllm_block_wx/data/Lansechen/bs17k_collection_filtered_hard_maxlength600" + + dataset=load_dataset(dataset_path, split="train") + for item in dataset: + data.append({"question": item['question'], "answer": item['qwen7b_answer']}) + return data + +def read_bs_easy(config=None): + data=[] + # Get path from config, use default path if no config + if config and hasattr(config, 'paths') and hasattr(config.paths, 'data') and hasattr(config.paths.data, 'bs_easy'): + dataset_path = config.paths.data.bs_easy + else: + dataset_path = "/data1/xck/dllm_block_wx/data/Lansechen/bs17k_collection_filtered_easy_maxlength600" + + dataset=load_dataset(dataset_path, split="train") + for item in dataset: + data.append({"question": item['question'], "answer": item['qwen7b_answer']}) + return data + +def read_bs_17k(): + data=[] + dataset=load_dataset("/data/wx/dataset/bespokelabs/Bespoke-Stratos-17k",split="train") + for item in dataset: + item=item["conversations"] + data.append({"question": item[0]['value'], "answer": extract_answer(item[1]['value'])}) + return data +class CustomDataset(Dataset): + def __init__(self, data): + self.data = data + + def __len__(self): + return len(self.data) + + def __getitem__(self, idx): + return self.data[idx] +def read_llada(file_path="/home/wx/dllm_block/data/merged_bs17k_easy_hard_llada_collected.jsonl"): + data = [] + with open(file_path, 'r', encoding='utf-8') as file: + for line in file: + try: + json_obj = json.loads(line) + data.append(json_obj) + except json.JSONDecodeError: + print(f'JSONDecodeError: {line}') + return data +def get_bs17k_dataloader(tokenizer, config, max_length=1024): + train_dataset = [] + # Pass global config to data reading functions + global_config = getattr(config, '_parent', config) # Try to get parent config + data_dict=read_bs(global_config)+read_bs_easy(global_config) + for data in data_dict: + question = data['question'] + answer = data['answer'] + + # messages = [ + # {"role": "user", "content": "Janet's ducks lay 16 eggs per day. She eats three for breakfast every morning and bakes muffins for her friends every day with four. She sells the remainder at the farmers' market daily for $2 per fresh duck egg. How much in dollars does she make every day at the farmers' market?"}, + # ] + messages = [ + {"role": "user", "content": question} + ] + question = tokenizer.apply_chat_template( + messages, return_tensors="pt", return_dict=True, add_generation_prompt=True + ).input_ids[0] + + # question = tokenizer(question, return_tensors='pt')['input_ids'][0] + answer = tokenizer(answer, return_tensors='pt')['input_ids'][0] + answer = torch.cat((answer, torch.tensor([tokenizer.eos_token_id])), dim=-1) + + question_length = question.shape[-1] + answer_length = answer.shape[-1] + combined_length = question_length + answer_length + if question_length > max_length-100: + continue + if combined_length > max_length: + padded_data = torch.cat((question, answer), dim=-1) + padded_data = padded_data[:max_length] # Truncate to max_length + else: + padding_length = max_length - combined_length + padding = torch.full((padding_length,), tokenizer.eos_token_id, dtype=question.dtype) + padded_data = torch.cat((question, answer, padding), dim=-1) + + train_dataset.append( + dict( + data = padded_data, + question_length = question_length, + length = combined_length, + ) + ) + + dataset = CustomDataset(train_dataset) + dataloader = DataLoader( + dataset, + batch_size = config.batch_size, + num_workers = 0, + shuffle = True, + pin_memory = True, + ) + + return dataloader + +# def get_gsm8k_dataloader(tokenizer, config, max_length=1024): +# train_dataset = [] +# data_dict = read_numinamath() +# for data in data_dict: +# question = data['question'] +# answer = data['answer'] + +# question = tokenizer(question, return_tensors='pt')['input_ids'][0] +# answer = tokenizer(answer, return_tensors='pt')['input_ids'][0] +# answer = torch.cat((answer, torch.tensor([tokenizer.eos_token_id])), dim=-1) + +# question_length = question.shape[-1] +# answer_length = answer.shape[-1] +# combined_length = question_length + answer_length + +# if combined_length > max_length: +# continue + +# padding_length = max_length - combined_length +# padding = torch.full((padding_length,), tokenizer.eos_token_id, dtype=question.dtype) +# padded_data = torch.cat((question, answer, padding), dim=-1) + +# train_dataset.append( +# dict( +# data = padded_data, +# question_length = question_length, +# length = combined_length, +# ) +# ) + +# dataset = CustomDataset(train_dataset) +# dataloader = DataLoader( +# dataset, +# batch_size = config.batch_size, +# collate_fn = lambda x: collate_fn_pad(x, tokenizer, max_length=max_length), +# num_workers = 0, +# shuffle = True, +# pin_memory = True, +# ) + +# return dataloader +def get_llada_bs17k_dataloader(tokenizer, config, max_length=1024): + train_dataset = [] + # Pass global config to data reading functions + global_config = getattr(config, '_parent', config) # Try to get parent config + data_dict = read_bs(global_config) + python_dict=read_bs_easy(global_config) + data_dict=data_dict+python_dict + print("Data length:",len(data_dict)) + # data_dict = read_llada() + for data in data_dict: + question = data['question'] + answer = data['answer'] + + # messages = [ + # {"role": "user", "content": "Janet's ducks lay 16 eggs per day. She eats three for breakfast every morning and bakes muffins for her friends every day with four. She sells the remainder at the farmers' market daily for $2 per fresh duck egg. How much in dollars does she make every day at the farmers' market?"}, + # ] + messages = [ + {"role": "user", "content": question} + ] + question = tokenizer.apply_chat_template( + messages, return_tensors="pt", return_dict=True, add_generation_prompt=True + ).input_ids[0] + + # question = tokenizer(question, return_tensors='pt')['input_ids'][0] + answer = tokenizer(answer, return_tensors='pt')['input_ids'][0] + answer = torch.cat((answer, torch.tensor([126348])), dim=-1) + + question_length = question.shape[-1] + answer_length = answer.shape[-1] + combined_length = question_length + answer_length + + if combined_length > max_length: + continue + + padding_length = max_length - combined_length + padding = torch.full((padding_length,), tokenizer.eos_token_id, dtype=question.dtype) + padded_data = torch.cat((question, answer, padding), dim=-1) + + train_dataset.append( + dict( + data = padded_data, + question_length = question_length, + length = combined_length, + ) + ) + + dataset = CustomDataset(train_dataset) + dataloader = DataLoader( + dataset, + batch_size = config.batch_size, + num_workers = 0, + shuffle = True, + pin_memory = True, + ) + + return dataloader +if __name__ == "__main__": + text="<|begin_of_thought|>\n\nOkay, let me try to figure out this problem. So, we have this operation defined as a⊗b = a²/b. And we need to compute [(1⊗2)⊗3] - [1⊗(2⊗3)]. Then choose the correct answer from the options given. Alright, let's break it down step by step.\n\nFirst, I need to remember that the operation ⊗ is not associative, right? Because the problem is asking for the difference between two different groupings: (1⊗2)⊗3 and 1⊗(2⊗3). So, the order in which we perform the operations matters here. That's probably why there's a subtraction between them.\n\nLet me start by computing each part separately. Let's tackle the first part: (1⊗2)⊗3.\n\nStarting with the innermost operation, which is 1⊗2. According to the definition, a⊗b = a²/b. So here, a is 1 and b is 2. Plugging those in: 1² / 2 = 1/2. So, 1⊗2 equals 1/2.\n\nNow, we take that result and perform the next operation with 3. So, (1⊗2)⊗3 becomes (1/2)⊗3. Again, using the same definition: a is now 1/2 and b is 3. So, ( (1/2)² ) / 3 = (1/4) / 3 = 1/12. So, (1⊗2)⊗3 equals 1/12.\n\nAlright, that's the first part. Now let's compute the second part: 1⊗(2⊗3). Again, starting with the innermost operation, which is 2⊗3. Applying the definition: a is 2 and b is 3. So, 2² / 3 = 4/3. Therefore, 2⊗3 equals 4/3.\n\nNow, we need to compute 1⊗(4/3). Here, a is 1 and b is 4/3. Using the operation definition: 1² / (4/3) = 1 / (4/3) = 3/4. So, 1⊗(2⊗3) equals 3/4.\n\nNow, the problem asks for the difference between the two results: [(1⊗2)⊗3] - [1⊗(2⊗3)] = (1/12) - (3/4). To subtract these fractions, they need a common denominator. The denominators are 12 and 4, so 12 is the common denominator.\n\nConverting 3/4 to twelfths: 3/4 = 9/12. So, 1/12 - 9/12 = (1 - 9)/12 = -8/12. Simplifying that fraction by dividing numerator and denominator by 4: -8/12 = -2/3.\n\nHmm, looking at the answer choices, option A is -2/3. So, is that the answer? Wait, but let me double-check my calculations to make sure I didn't make a mistake somewhere.\n\nFirst, checking (1⊗2): 1² / 2 = 1/2. Correct. Then, (1/2)⊗3: (1/2)² / 3 = (1/4)/3 = 1/12. That seems right.\n\nNow, for 2⊗3: 2² / 3 = 4/3. Correct. Then, 1⊗(4/3): 1² / (4/3) = 1 / (4/3) = 3/4. Yes, that's correct.\n\nSubtracting 3/4 from 1/12: 1/12 - 3/4. Convert 3/4 to 9/12, so 1/12 - 9/12 = -8/12 = -2/3. Yes, that all checks out. So the answer should be -2/3, which is option A.\n\nWait, but let me think again. The operation is defined for all nonzero numbers, so we don't have any issues with division by zero here. 2⊗3 is 4/3, which is fine, and then 1⊗(4/3) is 3/4. Correct.\n\nAlternatively, maybe there's a different way to approach the problem? Let me try expanding both expressions using variables to see if there's a pattern.\n\nLet's denote the first expression: (a⊗b)⊗c. Using the definition:\n\nFirst, compute a⊗b = a²/b.\n\nThen, take that result and ⊗ with c: (a²/b)⊗c = ( (a²/b)² ) / c = a⁴ / (b² c).\n\nNow, the second expression: a⊗(b⊗c). First compute b⊗c = b²/c.\n\nThen, a⊗(b²/c) = a² / (b²/c) = a² * (c / b²) = (a² c) / b².\n\nTherefore, the difference between the two expressions is:\n\n(a⁴ / (b² c)) - (a² c / b²) = (a⁴ - a² c²) / (b² c) = a² (a² - c²) / (b² c).\n\nHmm, factoring that, it's a² (a - c)(a + c) / (b² c).\n\nBut in our specific problem, a = 1, b = 2, c = 3. Plugging those values in:\n\n1² (1 - 3)(1 + 3) / (2² * 3) = 1 * (-2)(4) / (4 * 3) = (-8) / 12 = -2/3. Same result. So that confirms the answer is indeed -2/3.\n\nTherefore, I think my initial calculation was correct, and the answer is option A.\n\n**Final Answer**\n\\boxed{A}\n\n<|end_of_thought|>\n\n<|begin_of_solution|>\n\nTo determine the value of \\([(1 \\otimes 2) \\otimes 3] - [1 \\otimes (2 \\otimes 3)]\\) where the operation \\(\\otimes\\) is defined by \\(a \\otimes b = \\frac{a^2}{b}\\), we proceed as follows:\n\nFirst, compute \\(1 \\otimes 2\\):\n\\[\n1 \\otimes 2 = \\frac{1^2}{2} = \\frac{1}{2}\n\\]\nNext, use this result to compute \\((1 \\otimes 2) \\otimes 3\\):\n\\[\n\\left(\\frac{1}{2}\\right) \\otimes 3 = \\frac{\\left(\\frac{1}{2}\\right)^2}{3} = \\frac{\\frac{1}{4}}{3} = \\frac{1}{12}\n\\]\n\nNow, compute \\(2 \\otimes 3\\):\n\\[\n2 \\otimes 3 = \\frac{2^2}{3} = \\frac{4}{3}\n\\]\nThen, use this result to compute \\(1 \\otimes (2 \\otimes 3)\\):\n\\[\n1 \\otimes \\left(\\frac{4}{3}\\right) = \\frac{1^2}{\\frac{4}{3}} = \\frac{1}{\\frac{4}{3}} = \\frac{3}{4}\n\\]\n\nFinally, find the difference between the two results:\n\\[\n\\frac{1}{12} - \\frac{3}{4} = \\frac{1}{12} - \\frac{9}{12} = \\frac{1 - 9}{12} = \\frac{-8}{12} = -\\frac{2}{3}\n\\]\n\nThus, the answer is \\(\\boxed{A}\\).\n\n<|end_of_solution|>" + print(extract_answer(text)) + +def get_dataloader_by_config(tokenizer, config, global_config=None, max_length=1024): + """Select different data loaders based on config file""" + if global_config is None: + global_config = config + + training_mode = global_config.get('training_mode', 'dream') + + # Add reference to global config for data loading functions to access + config._parent = global_config + + if training_mode == 'llada': + return get_llada_bs17k_dataloader(tokenizer, config, max_length) + elif training_mode == 'dream': + return get_bs17k_dataloader(tokenizer, config, max_length) + else: + raise ValueError(f"Unsupported training mode: {training_mode}") \ No newline at end of file diff --git a/Discrete-Diffusion-Forcing/D2F-train/utils/generation.py b/Discrete-Diffusion-Forcing/D2F-train/utils/generation.py new file mode 100644 index 0000000000000000000000000000000000000000..61c4d9030c2f7c1d27cea67e831293ffe7abede0 --- /dev/null +++ b/Discrete-Diffusion-Forcing/D2F-train/utils/generation.py @@ -0,0 +1,144 @@ +import torch +import torch.nn.functional as F +import torch.distributions as dists +from peft import PeftModel, PeftConfig +def build_custom_float_attention_mask(input_ids, prompt_length, block_size, device=None): + B,seq_len= input_ids.shape + # 初始化为全 -inf + attn_mask = torch.full((B,1,seq_len, seq_len), float('-inf'), dtype=torch.float32, device=device) + # 1. Prompt部分:每个token可以注意整个prompt + for i in range(B): + attn_mask[i,:,:,:prompt_length[i]] = 0.0 # 允许所有 token 看 prompt + + # 2. 块划分:从 prompt_length 开始划分 block + num_blocks = (seq_len - prompt_length[i] + block_size - 1) // block_size + + for b in range(num_blocks): + block_start = prompt_length[i] + b * block_size + # print(block_start,block_size,seq_len) + block_end = min(block_start + block_size, seq_len) + + # 块内全注意 + attn_mask[i,:,block_start:block_end, block_start:block_end] = 0.0 + + # 块之间因果注意(只能看前面块) + for prev_b in range(b): + prev_start = prompt_length[i] + prev_b * block_size + prev_end = min(prev_start + block_size, seq_len) + + # 当前块可以看前面块 + attn_mask[i,:,block_start:block_end, prev_start:prev_end] = 0.0 + + return attn_mask +def top_p_logits(logits, top_p=None): + sorted_logits, sorted_indices = torch.sort(logits, descending=True) + cumulative_probs = torch.cumsum(F.softmax(sorted_logits, dim=-1), dim=-1) + sorted_indices_to_remove = cumulative_probs > top_p + # Shift the indices to the right to keep the first token above the threshold + sorted_indices_to_remove[..., 1:] = sorted_indices_to_remove[..., :-1].clone() + sorted_indices_to_remove[..., 0] = 0 + + mask = torch.zeros_like(logits, dtype=torch.bool, device=logits.device) + mask = mask.scatter_(-1, sorted_indices, sorted_indices_to_remove) + logits = logits.masked_fill(mask, torch.finfo(logits.dtype).min) + return logits + +def top_k_logits(logits, top_k=None): + top_k = min(top_k, logits.size(-1)) # Safety check + # Remove all tokens with a probability less than the last token of the top-k + indices_to_remove = logits < torch.topk(logits, top_k)[0][..., -1, None] + logits = logits.masked_fill(indices_to_remove, torch.finfo(logits.dtype).min) + return logits + +def sample_tokens(logits, temperature=0.0, top_p=None, top_k=None, margin_confidence=False, neg_entropy=False): + if temperature > 0: + logits = logits / temperature + if top_p is not None and top_p < 1: + logits = top_p_logits(logits, top_p) + if top_k is not None: + logits = top_k_logits(logits, top_k) + probs = torch.softmax(logits, dim=-1) + + if temperature > 0: + try: + x0 = dists.Categorical(probs=probs).sample() + confidence = torch.gather(probs, -1, x0.unsqueeze(-1)).squeeze(-1) + except: + confidence, x0 = probs.max(dim=-1) + else: + confidence, x0 = probs.max(dim=-1) + + if margin_confidence: + sorted_probs, _ = torch.sort(probs, dim=-1, descending=True) + # Extract top1 and top2 probabilities + top1_probs = sorted_probs[:, 0] + top2_probs = sorted_probs[:, 1] + # Calculate confidence as top1 - top2 + confidence = top1_probs - top2_probs + + if neg_entropy: + epsilon = 1e-10 + log_probs = torch.log(probs + epsilon) + confidence = torch.sum(probs * log_probs, dim=-1) + + return confidence, x0 +# def generate(model,prompt,block_size,max_length,mask_id): +# def generate(model, prompt, block_size, max_length, mask_id, eos_token_id=None): +# device = prompt.device +# output = prompt.clone() + +# while output.shape[1] < max_length: +# # 添加一个 block 的 mask +# mask_block = torch.full((1, block_size), mask_id, dtype=torch.long, device=device) +# input_ids = torch.cat([output, mask_block], dim=1) +# attention_mask = build_custom_float_attention_mask(input_ids, torch.tensor([[prompt.shape[1]]]), block_size, device=device) +# attention_mask = attention_mask.to(torch.bfloat16) +# for i in range(block_size): +def generate_block(denoiser, block_size, mask_id,tokenizer,device): + denoiser.eval() + question = 'please give me a code about transformer model' + # prompt = tokenizer(question)['input_ids'] + # prompt = torch.tensor(prompt).to(accelerator.device).unsqueeze(0) + messages = [ + {"role": "user", "content": question} + ] + prompt = tokenizer.apply_chat_template( + messages, return_tensors="pt", return_dict=True, add_generation_prompt=True + ).input_ids + prompt = prompt.to(device) + + mask_id = 151666 + gen_len = (384 - prompt.shape[1])//block_size + print(gen_len) + temperature = 0.2 + top_p = 0.95 + with torch.inference_mode(): + for i in range(gen_len): + if i==0: + x_t = torch.cat([prompt, torch.tensor([[mask_id]*block_size]).to(device)], dim=1) + else: + x_t = torch.cat([x_t, torch.tensor([[mask_id]*block_size]).to(device)], dim=1) + attention_mask = build_custom_float_attention_mask(x_t, torch.tensor([[prompt.shape[1]]]), block_size, device=device) + attention_mask = attention_mask.to(torch.bfloat16) + for n in range(block_size): + mask_index = (x_t == mask_id) + if mask_index.sum() == 0: + break + logits =denoiser(x_t, attention_mask=attention_mask).logits + logits = shift_logits(logits) + mask_logits = logits[mask_index] + confidence, x0 = sample_tokens(mask_logits, temperature, top_p=top_p, top_k=None, neg_entropy=True) + number_transfer_tokens = 1 + _, transfer_index = torch.topk(confidence, number_transfer_tokens) + x0_ = torch.zeros_like(x0, device=device, dtype=torch.long) + mask_id + x0_[transfer_index] = x0[transfer_index].clone() + x_t[mask_index] = x0_ + answer = tokenizer.batch_decode(x_t[:, prompt.shape[1]:], skip_special_tokens=False)[0] + print(answer) + answer = tokenizer.batch_decode(x_t[:, prompt.shape[1]:], skip_special_tokens=False)[0] + print(answer) + +if __name__ == "__main__": + config = PeftConfig.from_pretrained("ybelkada/opt-350m-lora") + model = AutoModelForCausalLM.from_pretrained(config.base_model_name_or_path) + lora_model = PeftModel.from_pretrained(model, "ybelkada/opt-350m-lora") \ No newline at end of file diff --git a/Discrete-Diffusion-Forcing/D2F-train/utils/loss.py b/Discrete-Diffusion-Forcing/D2F-train/utils/loss.py new file mode 100644 index 0000000000000000000000000000000000000000..54b0fd302378b5b98a6274fed81e676cf2cd523f --- /dev/null +++ b/Discrete-Diffusion-Forcing/D2F-train/utils/loss.py @@ -0,0 +1,193 @@ +import torch +from utils.util import forward_process_length, shift_logits,forward_process +import torch.nn.functional as F + +def compute_loss_by_config( + input_ids, + denoiser, + question_length, + mask_id, + block_size, + enable_shift, + share_steps, + self_align, + feature_align, + self_step, + eos_id, + config +): + """Select different loss functions based on config file""" + training_mode = config.get('training_mode', 'dream') + + if training_mode == 'llada': + return compute_llada_loss( + input_ids, denoiser, question_length, mask_id, block_size, + enable_shift, share_steps, self_align, feature_align, self_step, eos_id + ) + elif training_mode == 'dream': + return compute_loss( + input_ids, denoiser, question_length, mask_id, block_size, + enable_shift, share_steps, self_align, feature_align, self_step, eos_id + ) + else: + raise ValueError(f"Unsupported training mode: {training_mode}") + +def compute_loss( + input_ids, + denoiser, + question_length, + mask_id, + block_size, + enable_shift, + share_steps, + self_align, + feature_align, + self_step, + eos_id, +): + B, L = input_ids.shape + noisy_batch, masked_indices, p_mask = forward_process_length(input_ids, mask_id=mask_id,prompt_lengths=question_length, block_size=block_size,eos_id=eos_id) + token_positions = torch.arange(L, device=noisy_batch.device).expand(B, L) + prompt_mask = (token_positions < question_length.unsqueeze(1)) + noisy_batch[prompt_mask] = input_ids[prompt_mask] + # prompt_mask = prompt_mask.to(torch.int64) + noisy_batch = noisy_batch.to(denoiser.device) + attention_mask=build_custom_float_attention_mask(noisy_batch, question_length, block_size, device=noisy_batch.device) + attention_mask=attention_mask.to(torch.float16) + logits=denoiser(noisy_batch,attention_mask=attention_mask).logits + logits=shift_logits(logits) + if self_align: + with torch.no_grad(): + with denoiser.disable_adapter(): + # ref_model = denoiser + # ref_model.eval() + # print(type(ref_model)) + # denoiser.eval() + ref_logits=denoiser(noisy_batch,attention_mask=torch.zeros([1,1,noisy_batch.shape[1],noisy_batch.shape[1]],dtype=torch.float16,device=denoiser.device)).logits + ref_logits=shift_logits(ref_logits) + ref_logits = torch.nn.functional.softmax(ref_logits, dim=-1) + # denoiser.train() + token_loss_2 = F.cross_entropy(logits[masked_indices], ref_logits[masked_indices], reduction='none') / p_mask[masked_indices] + # print("token_loss_2",token_loss_2.shape) + else: + token_loss_2= F.cross_entropy(logits[masked_indices], input_ids[masked_indices], reduction='none') / p_mask[masked_indices] + losses = { + # 'loss_1': token_loss_2.mean() * 0, + 'loss': token_loss_2.mean(), + } + + return losses +def compute_normal_loss( + input_ids, + denoiser, + question_length, + mask_id, + block_size, + enable_shift, + share_steps, + self_align, + feature_align, + self_step, + eos_id, +): + B, L = input_ids.shape + noisy_batch, masked_indices, p_mask = forward_process_length(input_ids, mask_id=mask_id,prompt_lengths=question_length, block_size=block_size,eos_id=eos_id) + token_positions = torch.arange(L, device=noisy_batch.device).expand(B, L) + prompt_mask = (token_positions < question_length.unsqueeze(1)) + noisy_batch[prompt_mask] = input_ids[prompt_mask] + # prompt_mask = prompt_mask.to(torch.int64) + noisy_batch = noisy_batch.to(denoiser.device) + logits=denoiser(noisy_batch).logits + logits=shift_logits(logits) + token_loss_2= F.cross_entropy(logits[masked_indices], input_ids[masked_indices], reduction='none') / p_mask[masked_indices] + losses = { + # 'loss_1': token_loss_2.mean() * 0, + 'loss': token_loss_2.mean(), + } + + return losses +import torch +def compute_llada_loss( + input_ids, + denoiser, + question_length, + mask_id, + block_size, + enable_shift, + share_steps, + self_align, + feature_align, + self_step, + eos_id, +): + mask_id=126336 + B, L = input_ids.shape + noisy_batch, masked_indices, p_mask = forward_process_length(input_ids, mask_id=mask_id,prompt_lengths=question_length, block_size=block_size,eos_id=eos_id) + token_positions = torch.arange(L, device=noisy_batch.device).expand(B, L) + prompt_mask = (token_positions < question_length.unsqueeze(1)) + noisy_batch[prompt_mask] = input_ids[prompt_mask] + # prompt_mask = prompt_mask.to(torch.int64) + noisy_batch = noisy_batch.to(denoiser.device) + # print(noisy_batch) + attention_mask=build_custom_float_attention_mask(noisy_batch, question_length, block_size, device=noisy_batch.device) + attention_mask=attention_mask.to(torch.float16) + # print(type(denoiser),noisy_batch.shape,attention_mask.shape) + logits=denoiser(noisy_batch,attention_bias=attention_mask).logits + # logits=shift_logits(logits) + if self_align: + with torch.no_grad(): + with denoiser.disable_adapter(): + # ref_model = denoiser + # ref_model.eval() + # print(type(ref_model)) + ref_logits=denoiser(noisy_batch,attention_bias=torch.zeros([1,1,noisy_batch.shape[1],noisy_batch.shape[1]],dtype=torch.float16,device=denoiser.device)).logits + # ref_logits=shift_logits(ref_logits) + ref_logits = torch.nn.functional.softmax(ref_logits, dim=-1) + token_loss_2 = F.cross_entropy(logits[masked_indices], ref_logits[masked_indices], reduction='none') / p_mask[masked_indices] + # print("token_loss_2",token_loss_2.shape) + else: + token_loss_2= F.cross_entropy(logits[masked_indices], input_ids[masked_indices], reduction='none') / p_mask[masked_indices] + losses = { + # 'loss_1': token_loss_2.mean() * 0, + 'loss': token_loss_2.mean(), + } + + return losses + + +def build_custom_float_attention_mask(input_ids, prompt_length, block_size, device=None): + B,seq_len= input_ids.shape + # 初始化为全 -inf + attn_mask = torch.full((B,1,seq_len, seq_len), float('-inf'), dtype=torch.float32, device=device) + # 1. Prompt部分:每个token可以注意整个prompt + for i in range(B): + attn_mask[i,:,:,:prompt_length[i]] = 0.0 # 允许所有 token 看 prompt + + # 2. 块划分:从 prompt_length 开始划分 block + num_blocks = (seq_len - prompt_length[i] + block_size - 1) // block_size + + for b in range(num_blocks): + block_start = prompt_length[i] + b * block_size + # print(block_start,block_size,seq_len) + block_end = min(block_start + block_size, seq_len) + + # 块内全注意 + attn_mask[i,:,block_start:block_end, block_start:block_end] = 0.0 + + # 块之间因果注意(只能看前面块) + for prev_b in range(b): + prev_start = prompt_length[i] + prev_b * block_size + prev_end = min(prev_start + block_size, seq_len) + + # 当前块可以看前面块 + attn_mask[i,:,block_start:block_end, prev_start:prev_end] = 0.0 + + return attn_mask # [seq_len, seq_len], float, 0.0 for allowed, -inf for disallowed +if __name__ == "__main__": + seq_len = 10 + input_ids = torch.randint(0, 100, (2, seq_len)) # 示例输入 + block_size = 4 + prompt_length = torch.tensor([2, 4]) # 示例prompt长度 + device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') + attn_mask = build_custom_float_attention_mask(input_ids, prompt_length, block_size, device) + print(attn_mask) \ No newline at end of file diff --git a/Discrete-Diffusion-Forcing/D2F-train/utils/model.py b/Discrete-Diffusion-Forcing/D2F-train/utils/model.py new file mode 100644 index 0000000000000000000000000000000000000000..8e1870621df61acddf0f6ba4a38bfe610c6544dc --- /dev/null +++ b/Discrete-Diffusion-Forcing/D2F-train/utils/model.py @@ -0,0 +1,59 @@ +import transformers +from transformers import AutoModel, AutoTokenizer +from peft import LoraConfig,get_peft_model +from model.modeling_llada import LLaDAModelLM +from model.configuration_llada import LLaDAConfig + +def get_model_by_config(config): + """Select different models based on config file""" + training_mode = config.get('training_mode', 'dream') + + if training_mode == 'llada': + return get_llada(config) + elif training_mode == 'dream': + return get_model(config) + else: + raise ValueError(f"Unsupported training mode: {training_mode}") + +def get_model(config): + # Use path from config, use default path if no config + model_path = config.paths.model if hasattr(config, 'paths') and hasattr(config.paths, 'model') else "/home/wx/data/model/Dream-org/Dream-v0-Base-7B" + + model = AutoModel.from_pretrained(model_path, trust_remote_code=True) + # print(model.named_modules()) + # print(model,"model + for param in model.parameters(): + param.requires_grad = False + tokenizer=AutoTokenizer.from_pretrained(model_path, trust_remote_code=True) + peft_config = LoraConfig(r=32, lora_alpha=32, lora_dropout=0.1,target_modules=["q_proj", "v_proj","k_proj", "o_proj"],) + model = get_peft_model(model, peft_config) + model.print_trainable_parameters() + return model, tokenizer + +def get_llada(config): + # Use path from config, use default path if no config + model_path = config.paths.model if hasattr(config, 'paths') and hasattr(config.paths, 'model') else "/data1/xck/models/llada-8b-instruct" + + config_obj=LLaDAConfig.from_pretrained(model_path) + model = LLaDAModelLM.from_pretrained(model_path,config=config_obj) + # print(model.named_modules()) + # print(model,"model + # print(model) + # exit() + for param in model.parameters(): + param.requires_grad = False + tokenizer=AutoTokenizer.from_pretrained(model_path, trust_remote_code=True) + peft_config = LoraConfig(r=32, lora_alpha=32, lora_dropout=0.1,target_modules=["q_proj", "v_proj","k_proj", "attn_out"],) + model = get_peft_model(model, peft_config) + model.print_trainable_parameters() + return model, tokenizer +# def create_attention_mask(input_ids, mask_id): +# """ +# Create an attention mask based on the input_ids and mask_id. + +# Args: +# input_ids (torch.Tensor): The input tensor of shape (batch_size, sequence_length). +# mask_id (int): The ID of the mask token. + +# Returns: +# torch.Tensor: The attention mask of shape (batch_size, sequence_length, sequence_length). diff --git a/Discrete-Diffusion-Forcing/D2F-train/utils/util.py b/Discrete-Diffusion-Forcing/D2F-train/utils/util.py new file mode 100644 index 0000000000000000000000000000000000000000..51fa32927ce7514024f2e98466a19980823373cd --- /dev/null +++ b/Discrete-Diffusion-Forcing/D2F-train/utils/util.py @@ -0,0 +1,159 @@ +import torch +from torch.distributions import Uniform + +def forward_process_block_fixed_p(x, mask_id, p_mask): + B, L = x.shape + if isinstance(p_mask, float): + p_mask = torch.full((B, 1), p_mask, device=x.device) + elif p_mask.ndim == 1: + p_mask = p_mask[:, None] + rand = torch.rand((B, L), device=x.device) + mask = rand < p_mask + x_masked = torch.where(mask, mask_id, x) + return x_masked, mask + +import torch + +def generate_monotonic_pmasks(batch_size, max_blocks, device): + """ + 生成 shape (B, max_blocks) 的单调非降随机序列,每行第一个元素在[0,1]随机,后续不小于前一个 + """ + # 第一个block p_mask随机 + p0 = torch.rand(batch_size, 1, device=device)/2+0.2 + # print(p0) + # 后续blocks生成增量 [0, 1],加起来保证不超过1(之后用 clamp) + increments = torch.rand(batch_size, max_blocks - 1, device=device) * (0.7 - p0)/ (max_blocks - 1) + # print(increments) + # 逐元素累加,保证非降 + cum_increments = torch.cumsum(increments, dim=1) + # print(cum_increments) + # 总 p_mask = p0 + 累积增量,保证不超过1 + p_masks = torch.cat([p0, p0 + cum_increments], dim=1) + p_masks = torch.clamp(p_masks, max=1.0) + # print(p_masks) + return p_masks # (B, max_blocks) + + +def forward_process_length(input_ids, mask_id, block_size, prompt_lengths,eos_id=None): + """ + Args: + input_ids: (B, L) + prompt_lengths: (B,) + Returns: + noisy_batch, masked_indices, p_mask_tensor + """ + B, L = input_ids.shape + device = input_ids.device + noisy_batch = input_ids.clone() + eos_indices= (input_ids==eos_id) + masked_indices = torch.zeros_like(input_ids,dtype=torch.bool) + p_mask_tensor = torch.zeros((B, L), device=device) + + # 计算每个样本block数 + non_prompt_lens = L - prompt_lengths + full_blocks = non_prompt_lens // block_size + remainders = non_prompt_lens % block_size + total_blocks = full_blocks + (remainders > 0).long() + + max_blocks = total_blocks.max().item() + + # 生成每个样本block的mask比率,单调非降且第一个随机 + p_masks = generate_monotonic_pmasks(B, max_blocks, device) # shape (B, max_blocks) + + for i in range(B): + prompt_len = prompt_lengths[i].item() + num_blocks = total_blocks[i].item() + start_block = torch.tensor([0]) # 随机选择一个block开始 + for block_idx in range(num_blocks): + if block_idx < start_block: + continue + start = prompt_len + block_idx * block_size + end = min(start + block_size, L) + + p_block = p_masks[i, block_idx-start_block].item() + + block = noisy_batch[i, start:end].unsqueeze(0) + masked_block, mask = forward_process_block_fixed_p(block, mask_id, p_block) + + noisy_batch[i, start:end] = masked_block.squeeze(0) + masked_indices[i, start:end] = mask.squeeze(0) + # if torch.all(input_ids[i, start:end] == eos_id): + # masked_indices[i,start:end]== False + # print("1") + + p_mask_tensor[i, start:end] = p_block + + return noisy_batch, masked_indices, p_mask_tensor + +# def forward_process_length(input_ids, mask_id, block_size, prompt_lengths, p_min=0.2, p_max=0.9): +# """ +# 返回每个 token 的实际 mask 概率 tensor(非prompt区域),其余为0。 +# """ +# B, L = input_ids.shape +# device = input_ids.device +# noisy_batch = input_ids.clone() +# masked_indices = torch.zeros_like(input_ids, dtype=torch.bool) +# p_mask_tensor = torch.zeros((B, L), device=device) # 最终返回值 + +# for i in range(B): +# prompt_len = prompt_lengths[i].item() +# non_prompt_len = L - prompt_len +# full_blocks = non_prompt_len // block_size +# remainder = non_prompt_len % block_size +# total_blocks = full_blocks + (1 if remainder > 0 else 0) + +# for block_idx in range(total_blocks): +# start = prompt_len + block_idx * block_size +# end = min(start + block_size, L) + +# # block的 mask 概率(线性递增) +# if total_blocks > 1: +# p_block = p_min + (p_max - p_min) * (block_idx / (total_blocks - 1)) +# else: +# p_block = p_max + +# block = noisy_batch[i, start:end].unsqueeze(0) +# masked_block, mask = forward_process_block_fixed_p(block, mask_id, p_block) +# noisy_batch[i, start:end] = masked_block.squeeze(0) +# masked_indices[i, start:end] = mask.squeeze(0) + +# # 记录 p_mask 到 tensor 中 +# p_mask_tensor[i, start:end] = p_block + +# return noisy_batch, masked_indices, p_mask_tensor +def forward_process(input_ids,mask_id ,t_max=1.0, eps=1e-4): + B, L = input_ids.shape + # t = torch.rand(B, device=input_ids.device) + dist = Uniform(0., t_max) + t = dist.sample((B,)).to(input_ids.device) + p_mask = (1 - eps) * t + eps + p_mask = p_mask[:, None].repeat(1, L) + masked_indices = torch.rand((B, L), device=input_ids.device) < p_mask + noisy_batch = torch.where(masked_indices, mask_id, input_ids) + + return noisy_batch, masked_indices, p_mask +def flatten_dict(d, parent_key='', sep='_'): + items = [] + for k, v in d.items(): + new_key = f"{parent_key}{sep}{k}" if parent_key else k + if isinstance(v, dict): + items.extend(flatten_dict(v, new_key, sep=sep).items()) + else: + items.append((new_key, v)) + return dict(items) + +def shift_logits(logits): + shifted_logits = torch.zeros_like(logits) + shifted_logits[:, 1:, :] = logits[:, :-1, :] + shifted_logits[:, 0, :] = 1.0 + + return shifted_logits +if __name__ == '__main__': + input_ids= torch.tensor([[1,5,4,3,25,6,7,9,5,8,7,6],[1,3,8,9,7,34,6,9,5,8,7,6]]) + mask_id=0 + block_size=3 + prompt_length=torch.tensor([2,1]) + noisy_batch, masked_indices,p_mask = forward_process_length(input_ids, mask_id, block_size, prompt_length) + print("noisy_batch:", noisy_batch) + print("masked_indices:", masked_indices) + print("p_mask:", p_mask) diff --git a/Discrete-Diffusion-Forcing/Discrete Diffusion Forcing.pdf b/Discrete-Diffusion-Forcing/Discrete Diffusion Forcing.pdf new file mode 100644 index 0000000000000000000000000000000000000000..bb9ef6b4fa7c13fb1b677dda95ceeb315c031953 --- /dev/null +++ b/Discrete-Diffusion-Forcing/Discrete Diffusion Forcing.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:882a505b24c5a09dacae481b637388774c24f0f53e27545da67291b07e5b157d +size 788772 diff --git a/Discrete-Diffusion-Forcing/LICENCE b/Discrete-Diffusion-Forcing/LICENCE new file mode 100644 index 0000000000000000000000000000000000000000..5aa6f712d05a1f83d82543b1c0495e6040bd349e --- /dev/null +++ b/Discrete-Diffusion-Forcing/LICENCE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 DENG Lab @ SJTU + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Discrete-Diffusion-Forcing/README.md b/Discrete-Diffusion-Forcing/README.md new file mode 100644 index 0000000000000000000000000000000000000000..94744500ec444a2704ed592a60092b9a14e82582 --- /dev/null +++ b/Discrete-Diffusion-Forcing/README.md @@ -0,0 +1,376 @@ +

+ +

+ +## Discrete Diffusion Forcing (D2F): dLLMs Can Do Faster-Than-AR Inference + +

+ 📄 Paper • + 📝 Blog Post • + 🚀 Online Demo • + 🤗 D2F-Dream LoRA • + 🤗 D2F-LLaDA LoRA +

+ +

+ 💬 Discord • + 💬 Wechat +

+ + + +https://github.com/user-attachments/assets/d9de6450-68d6-4caf-85c2-c7f384395c42 + + +

+
+ Real-time generation demo: our D2F model (left) uses parallel block decoding, while the AR baseline (right) generates tokens sequentially. This visualizes the source of D2F's significant throughput advantage. +

+ +
+ +

+ +
+ Inference throughput comparison: D2F dLLMs surpass similarly-sized AR models in inference speed for the first time, achieving up to a 2.5x speedup over LLaMA3 and a >50x speedup over vanilla dLLM baselines (Speed tests conducted on NVIDIA A100-PCIe-40GB GPUs). +

+ +**Discrete Diffusion Forcing (D2F)** is a novel training and inference paradigm that, for the first time, enables open-source Diffusion Language Models (dLLMs) to surpass their autoregressive (AR) counterparts in inference speed. By introducing a highly efficient AR-diffusion hybrid model, D2F achieves: +- Up to a **2.5x speedup** over leading AR models like LLaMA3-8B. +- A staggering **50x+ acceleration** over vanilla dLLM baselines. +- Comparable generation quality on standard reasoning and coding benchmarks. +- **Integration with vLLM** to unlock the next tier of extreme inference acceleration. + +This repository provides the code to reproduce our evaluation results and run generation demos. + +## 🔥 News! +* Aug 20, 2025: We've released the training pipeline of D2F! +* Aug 8, 2025: We've released the inference code of D2F! +## Contents +- [🤔 How It Works](#-how-it-works) +- [📊 Performance Highlights](#-performance-highlights) +- [⚡️ Extreme Acceleration with vLLM Integration](#️-extreme-acceleration-with-vllm-integration) +- [🚀 Usage Guide](#-usage-guide) +- [🙏 Acknowledgements](#-acknowledgements) +- [©️ Citation](#️-citation) + +## 🤔 How It Works + +D2F overcomes the historical speed bottlenecks of dLLMs (KV Cache incompatibility and strict sequential dependencies) by restructuring the generation process. + +**1. Hybrid Architecture:** D2F employs a **block-wise causal attention** mechanism. Attention *within* a block is bidirectional, preserving rich local context, while attention *between* blocks is causal. This simple but powerful change makes the model fully compatible with the standard KV Cache, drastically reducing redundant computations. + +**2. Efficient Training via Asymmetric Distillation:** Instead of training from scratch, we distill a powerful, pre-trained bidirectional dLLM (teacher) into our cache-friendly D2F model (student). The student learns to match the teacher's output with only a limited, causal view of the context. + +

+ +
+ Overview of Discrete Diffusion Forcing (D2F): A D2F model (student) with a KV-cache-friendly block-wise causal attention mask is trained to mimic a powerful, pre-trained bidirectional dLLM (teacher), efficiently inheriting its capabilities. +

+ +**3. High-Throughput Pipelined Decoding:** D2F is trained to predict future blocks based on *partially incomplete* prefixes. This enables a **pipelined parallel decoding** algorithm during inference, where multiple blocks are refined simultaneously in an asynchronous workflow, maximizing GPU utilization and throughput. + +

+ +
+ Visualization of our pipelined parallel decoding: New blocks are dynamically added and decoded in parallel with their predecessors, moving from a conservative "semi-activated" state to an aggressive "fully-activated" state. This creates a continuous, high-throughput generation flow. +

+ +https://github.com/user-attachments/assets/41a0176b-e4ae-4f8b-95a6-daed7af2a027 + +

+
+ A slow-motion demonstration of the parallel decoding process within a single block of D2F. Watch as multiple tokens within the block are refined simultaneously, showcasing the efficiency of our approach. +

+ +## 📊 Performance Highlights + +D2F delivers transformative speedups while maintaining or improving scores. Below is a comprehensive summary of performance on **LLaDA-Instruct-8B** and **Dream-Base-7B**, comparing our method against the original baseline and the previous SOTA acceleration method, Fast-dLLM. + +
+ +**Performance on LLaDA-Instruct-8B** + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
BenchmarkMetricLLaDA-Instruct (Baseline)Fast-dLLM (SOTA)D2F-LLaDA (Ours)
GSM8K-4-shotTPS ↑7.235.252.5 (7.3x)
Score ↑77.478.977.3
MBPP-3-shotTPS ↑0.915.347.6 (52.9x)
Score ↑39.036.438.0
HumanEval-0-shotTPS ↑2.819.281.6 (29.1x)
Score ↑36.035.440.2
Math-4-shotTPS ↑21.142.590.2 (4.3x)
Score ↑23.722.429.1
+ +**Performance on Dream-Base-7B** + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
BenchmarkMetricDream-Base (Baseline)Fast-dLLM (SOTA)D2F-Dream (Ours)
GSM8K-CoT-8-shotTPS ↑9.549.891.2 (9.6x)
Score ↑75.075.077.6
MBPP-3-shotTPS ↑10.473.2105 (10.1x)
Score ↑56.251.056.4
HumanEval-0-shotTPS ↑20.260.073.2 (3.6x)
Score ↑54.353.055.5
Math-4-shotTPS ↑9.967.098.8 (10.0x)
Score ↑35.837.635.4
+
+ +## ⚡️ Extreme Acceleration with vLLM Integration + +To push the boundaries of inference speed, we've integrated D2F with a **preliminary vLLM-based engine**. This unlocks a multiplicative speedup on top of our already-accelerated model, showcasing the immense potential for production environments. + +
+ +HumanEval-0-shot with vLLM + + + + + + + + + + + + + + + + + + + + + + + + + +
ModelTPS ↑Score ↑
Dream-Base (Baseline)20.2 (1.0x)54.3
D2F-Dream (Ours)73.2 (3.6x)54.3
D2F-Dream + vLLM (Ours)131.7 (6.5x)40.2
+
+Our D2F-Dream model with a preliminary vLLM engine achieves a 6.5x speedup over the original Dream-Base, though we observe a score drop that we are actively working to resolve through optimized kernels. + +
+ +> **Implementation Notes:** +> The current vLLM integration is an initial proof-of-concept. It already provides a significant performance boost by leveraging Flex Attention, but there is substantial room for further optimization. Our future work will focus on implementing specialized CUDA kernels and other advanced vLLM features to maximize speed while restoring the score. + +## 🚀 Usage Guide + +### 1. Installation + +First, clone the repository and set up the environment. + +```shell +# Clone the repository +git clone https://github.com/zhijie-group/Discrete-Diffusion-Forcing.git +cd Discrete-Diffusion-Forcing +``` + +#### Environment Configuration + +##### UV (Recommended) + +```shell +uv sync +``` + +##### Conda + +```shell +# Create and activate a conda environment +conda create -n d2f python=3.10 +conda activate d2f + +# Install dependencies +pip install -r requirements.txt +``` + +#### vLLM Installation + +vLLM is comming soon, right now we only implemented the basic functions of vLLM. + +### 2. Evaluation +All evaluation scripts are located in the `D2F-eval` directory. + +```shell +cd D2F-eval +``` + +To evaluate the **D2F-Dream** model on all benchmarks, run: + +```shell +shell eval_dream.sh +``` + +To evaluate the **D2F-LLaDA** model on all benchmarks, run: + +```shell +shell eval_llada.sh +``` +The results will be saved in the `output_path` specified within the shell scripts. + +> ### ❗️ Important Notice for HumanEval +> The `HumanEval` benchmark requires a post-processing step to sanitize the generated code and calculate the final `pass@1` score. After the evaluation script finishes, run the following command: +> ```shell +> python postprocess_code.py {path/to/your/samples_humaneval_xxx.jsonl} +> ``` +> Replace the path with the actual path to your generated samples file, which can be found in the specified `output_path`. + +### 3. Training +All training scripts and configurations are located in the `D2F-train` directory. +```shell +# Navigate to the training directory +cd D2F-train +``` +Before starting the training, you need to configure the paths for your dataset, models, and output directories. Modify the relevant paths in the configuration files located inside the `config` folder. + +Once the configuration is set, you can start the training process by running: +```shell +bash train.sh +``` + +### 4. Generation Demo + +We provide simple scripts to demonstrate the generation process and compare D2F with a standard AR baseline. +```shell +# To run a demo with the D2F pipelined block generation method: +python generate_llada_demo_block.py + +# To compare, run a demo with the baseline AR generation method: +python generate_llada_demo_ar.py +``` +You can inspect these files to see how to use the D2F model for inference in your own projects. + +## 📚 Future Works + +- [x] Implement dLLM-suported vLLM (preliminary). +- [ ] Implement fused dLLM-specific decoding kernels for vLLM to maximize performance and restore scores. +- [ ] Implement distributed inference with multi-GPUs in vLLM. +- [ ] Implement CUDA graph capturing for dynamic sequences in vLLM. + +## 🙏 Acknowledgements +Our work builds upon the foundations laid by the original **LLaDA** and **Dream** models. We thank their authors for making their work public. We are also grateful for the powerful open-source tools from Hugging Face and the vLLM team that made this research possible. + +## ©️ Citation +If you find our work useful for your research, please consider citing our paper: +```bibtex +@article{wang2025diffusion, + title={Diffusion llms can do faster-than-ar inference via discrete diffusion forcing}, + author={Wang, Xu and Xu, Chenkai and Jin, Yijie and Jin, Jiachun and Zhang, Hao and Deng, Zhijie}, + journal={arXiv preprint arXiv:2508.09192}, + year={2025} +} + + +``` diff --git a/Discrete-Diffusion-Forcing/docs/_config.yml b/Discrete-Diffusion-Forcing/docs/_config.yml new file mode 100644 index 0000000000000000000000000000000000000000..fff4ab923ce55f11569939421ffa39cd3566f5d9 --- /dev/null +++ b/Discrete-Diffusion-Forcing/docs/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-minimal diff --git a/Discrete-Diffusion-Forcing/docs/assets/img/d2f/fig1_main_result.png b/Discrete-Diffusion-Forcing/docs/assets/img/d2f/fig1_main_result.png new file mode 100644 index 0000000000000000000000000000000000000000..a6db48deea48fdb72684ce993ff9ca795aa85c4e --- /dev/null +++ b/Discrete-Diffusion-Forcing/docs/assets/img/d2f/fig1_main_result.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cfb1e118048eb01d3d0ea6d66441b4514649f3ae4f18b9030d14fcc89ee7ec21 +size 147493 diff --git a/Discrete-Diffusion-Forcing/docs/assets/img/d2f/fig2_tradeoff.png b/Discrete-Diffusion-Forcing/docs/assets/img/d2f/fig2_tradeoff.png new file mode 100644 index 0000000000000000000000000000000000000000..6a6f3d285158767f5f1e4a7b1bbdd66e35f00f50 --- /dev/null +++ b/Discrete-Diffusion-Forcing/docs/assets/img/d2f/fig2_tradeoff.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b2094cc706b52e112c9c534f477ce9176a427d06825d149ba629b87178ca3b5a +size 148476 diff --git a/Discrete-Diffusion-Forcing/docs/assets/img/d2f/fig3_overview.png b/Discrete-Diffusion-Forcing/docs/assets/img/d2f/fig3_overview.png new file mode 100644 index 0000000000000000000000000000000000000000..7108fccd0fc43bd39d65d32f5f88e12b4822d65c --- /dev/null +++ b/Discrete-Diffusion-Forcing/docs/assets/img/d2f/fig3_overview.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f44c48ebc35f89a07282339bad7b3d93bcca78690288e04677eddc9a82c3b87b +size 238897 diff --git a/Discrete-Diffusion-Forcing/docs/assets/img/d2f/fig4_pipeline.png b/Discrete-Diffusion-Forcing/docs/assets/img/d2f/fig4_pipeline.png new file mode 100644 index 0000000000000000000000000000000000000000..901aaa6da9fcc3045f7bc5e669f9aa6b57974193 --- /dev/null +++ b/Discrete-Diffusion-Forcing/docs/assets/img/d2f/fig4_pipeline.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ee9444329171647e78530fe7f7a10e472f3670365ed1acb4a790d6e0edb7347f +size 391012 diff --git a/Discrete-Diffusion-Forcing/docs/assets/img/d2f/logo_lr.jpg b/Discrete-Diffusion-Forcing/docs/assets/img/d2f/logo_lr.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ae1f1519994ebcb8a29b47ca81ff458130c7743e Binary files /dev/null and b/Discrete-Diffusion-Forcing/docs/assets/img/d2f/logo_lr.jpg differ diff --git a/Discrete-Diffusion-Forcing/docs/assets/img/d2f/logo_lr.png b/Discrete-Diffusion-Forcing/docs/assets/img/d2f/logo_lr.png new file mode 100644 index 0000000000000000000000000000000000000000..ce6982604ce3f85ded91b0ca6f5a0448fab23ccc --- /dev/null +++ b/Discrete-Diffusion-Forcing/docs/assets/img/d2f/logo_lr.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3f3991d4d92c641746a9893c11544f0d61e5323d2224e6a561774c67540378d6 +size 133730 diff --git a/Discrete-Diffusion-Forcing/docs/assets/img/d2f/logo_ud.jpg b/Discrete-Diffusion-Forcing/docs/assets/img/d2f/logo_ud.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7b452158ad31d4fc01da0971cd1cd8506a2cf1e9 Binary files /dev/null and b/Discrete-Diffusion-Forcing/docs/assets/img/d2f/logo_ud.jpg differ diff --git a/Discrete-Diffusion-Forcing/docs/assets/img/d2f/logo_ud.png b/Discrete-Diffusion-Forcing/docs/assets/img/d2f/logo_ud.png new file mode 100644 index 0000000000000000000000000000000000000000..5c0238cc5f5e359b5d3cafe87551b244c4dae231 --- /dev/null +++ b/Discrete-Diffusion-Forcing/docs/assets/img/d2f/logo_ud.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f97c972923780a792903965ccdc9086bcdd59031f9f83ae239a26ea63bc802e4 +size 124940 diff --git a/Discrete-Diffusion-Forcing/docs/assets/img/d2f/table1_llada_results.png b/Discrete-Diffusion-Forcing/docs/assets/img/d2f/table1_llada_results.png new file mode 100644 index 0000000000000000000000000000000000000000..4cbc6307523ac181266bdb93bb8539d676f0c1d4 --- /dev/null +++ b/Discrete-Diffusion-Forcing/docs/assets/img/d2f/table1_llada_results.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f5dfe4ec848d09ae6651157748a7e855fd67f6b6699ce5a63a0178119ffa596b +size 293059 diff --git a/Discrete-Diffusion-Forcing/docs/assets/img/d2f/table2_dream_results.png b/Discrete-Diffusion-Forcing/docs/assets/img/d2f/table2_dream_results.png new file mode 100644 index 0000000000000000000000000000000000000000..8586783a3d721cf1f0bcfef5c6f2f4ced1a49a6d --- /dev/null +++ b/Discrete-Diffusion-Forcing/docs/assets/img/d2f/table2_dream_results.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2507bc2ae053ac159a586ec5898a2050cdf06e28d84089c9a85f45fce211bb26 +size 296829 diff --git a/Discrete-Diffusion-Forcing/docs/assets/img/d2f/wechat.png b/Discrete-Diffusion-Forcing/docs/assets/img/d2f/wechat.png new file mode 100644 index 0000000000000000000000000000000000000000..76652eb4f05d869b7f7972a65c88f457b5b503bf --- /dev/null +++ b/Discrete-Diffusion-Forcing/docs/assets/img/d2f/wechat.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b0dc909260521f5b29214b6bf1d2c794164dffaad679e5aa98d664281629e1dd +size 241185 diff --git a/Discrete-Diffusion-Forcing/docs/assets/video/block_demo.mp4 b/Discrete-Diffusion-Forcing/docs/assets/video/block_demo.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..388dc533fed16ef15b2133e98aa6b9d76540f004 --- /dev/null +++ b/Discrete-Diffusion-Forcing/docs/assets/video/block_demo.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0af9d7646237b7e3b5f3caaa07d2b62d685fbd7de167a76028156dbd13399569 +size 11324611 diff --git a/Discrete-Diffusion-Forcing/docs/assets/video/block_demo_small.mp4 b/Discrete-Diffusion-Forcing/docs/assets/video/block_demo_small.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..48d73db3dcf548805817098e62fd2f643febda40 --- /dev/null +++ b/Discrete-Diffusion-Forcing/docs/assets/video/block_demo_small.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dc78f8032ef29751905cf7ca950e6eceacd5421c9553bd4a60a37dc5d3babf98 +size 6986379 diff --git a/Discrete-Diffusion-Forcing/docs/assets/video/d2f_vs_ar_demo.mp4 b/Discrete-Diffusion-Forcing/docs/assets/video/d2f_vs_ar_demo.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..88e27410bbd203a215423993a4af4ce18d4133d7 --- /dev/null +++ b/Discrete-Diffusion-Forcing/docs/assets/video/d2f_vs_ar_demo.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e1aa010e9417f21678fd67fc28c5f9d703c68fa8bc5351f5b2303efed5d54635 +size 12208075 diff --git a/Discrete-Diffusion-Forcing/docs/assets/video/d2f_vs_ar_demo_small.mp4 b/Discrete-Diffusion-Forcing/docs/assets/video/d2f_vs_ar_demo_small.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..066cc23ef340f73150893637ac0437c030963563 --- /dev/null +++ b/Discrete-Diffusion-Forcing/docs/assets/video/d2f_vs_ar_demo_small.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dc0a2a1b07a301c481c36734da20e525b3933e890341a4ce0fea2237a439f16c +size 4888478 diff --git a/Discrete-Diffusion-Forcing/docs/index.html b/Discrete-Diffusion-Forcing/docs/index.html new file mode 100644 index 0000000000000000000000000000000000000000..1acb57d699f85b8869233f93cd88f30fd4927244 --- /dev/null +++ b/Discrete-Diffusion-Forcing/docs/index.html @@ -0,0 +1,530 @@ + + + + + + + + + + + + + D2F: Diffusion LLMs Can Do Faster-Than-AR Inference via Discrete Diffusion Forcing + + + + + + + + + + +
+
+ +

D2F: Diffusion LLMs Can Do Faster-Than-AR Inference via Discrete Diffusion Forcing

+ +
+ Xu Wang*, Chenkai Xu*, Yijie Jin, Jiachun Jin, Hao Zhang, Zhijie Deng + Shanghai Jiao Tong University, University of California San Diego + * Equal Contribution    Corresponding Author +
+ + +
+ +
+
+ +
+ Real-time demo comparing our D2F-dLLM (left) and a baseline AR model (right). D2F generates text in parallel blocks, achieving significantly higher throughput. +
+
+ +

Diffusion Large Language Models (dLLMs) have long held the promise of ultra-fast text generation through parallel decoding. Yet, this promise has remained unfulfilled—until now. In practice, open-source dLLMs have consistently lagged behind their autoregressive (AR) counterparts like LLaMA3 in inference speed, hindering their real-world adoption.

+ +

Today, we are excited to introduce Discrete Diffusion Forcing (D2F), a simple yet powerful strategy that shatters this speed limit. D2F creates a novel AR-diffusion hybrid model that achieves up to a 2.5x speedup over leading AR models and a staggering 50x acceleration over vanilla dLLMs, all while maintaining comparable output quality.

+ +
+ Bar chart comparing inference throughput of D2F with other DLLMs and AR models. +
+ Figure 1. D2F dLLMs surpass similarly-sized autoregressive (AR) models in inference speed for the first time, achieving up to 2.5x higher throughput than LLaMA3 and Qwen2.5 (Speed tests conducted on NVIDIA A100-PCIe-40GB GPUs). This comparison includes vanilla dLLMs, previous acceleration methods, and AR baselines. +
+
+ +

The Bottleneck: Why Were Diffusion LLMs Slow?

+

The potential of dLLMs lies in parallel decoding, but two fundamental issues have historically crippled their speed:

+
    +
  • KV Cache Incompatibility: The problem starts with bidirectional attention, where every token can see every other token. This holistic view is powerful for quality but prevents the use of the Key-Value (KV) Cache—a critical optimization in AR models that saves massive amounts of redundant computation.
  • +
  • Strict Inter-Block Dependency: Previous attempts to enable caching by generating text in blocks were still stuck. They required each block to be fully denoised before starting the next, killing any hope of true parallel processing across blocks.
  • +
+ +

The D2F Solution: A Hybrid of Speed and Power

+

D2F overcomes these bottlenecks by rethinking how dLLMs are trained and how they generate text. It's a two-part solution.

+ +

Architecture: Block-wise AR meets Parallel Diffusion

+

At its core, D2F reframes generation as a block-autoregressive process. Text is produced in sequential blocks, but with a crucial architectural twist in the attention mechanism:

+
    +
  • Attention within a block remains bidirectional to capture rich local context.
  • +
  • Attention between blocks is made causal, meaning a block only attends to previously completed blocks.
  • +
+

This smart hybrid design makes D2F fully compatible with the standard KV cache, slashing a huge source of computational waste.

+ +

Method: Asymmetric Distillation and Pipelined Inference

+

With this new architecture, D2F uses an elegant strategy for training and inference:

+

Training with Asymmetric Distillation: Instead of costly training from scratch, we use an efficient distillation process. A powerful, pre-trained dLLM (the "teacher") distills its knowledge into our D2F model (the "student"). The teacher uses its full bidirectional view to make predictions, while the student learns to match them using only its limited, block-wise causal view. This process transfers the teacher's capabilities into our faster, cache-friendly architecture in just 12 hours on 8 A100 GPUs.

+ +
+ Diagram showing the asymmetric distillation process for D2F. +
+ Figure 2. Overview of our training method, Discrete Diffusion Forcing (D2F). A D2F model with a block-wise causal attention mask (the student) is trained to mimic the predictions of a pre-trained bidirectional dLLM (the teacher). This enables KV caching while learning to perform parallel decoding across blocks. +
+
+ +

Inference with a Pipelined Parallel Decoder: This is where D2F’s speed truly shines. Because the model is trained to predict future blocks from partially incomplete ones, it doesn't need to wait. During inference, it runs a dynamic pipeline:

+ +
+ +
+ Figure 3. A slow-motion demonstration of the parallel decoding process within a single block of D2F. Watch as multiple tokens within the block are refined simultaneously, showcasing the efficiency of our approach. +
+
+ +
    +
  • A new block is added to the pipeline as soon as its predecessor is only partially complete.
  • +
  • Blocks begin in a conservative "semi-activated" state and transition to an aggressive "fully-activated" state once they have enough context from the block before them.
  • +
+

This creates a highly efficient, asynchronous workflow where multiple blocks are refined in parallel, maximizing GPU utilization and dramatically boosting throughput.

+ +
+ Visualization of the pipelined parallel decoding workflow. +
+ Figure 4. Visualization of our pipelined parallel decoding algorithm. A dynamic pipeline of blocks is decoded in parallel. New blocks are added once their predecessor is partially complete, and transition from a conservative 'semi-activated' state to an aggressive 'fully-activated' state, maximizing throughput. +
+
+ +

Putting D2F to the Test: Performance Highlights

+

D2F sets a new standard for dLLM efficiency across multiple benchmarks. We applied our D2F method to two popular open-source dLLMs, LLaDA and Dream, and the results speak for themselves.

+ +

Detailed Benchmark Results: LLaDA-Instruct-8B

+

Our D2F method dramatically boosts speed for LLaDA, achieving up to a 52.9x increase in throughput on MBPP and a 29.1x increase on HumanEval compared to the baseline, while significantly outperforming previous SOTA acceleration methods.

+ +
GSM8K-4-shot
+ + + + + + + + + +
MethodTPS ↑Latency (s) ↓Gen. LengthScore ↑
LLaDA-Instruct7.2 (1.0x)32.3 (1.0x)23177.4
dLLM-Cache20.1 (2.8x)11.5 (2.8x)23177.5
Fast-dLLM (Prefix-Cache)33.3 (4.6x)7.0 (4.6x)23277.8
Fast-dLLM (Dual-Cache)35.2 (4.9x)6.6 (4.9x)23278.9
D2F-LLaDA52.5 (7.3x)2.8 (11.5x)14477.3
+ +
MBPP-3-shot
+ + + + + + + + + +
MethodTPS ↑Latency (s) ↓Gen. LengthScore ↑
LLaDA-Instruct0.9 (1.0x)71.4 (1.0x)6539.0
dLLM-Cache2.3 (2.6x)28.3 (2.5x)6637.0
Fast-dLLM (Prefix-Cache)13.0 (14.4x)4.9 (14.6x)6437.6
Fast-dLLM (Dual-Cache)15.3 (17.0x)3.8 (18.8x)5836.4
D2F-LLaDA47.6 (52.9x)1.4 (51.0x)6838.0
+ +

Detailed Benchmark Results: Dream-Base-7B

+

Applying D2F to Dream-Base-7B results in substantial gains, including a 9.6x speedup on GSM8K-CoT and a 10.1x speedup on MBPP. Notably, the performance score often improves alongside the speed.

+ +
GSM8K-CoT-8-shot
+ + + + + + + + + +
MethodTPS ↑Latency (s) ↓Gen. LengthScore ↑
Dream-Base9.5 (1.0x)26.8 (1.0x)25575.0
dLLM-Cache26.0 (2.7x)9.8 (2.7x)25572.0
Fast-dLLM (Prefix-Cache)50.3 (5.3x)5.1 (5.3x)25576.6
Fast-dLLM (Dual-Cache)49.8 (5.2x)5.1 (5.3x)25575.0
D2F-Dream91.2 (9.6x)2.8 (9.6x)25677.6
+ +
MBPP-3-shot
+ + + + + + + + + +
MethodTPS ↑Latency (s) ↓Gen. LengthScore ↑
Dream-Base10.4 (1.0x)24.6 (1.0x)25656.2
dLLM-Cache25.5 (2.5x)10.0 (2.5x)25652.6
Fast-dLLM (Prefix-Cache)71.6 (6.9x)3.6 (6.8x)25656.4
Fast-dLLM (Dual-Cache)73.2 (7.0x)3.5 (7.0x)25651.0
D2F-Dream105 (10.1x)2.3 (10.7x)24055.2
+ +
Demo Results of vLLM on HumanEval
+ + We tested the performance of our implemented vLLM (initial version). While there was significant performance improvement, we observed an unexpected decrease in inference scores. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Model & Thresholds (Add, Act, Conf)TPS ↑Latency (s) ↓Gen. LengthScore ↑
Dream-Base (0.1, 0.95, 0.90)20.2 (1.0x)12.6 (1.0x)25554.3
D2F-Dream (0.1, 0.95, 0.90)73.2 (3.6x)3.1 (4.1x)22754.3
D2F-Dream-vLLM (0.1, 0.95, 0.90)127.2 (6.3x)1.87 (6.74x)23834.1
D2F-Dream-vLLM (0.05, 0.90, 0.95)131.7 (6.5x)1.78 (7.08x)23840.2
+
+
Implementation Notes
+

The current vLLM-based prototype does not yet include:

+
    +
  • Specialized fused / hand-optimized operators for existing decoding paradigms
  • +
  • CUDA Graph capture
  • +
  • Multi-GPU distributed inference
  • +
+

We leverage Flex Attention for convenience to obtain efficient inference. Despite saturating GPU memory, overall SM occupancy remains below optimal, indicating ample headroom for future engine-level optimization (e.g., kernel fusion, graph capture, improved scheduling, compute / communication overlap). Even in this preliminary state, the implementation already delivers substantial performance gains.

+
+ +

Superior Efficiency Frontier

+

Beyond raw numbers, D2F operates on a significantly better throughput-performance curve. The graph below shows that for any given performance level, D2F is drastically faster. For instance, on GSM8K, our model achieves 3.1x higher throughput than LLaMA3 while also attaining a better score.

+ +
+ Graph showing the throughput vs. performance trade-off for D2F and other models. +
+ Figure 5. The throughput-performance trade-off. D2F models (blue data points) establish a far more favorable efficiency curve. Unlike vanilla dLLMs, which see performance plummet at higher speeds, D2F maintains high scores while dramatically increasing throughput, outperforming AR baselines (stars) on both axes. +
+
+ +

Conclusion

+

Discrete Diffusion Forcing (D2F) represents a fundamental leap forward for diffusion-based language models. By creating a smart hybrid of autoregressive and diffusion principles, it overcomes the critical inference bottlenecks that have long held dLLMs back. For the first time, an open-source dLLM can not only match but significantly exceed the speed of highly optimized autoregressive models.

+

This breakthrough establishes dLLMs as a practical, high-performance alternative for a wide range of text generation tasks, paving the way for new applications and further research into efficient, parallel-first language modeling.

+ +

Citation

+

If you find our work useful, please consider citing our paper:

+
@article{wang2025diffusion,
+              title={Diffusion llms can do faster-than-ar inference via discrete diffusion forcing},
+              author={Wang, Xu and Xu, Chenkai and Jin, Yijie and Jin, Jiachun and Zhang, Hao and Deng, Zhijie},
+              journal={arXiv preprint arXiv:2508.09192},
+              year={2025}
+            }
+
+
+ + + diff --git a/Discrete-Diffusion-Forcing/pyproject.toml b/Discrete-Diffusion-Forcing/pyproject.toml new file mode 100644 index 0000000000000000000000000000000000000000..c4fa476ca4c64ff07411b6be3f8bb444809a689c --- /dev/null +++ b/Discrete-Diffusion-Forcing/pyproject.toml @@ -0,0 +1,17 @@ +[project] +name = "discrete-diffusion-forcing" +version = "0.1.0" +description = "Discrete Diffusion Forcing (D2F): dLLMs Can Do Faster-Than-AR Inference" +readme = "README.md" +requires-python = ">=3.12" +dependencies = [ + "transformers==4.49.0", + "lm_eval==0.4.8", + "accelerate==0.34.2", + "antlr4-python3-runtime==4.11", + "math_verify", + "sympy", + "hf_xet", + "pydantic==2.10.6", + "gradio", +] diff --git a/Discrete-Diffusion-Forcing/requirements.txt b/Discrete-Diffusion-Forcing/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..f75bf873eecace307865deea4b7a470fb7c243f1 --- /dev/null +++ b/Discrete-Diffusion-Forcing/requirements.txt @@ -0,0 +1,9 @@ +transformers==4.49.0 +lm_eval==0.4.8 +accelerate==0.34.2 +antlr4-python3-runtime==4.11 +math_verify +sympy +hf_xet +pydantic==2.10.6 +gradio \ No newline at end of file diff --git a/Discrete-Diffusion-Forcing/uv.lock b/Discrete-Diffusion-Forcing/uv.lock new file mode 100644 index 0000000000000000000000000000000000000000..ddd15a62e13d4658b5fd905be1acca854a2463b8 --- /dev/null +++ b/Discrete-Diffusion-Forcing/uv.lock @@ -0,0 +1,2493 @@ +version = 1 +revision = 2 +requires-python = ">=3.12" +resolution-markers = [ + "python_full_version >= '3.13'", + "python_full_version < '3.13'", +] + +[[package]] +name = "absl-py" +version = "2.3.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/10/2a/c93173ffa1b39c1d0395b7e842bbdc62e556ca9d8d3b5572926f3e4ca752/absl_py-2.3.1.tar.gz", hash = "sha256:a97820526f7fbfd2ec1bce83f3f25e3a14840dac0d8e02a0b71cd75db3f77fc9", size = 116588, upload-time = "2025-07-03T09:31:44.05Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8f/aa/ba0014cc4659328dc818a28827be78e6d97312ab0cb98105a770924dc11e/absl_py-2.3.1-py3-none-any.whl", hash = "sha256:eeecf07f0c2a93ace0772c92e596ace6d3d3996c042b2128459aaae2a76de11d", size = 135811, upload-time = "2025-07-03T09:31:42.253Z" }, +] + +[[package]] +name = "accelerate" +version = "0.34.2" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "huggingface-hub" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "psutil" }, + { name = "pyyaml" }, + { name = "safetensors" }, + { name = "torch" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4d/82/5712c44a5a5ef7c4d375363b179a099e834491221ece02e81a1209b08233/accelerate-0.34.2.tar.gz", hash = "sha256:98c1ebe1f5a45c0a3af02dc60b5bb8b7d58d60c3326a326a06ce6d956b18ca5b", size = 328806, upload-time = "2024-09-05T16:45:20.816Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b0/5e/80cee674cdbe529ef008721d7eebb50ae5def4314211d82123aa23e828f8/accelerate-0.34.2-py3-none-any.whl", hash = "sha256:d69159e2c4e4a473d14443b27d2d732929254e826b3ab4813b3785b5ac616c7c", size = 324366, upload-time = "2024-09-05T16:45:17.121Z" }, +] + +[[package]] +name = "aiofiles" +version = "24.1.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0b/03/a88171e277e8caa88a4c77808c20ebb04ba74cc4681bf1e9416c862de237/aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c", size = 30247, upload-time = "2024-06-24T11:02:03.584Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a5/45/30bb92d442636f570cb5651bc661f52b610e2eec3f891a5dc3a4c3667db0/aiofiles-24.1.0-py3-none-any.whl", hash = "sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5", size = 15896, upload-time = "2024-06-24T11:02:01.529Z" }, +] + +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" }, +] + +[[package]] +name = "aiohttp" +version = "3.12.15" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "yarl" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9b/e7/d92a237d8802ca88483906c388f7c201bbe96cd80a165ffd0ac2f6a8d59f/aiohttp-3.12.15.tar.gz", hash = "sha256:4fc61385e9c98d72fcdf47e6dd81833f47b2f77c114c29cd64a361be57a763a2", size = 7823716, upload-time = "2025-07-29T05:52:32.215Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/63/97/77cb2450d9b35f517d6cf506256bf4f5bda3f93a66b4ad64ba7fc917899c/aiohttp-3.12.15-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:802d3868f5776e28f7bf69d349c26fc0efadb81676d0afa88ed00d98a26340b7", size = 702333, upload-time = "2025-07-29T05:50:46.507Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/83/6d/0544e6b08b748682c30b9f65640d006e51f90763b41d7c546693bc22900d/aiohttp-3.12.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2800614cd560287be05e33a679638e586a2d7401f4ddf99e304d98878c29444", size = 476948, upload-time = "2025-07-29T05:50:48.067Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3a/1d/c8c40e611e5094330284b1aea8a4b02ca0858f8458614fa35754cab42b9c/aiohttp-3.12.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8466151554b593909d30a0a125d638b4e5f3836e5aecde85b66b80ded1cb5b0d", size = 469787, upload-time = "2025-07-29T05:50:49.669Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/38/7d/b76438e70319796bfff717f325d97ce2e9310f752a267bfdf5192ac6082b/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e5a495cb1be69dae4b08f35a6c4579c539e9b5706f606632102c0f855bcba7c", size = 1716590, upload-time = "2025-07-29T05:50:51.368Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/79/b1/60370d70cdf8b269ee1444b390cbd72ce514f0d1cd1a715821c784d272c9/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6404dfc8cdde35c69aaa489bb3542fb86ef215fc70277c892be8af540e5e21c0", size = 1699241, upload-time = "2025-07-29T05:50:53.628Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a3/2b/4968a7b8792437ebc12186db31523f541943e99bda8f30335c482bea6879/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3ead1c00f8521a5c9070fcb88f02967b1d8a0544e6d85c253f6968b785e1a2ab", size = 1754335, upload-time = "2025-07-29T05:50:55.394Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fb/c1/49524ed553f9a0bec1a11fac09e790f49ff669bcd14164f9fab608831c4d/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6990ef617f14450bc6b34941dba4f12d5613cbf4e33805932f853fbd1cf18bfb", size = 1800491, upload-time = "2025-07-29T05:50:57.202Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/de/5e/3bf5acea47a96a28c121b167f5ef659cf71208b19e52a88cdfa5c37f1fcc/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd736ed420f4db2b8148b52b46b88ed038d0354255f9a73196b7bbce3ea97545", size = 1719929, upload-time = "2025-07-29T05:50:59.192Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/39/94/8ae30b806835bcd1cba799ba35347dee6961a11bd507db634516210e91d8/aiohttp-3.12.15-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c5092ce14361a73086b90c6efb3948ffa5be2f5b6fbcf52e8d8c8b8848bb97c", size = 1635733, upload-time = "2025-07-29T05:51:01.394Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7a/46/06cdef71dd03acd9da7f51ab3a9107318aee12ad38d273f654e4f981583a/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aaa2234bb60c4dbf82893e934d8ee8dea30446f0647e024074237a56a08c01bd", size = 1696790, upload-time = "2025-07-29T05:51:03.657Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/02/90/6b4cfaaf92ed98d0ec4d173e78b99b4b1a7551250be8937d9d67ecb356b4/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6d86a2fbdd14192e2f234a92d3b494dd4457e683ba07e5905a0b3ee25389ac9f", size = 1718245, upload-time = "2025-07-29T05:51:05.911Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2e/e6/2593751670fa06f080a846f37f112cbe6f873ba510d070136a6ed46117c6/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a041e7e2612041a6ddf1c6a33b883be6a421247c7afd47e885969ee4cc58bd8d", size = 1658899, upload-time = "2025-07-29T05:51:07.753Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8f/28/c15bacbdb8b8eb5bf39b10680d129ea7410b859e379b03190f02fa104ffd/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5015082477abeafad7203757ae44299a610e89ee82a1503e3d4184e6bafdd519", size = 1738459, upload-time = "2025-07-29T05:51:09.56Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/00/de/c269cbc4faa01fb10f143b1670633a8ddd5b2e1ffd0548f7aa49cb5c70e2/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:56822ff5ddfd1b745534e658faba944012346184fbfe732e0d6134b744516eea", size = 1766434, upload-time = "2025-07-29T05:51:11.423Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/52/b0/4ff3abd81aa7d929b27d2e1403722a65fc87b763e3a97b3a2a494bfc63bc/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b2acbbfff69019d9014508c4ba0401822e8bae5a5fdc3b6814285b71231b60f3", size = 1726045, upload-time = "2025-07-29T05:51:13.689Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/71/16/949225a6a2dd6efcbd855fbd90cf476052e648fb011aa538e3b15b89a57a/aiohttp-3.12.15-cp312-cp312-win32.whl", hash = "sha256:d849b0901b50f2185874b9a232f38e26b9b3d4810095a7572eacea939132d4e1", size = 423591, upload-time = "2025-07-29T05:51:15.452Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2b/d8/fa65d2a349fe938b76d309db1a56a75c4fb8cc7b17a398b698488a939903/aiohttp-3.12.15-cp312-cp312-win_amd64.whl", hash = "sha256:b390ef5f62bb508a9d67cb3bba9b8356e23b3996da7062f1a57ce1a79d2b3d34", size = 450266, upload-time = "2025-07-29T05:51:17.239Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f2/33/918091abcf102e39d15aba2476ad9e7bd35ddb190dcdd43a854000d3da0d/aiohttp-3.12.15-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9f922ffd05034d439dde1c77a20461cf4a1b0831e6caa26151fe7aa8aaebc315", size = 696741, upload-time = "2025-07-29T05:51:19.021Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b5/2a/7495a81e39a998e400f3ecdd44a62107254803d1681d9189be5c2e4530cd/aiohttp-3.12.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2ee8a8ac39ce45f3e55663891d4b1d15598c157b4d494a4613e704c8b43112cd", size = 474407, upload-time = "2025-07-29T05:51:21.165Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/49/fc/a9576ab4be2dcbd0f73ee8675d16c707cfc12d5ee80ccf4015ba543480c9/aiohttp-3.12.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3eae49032c29d356b94eee45a3f39fdf4b0814b397638c2f718e96cfadf4c4e4", size = 466703, upload-time = "2025-07-29T05:51:22.948Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/09/2f/d4bcc8448cf536b2b54eed48f19682031ad182faa3a3fee54ebe5b156387/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97752ff12cc12f46a9b20327104448042fce5c33a624f88c18f66f9368091c7", size = 1705532, upload-time = "2025-07-29T05:51:25.211Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f1/f3/59406396083f8b489261e3c011aa8aee9df360a96ac8fa5c2e7e1b8f0466/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:894261472691d6fe76ebb7fcf2e5870a2ac284c7406ddc95823c8598a1390f0d", size = 1686794, upload-time = "2025-07-29T05:51:27.145Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/dc/71/164d194993a8d114ee5656c3b7ae9c12ceee7040d076bf7b32fb98a8c5c6/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5fa5d9eb82ce98959fc1031c28198b431b4d9396894f385cb63f1e2f3f20ca6b", size = 1738865, upload-time = "2025-07-29T05:51:29.366Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1c/00/d198461b699188a93ead39cb458554d9f0f69879b95078dce416d3209b54/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0fa751efb11a541f57db59c1dd821bec09031e01452b2b6217319b3a1f34f3d", size = 1788238, upload-time = "2025-07-29T05:51:31.285Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/85/b8/9e7175e1fa0ac8e56baa83bf3c214823ce250d0028955dfb23f43d5e61fd/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5346b93e62ab51ee2a9d68e8f73c7cf96ffb73568a23e683f931e52450e4148d", size = 1710566, upload-time = "2025-07-29T05:51:33.219Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/59/e4/16a8eac9df39b48ae102ec030fa9f726d3570732e46ba0c592aeeb507b93/aiohttp-3.12.15-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:049ec0360f939cd164ecbfd2873eaa432613d5e77d6b04535e3d1fbae5a9e645", size = 1624270, upload-time = "2025-07-29T05:51:35.195Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1f/f8/cd84dee7b6ace0740908fd0af170f9fab50c2a41ccbc3806aabcb1050141/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b52dcf013b57464b6d1e51b627adfd69a8053e84b7103a7cd49c030f9ca44461", size = 1677294, upload-time = "2025-07-29T05:51:37.215Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ce/42/d0f1f85e50d401eccd12bf85c46ba84f947a84839c8a1c2c5f6e8ab1eb50/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9b2af240143dd2765e0fb661fd0361a1b469cab235039ea57663cda087250ea9", size = 1708958, upload-time = "2025-07-29T05:51:39.328Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d5/6b/f6fa6c5790fb602538483aa5a1b86fcbad66244997e5230d88f9412ef24c/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ac77f709a2cde2cc71257ab2d8c74dd157c67a0558a0d2799d5d571b4c63d44d", size = 1651553, upload-time = "2025-07-29T05:51:41.356Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/04/36/a6d36ad545fa12e61d11d1932eef273928b0495e6a576eb2af04297fdd3c/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:47f6b962246f0a774fbd3b6b7be25d59b06fdb2f164cf2513097998fc6a29693", size = 1727688, upload-time = "2025-07-29T05:51:43.452Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/aa/c8/f195e5e06608a97a4e52c5d41c7927301bf757a8e8bb5bbf8cef6c314961/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:760fb7db442f284996e39cf9915a94492e1896baac44f06ae551974907922b64", size = 1761157, upload-time = "2025-07-29T05:51:45.643Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/05/6a/ea199e61b67f25ba688d3ce93f63b49b0a4e3b3d380f03971b4646412fc6/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad702e57dc385cae679c39d318def49aef754455f237499d5b99bea4ef582e51", size = 1710050, upload-time = "2025-07-29T05:51:48.203Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b4/2e/ffeb7f6256b33635c29dbed29a22a723ff2dd7401fff42ea60cf2060abfb/aiohttp-3.12.15-cp313-cp313-win32.whl", hash = "sha256:f813c3e9032331024de2eb2e32a88d86afb69291fbc37a3a3ae81cc9917fb3d0", size = 422647, upload-time = "2025-07-29T05:51:50.718Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1b/8e/78ee35774201f38d5e1ba079c9958f7629b1fd079459aea9467441dbfbf5/aiohttp-3.12.15-cp313-cp313-win_amd64.whl", hash = "sha256:1a649001580bdb37c6fdb1bebbd7e3bc688e8ec2b5c6f52edbb664662b17dc84", size = 449067, upload-time = "2025-07-29T05:51:52.549Z" }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "frozenlist" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "antlr4-python3-runtime" +version = "4.11.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a4/1b/59dc12d9816edee8be6a166be7c220910ff18cce2ded9877a08b27396a83/antlr4-python3-runtime-4.11.0.tar.gz", hash = "sha256:6a46d4f6033c8d7e53c8975cee5d83b3ba1b48157f6beb09a4965062dacfe2e0", size = 116951, upload-time = "2022-09-04T19:49:27.321Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/37/f3/2cab1ffe441ffcc4605bf638f524fed86db6699a25d04bbecb5bfdde372b/antlr4_python3_runtime-4.11.0-py3-none-any.whl", hash = "sha256:f523f91387283045f129c343b363a8dda3a07eea62721b8eda95f2d8b817b656", size = 144189, upload-time = "2022-09-04T19:49:25.198Z" }, +] + +[[package]] +name = "anyio" +version = "4.10.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "idna" }, + { name = "sniffio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f1/b4/636b3b65173d3ce9a38ef5f0522789614e590dab6a8d505340a4efe4c567/anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6", size = 213252, upload-time = "2025-08-04T08:54:26.451Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6f/12/e5e0282d673bb9746bacfb6e2dba8719989d3660cdb2ea79aee9a9651afb/anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1", size = 107213, upload-time = "2025-08-04T08:54:24.882Z" }, +] + +[[package]] +name = "attrs" +version = "25.3.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, +] + +[[package]] +name = "audioop-lts" +version = "0.2.2" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/38/53/946db57842a50b2da2e0c1e34bd37f36f5aadba1a929a3971c5d7841dbca/audioop_lts-0.2.2.tar.gz", hash = "sha256:64d0c62d88e67b98a1a5e71987b7aa7b5bcffc7dcee65b635823dbdd0a8dbbd0", size = 30686, upload-time = "2025-08-05T16:43:17.409Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/de/d4/94d277ca941de5a507b07f0b592f199c22454eeaec8f008a286b3fbbacd6/audioop_lts-0.2.2-cp313-abi3-macosx_10_13_universal2.whl", hash = "sha256:fd3d4602dc64914d462924a08c1a9816435a2155d74f325853c1f1ac3b2d9800", size = 46523, upload-time = "2025-08-05T16:42:20.836Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f8/5a/656d1c2da4b555920ce4177167bfeb8623d98765594af59702c8873f60ec/audioop_lts-0.2.2-cp313-abi3-macosx_10_13_x86_64.whl", hash = "sha256:550c114a8df0aafe9a05442a1162dfc8fec37e9af1d625ae6060fed6e756f303", size = 27455, upload-time = "2025-08-05T16:42:22.283Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1b/83/ea581e364ce7b0d41456fb79d6ee0ad482beda61faf0cab20cbd4c63a541/audioop_lts-0.2.2-cp313-abi3-macosx_11_0_arm64.whl", hash = "sha256:9a13dc409f2564de15dd68be65b462ba0dde01b19663720c68c1140c782d1d75", size = 26997, upload-time = "2025-08-05T16:42:23.849Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b8/3b/e8964210b5e216e5041593b7d33e97ee65967f17c282e8510d19c666dab4/audioop_lts-0.2.2-cp313-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:51c916108c56aa6e426ce611946f901badac950ee2ddaf302b7ed35d9958970d", size = 85844, upload-time = "2025-08-05T16:42:25.208Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c7/2e/0a1c52faf10d51def20531a59ce4c706cb7952323b11709e10de324d6493/audioop_lts-0.2.2-cp313-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:47eba38322370347b1c47024defbd36374a211e8dd5b0dcbce7b34fdb6f8847b", size = 85056, upload-time = "2025-08-05T16:42:26.559Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/75/e8/cd95eef479656cb75ab05dfece8c1f8c395d17a7c651d88f8e6e291a63ab/audioop_lts-0.2.2-cp313-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba7c3a7e5f23e215cb271516197030c32aef2e754252c4c70a50aaff7031a2c8", size = 93892, upload-time = "2025-08-05T16:42:27.902Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5c/1e/a0c42570b74f83efa5cca34905b3eef03f7ab09fe5637015df538a7f3345/audioop_lts-0.2.2-cp313-abi3-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:def246fe9e180626731b26e89816e79aae2276f825420a07b4a647abaa84becc", size = 96660, upload-time = "2025-08-05T16:42:28.9Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/50/d5/8a0ae607ca07dbb34027bac8db805498ee7bfecc05fd2c148cc1ed7646e7/audioop_lts-0.2.2-cp313-abi3-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e160bf9df356d841bb6c180eeeea1834085464626dc1b68fa4e1d59070affdc3", size = 79143, upload-time = "2025-08-05T16:42:29.929Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/12/17/0d28c46179e7910bfb0bb62760ccb33edb5de973052cb2230b662c14ca2e/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4b4cd51a57b698b2d06cb9993b7ac8dfe89a3b2878e96bc7948e9f19ff51dba6", size = 84313, upload-time = "2025-08-05T16:42:30.949Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/84/ba/bd5d3806641564f2024e97ca98ea8f8811d4e01d9b9f9831474bc9e14f9e/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:4a53aa7c16a60a6857e6b0b165261436396ef7293f8b5c9c828a3a203147ed4a", size = 93044, upload-time = "2025-08-05T16:42:31.959Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f9/5e/435ce8d5642f1f7679540d1e73c1c42d933331c0976eb397d1717d7f01a3/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:3fc38008969796f0f689f1453722a0f463da1b8a6fbee11987830bfbb664f623", size = 78766, upload-time = "2025-08-05T16:42:33.302Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ae/3b/b909e76b606cbfd53875693ec8c156e93e15a1366a012f0b7e4fb52d3c34/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_s390x.whl", hash = "sha256:15ab25dd3e620790f40e9ead897f91e79c0d3ce65fe193c8ed6c26cffdd24be7", size = 87640, upload-time = "2025-08-05T16:42:34.854Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/30/e7/8f1603b4572d79b775f2140d7952f200f5e6c62904585d08a01f0a70393a/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:03f061a1915538fd96272bac9551841859dbb2e3bf73ebe4a23ef043766f5449", size = 86052, upload-time = "2025-08-05T16:42:35.839Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b5/96/c37846df657ccdda62ba1ae2b6534fa90e2e1b1742ca8dcf8ebd38c53801/audioop_lts-0.2.2-cp313-abi3-win32.whl", hash = "sha256:3bcddaaf6cc5935a300a8387c99f7a7fbbe212a11568ec6cf6e4bc458c048636", size = 26185, upload-time = "2025-08-05T16:42:37.04Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/34/a5/9d78fdb5b844a83da8a71226c7bdae7cc638861085fff7a1d707cb4823fa/audioop_lts-0.2.2-cp313-abi3-win_amd64.whl", hash = "sha256:a2c2a947fae7d1062ef08c4e369e0ba2086049a5e598fda41122535557012e9e", size = 30503, upload-time = "2025-08-05T16:42:38.427Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/34/25/20d8fde083123e90c61b51afb547bb0ea7e77bab50d98c0ab243d02a0e43/audioop_lts-0.2.2-cp313-abi3-win_arm64.whl", hash = "sha256:5f93a5db13927a37d2d09637ccca4b2b6b48c19cd9eda7b17a2e9f77edee6a6f", size = 24173, upload-time = "2025-08-05T16:42:39.704Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/58/a7/0a764f77b5c4ac58dc13c01a580f5d32ae8c74c92020b961556a43e26d02/audioop_lts-0.2.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:73f80bf4cd5d2ca7814da30a120de1f9408ee0619cc75da87d0641273d202a09", size = 47096, upload-time = "2025-08-05T16:42:40.684Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/aa/ed/ebebedde1a18848b085ad0fa54b66ceb95f1f94a3fc04f1cd1b5ccb0ed42/audioop_lts-0.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:106753a83a25ee4d6f473f2be6b0966fc1c9af7e0017192f5531a3e7463dce58", size = 27748, upload-time = "2025-08-05T16:42:41.992Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cb/6e/11ca8c21af79f15dbb1c7f8017952ee8c810c438ce4e2b25638dfef2b02c/audioop_lts-0.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fbdd522624141e40948ab3e8cdae6e04c748d78710e9f0f8d4dae2750831de19", size = 27329, upload-time = "2025-08-05T16:42:42.987Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/84/52/0022f93d56d85eec5da6b9da6a958a1ef09e80c39f2cc0a590c6af81dcbb/audioop_lts-0.2.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:143fad0311e8209ece30a8dbddab3b65ab419cbe8c0dde6e8828da25999be911", size = 92407, upload-time = "2025-08-05T16:42:44.336Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/87/1d/48a889855e67be8718adbc7a01f3c01d5743c325453a5e81cf3717664aad/audioop_lts-0.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dfbbc74ec68a0fd08cfec1f4b5e8cca3d3cd7de5501b01c4b5d209995033cde9", size = 91811, upload-time = "2025-08-05T16:42:45.325Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/98/a6/94b7213190e8077547ffae75e13ed05edc488653c85aa5c41472c297d295/audioop_lts-0.2.2-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cfcac6aa6f42397471e4943e0feb2244549db5c5d01efcd02725b96af417f3fe", size = 100470, upload-time = "2025-08-05T16:42:46.468Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e9/e9/78450d7cb921ede0cfc33426d3a8023a3bda755883c95c868ee36db8d48d/audioop_lts-0.2.2-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:752d76472d9804ac60f0078c79cdae8b956f293177acd2316cd1e15149aee132", size = 103878, upload-time = "2025-08-05T16:42:47.576Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4f/e2/cd5439aad4f3e34ae1ee852025dc6aa8f67a82b97641e390bf7bd9891d3e/audioop_lts-0.2.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:83c381767e2cc10e93e40281a04852facc4cd9334550e0f392f72d1c0a9c5753", size = 84867, upload-time = "2025-08-05T16:42:49.003Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/68/4b/9d853e9076c43ebba0d411e8d2aa19061083349ac695a7d082540bad64d0/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c0022283e9556e0f3643b7c3c03f05063ca72b3063291834cca43234f20c60bb", size = 90001, upload-time = "2025-08-05T16:42:50.038Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/58/26/4bae7f9d2f116ed5593989d0e521d679b0d583973d203384679323d8fa85/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:a2d4f1513d63c795e82948e1305f31a6d530626e5f9f2605408b300ae6095093", size = 99046, upload-time = "2025-08-05T16:42:51.111Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b2/67/a9f4fb3e250dda9e9046f8866e9fa7d52664f8985e445c6b4ad6dfb55641/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:c9c8e68d8b4a56fda8c025e538e639f8c5953f5073886b596c93ec9b620055e7", size = 84788, upload-time = "2025-08-05T16:42:52.198Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/70/f7/3de86562db0121956148bcb0fe5b506615e3bcf6e63c4357a612b910765a/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:96f19de485a2925314f5020e85911fb447ff5fbef56e8c7c6927851b95533a1c", size = 94472, upload-time = "2025-08-05T16:42:53.59Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f1/32/fd772bf9078ae1001207d2df1eef3da05bea611a87dd0e8217989b2848fa/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e541c3ef484852ef36545f66209444c48b28661e864ccadb29daddb6a4b8e5f5", size = 92279, upload-time = "2025-08-05T16:42:54.632Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4f/41/affea7181592ab0ab560044632571a38edaf9130b84928177823fbf3176a/audioop_lts-0.2.2-cp313-cp313t-win32.whl", hash = "sha256:d5e73fa573e273e4f2e5ff96f9043858a5e9311e94ffefd88a3186a910c70917", size = 26568, upload-time = "2025-08-05T16:42:55.627Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/28/2b/0372842877016641db8fc54d5c88596b542eec2f8f6c20a36fb6612bf9ee/audioop_lts-0.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9191d68659eda01e448188f60364c7763a7ca6653ed3f87ebb165822153a8547", size = 30942, upload-time = "2025-08-05T16:42:56.674Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ee/ca/baf2b9cc7e96c179bb4a54f30fcd83e6ecb340031bde68f486403f943768/audioop_lts-0.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:c174e322bb5783c099aaf87faeb240c8d210686b04bd61dfd05a8e5a83d88969", size = 24603, upload-time = "2025-08-05T16:42:57.571Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5c/73/413b5a2804091e2c7d5def1d618e4837f1cb82464e230f827226278556b7/audioop_lts-0.2.2-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:f9ee9b52f5f857fbaf9d605a360884f034c92c1c23021fb90b2e39b8e64bede6", size = 47104, upload-time = "2025-08-05T16:42:58.518Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ae/8c/daa3308dc6593944410c2c68306a5e217f5c05b70a12e70228e7dd42dc5c/audioop_lts-0.2.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:49ee1a41738a23e98d98b937a0638357a2477bc99e61b0f768a8f654f45d9b7a", size = 27754, upload-time = "2025-08-05T16:43:00.132Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4e/86/c2e0f627168fcf61781a8f72cab06b228fe1da4b9fa4ab39cfb791b5836b/audioop_lts-0.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5b00be98ccd0fc123dcfad31d50030d25fcf31488cde9e61692029cd7394733b", size = 27332, upload-time = "2025-08-05T16:43:01.666Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c7/bd/35dce665255434f54e5307de39e31912a6f902d4572da7c37582809de14f/audioop_lts-0.2.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a6d2e0f9f7a69403e388894d4ca5ada5c47230716a03f2847cfc7bd1ecb589d6", size = 92396, upload-time = "2025-08-05T16:43:02.991Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2d/d2/deeb9f51def1437b3afa35aeb729d577c04bcd89394cb56f9239a9f50b6f/audioop_lts-0.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f9b0b8a03ef474f56d1a842af1a2e01398b8f7654009823c6d9e0ecff4d5cfbf", size = 91811, upload-time = "2025-08-05T16:43:04.096Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/76/3b/09f8b35b227cee28cc8231e296a82759ed80c1a08e349811d69773c48426/audioop_lts-0.2.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2b267b70747d82125f1a021506565bdc5609a2b24bcb4773c16d79d2bb260bbd", size = 100483, upload-time = "2025-08-05T16:43:05.085Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0b/15/05b48a935cf3b130c248bfdbdea71ce6437f5394ee8533e0edd7cfd93d5e/audioop_lts-0.2.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0337d658f9b81f4cd0fdb1f47635070cc084871a3d4646d9de74fdf4e7c3d24a", size = 103885, upload-time = "2025-08-05T16:43:06.197Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/83/80/186b7fce6d35b68d3d739f228dc31d60b3412105854edb975aa155a58339/audioop_lts-0.2.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:167d3b62586faef8b6b2275c3218796b12621a60e43f7e9d5845d627b9c9b80e", size = 84899, upload-time = "2025-08-05T16:43:07.291Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/49/89/c78cc5ac6cb5828f17514fb12966e299c850bc885e80f8ad94e38d450886/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0d9385e96f9f6da847f4d571ce3cb15b5091140edf3db97276872647ce37efd7", size = 89998, upload-time = "2025-08-05T16:43:08.335Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4c/4b/6401888d0c010e586c2ca50fce4c903d70a6bb55928b16cfbdfd957a13da/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:48159d96962674eccdca9a3df280e864e8ac75e40a577cc97c5c42667ffabfc5", size = 99046, upload-time = "2025-08-05T16:43:09.367Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/de/f8/c874ca9bb447dae0e2ef2e231f6c4c2b0c39e31ae684d2420b0f9e97ee68/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8fefe5868cd082db1186f2837d64cfbfa78b548ea0d0543e9b28935ccce81ce9", size = 84843, upload-time = "2025-08-05T16:43:10.749Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3e/c0/0323e66f3daebc13fd46b36b30c3be47e3fc4257eae44f1e77eb828c703f/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:58cf54380c3884fb49fdd37dfb7a772632b6701d28edd3e2904743c5e1773602", size = 94490, upload-time = "2025-08-05T16:43:12.131Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/98/6b/acc7734ac02d95ab791c10c3f17ffa3584ccb9ac5c18fd771c638ed6d1f5/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:088327f00488cdeed296edd9215ca159f3a5a5034741465789cad403fcf4bec0", size = 92297, upload-time = "2025-08-05T16:43:13.139Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/13/c3/c3dc3f564ce6877ecd2a05f8d751b9b27a8c320c2533a98b0c86349778d0/audioop_lts-0.2.2-cp314-cp314t-win32.whl", hash = "sha256:068aa17a38b4e0e7de771c62c60bbca2455924b67a8814f3b0dee92b5820c0b3", size = 27331, upload-time = "2025-08-05T16:43:14.19Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/72/bb/b4608537e9ffcb86449091939d52d24a055216a36a8bf66b936af8c3e7ac/audioop_lts-0.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:a5bf613e96f49712073de86f20dbdd4014ca18efd4d34ed18c75bd808337851b", size = 31697, upload-time = "2025-08-05T16:43:15.193Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f6/22/91616fe707a5c5510de2cac9b046a30defe7007ba8a0c04f9c08f27df312/audioop_lts-0.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:b492c3b040153e68b9fdaff5913305aaaba5bb433d8a7f73d5cf6a64ed3cc1dd", size = 25206, upload-time = "2025-08-05T16:43:16.444Z" }, +] + +[[package]] +name = "brotli" +version = "1.1.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2f/c2/f9e977608bdf958650638c3f1e28f85a1b075f075ebbe77db8555463787b/Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724", size = 7372270, upload-time = "2023-09-07T14:05:41.643Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5c/d0/5373ae13b93fe00095a58efcbce837fd470ca39f703a235d2a999baadfbc/Brotli-1.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:32d95b80260d79926f5fab3c41701dbb818fde1c9da590e77e571eefd14abe28", size = 815693, upload-time = "2024-10-18T12:32:23.824Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8e/48/f6e1cdf86751300c288c1459724bfa6917a80e30dbfc326f92cea5d3683a/Brotli-1.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b760c65308ff1e462f65d69c12e4ae085cff3b332d894637f6273a12a482d09f", size = 422489, upload-time = "2024-10-18T12:32:25.641Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/06/88/564958cedce636d0f1bed313381dfc4b4e3d3f6015a63dae6146e1b8c65c/Brotli-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:316cc9b17edf613ac76b1f1f305d2a748f1b976b033b049a6ecdfd5612c70409", size = 873081, upload-time = "2023-09-07T14:03:57.967Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/58/79/b7026a8bb65da9a6bb7d14329fd2bd48d2b7f86d7329d5cc8ddc6a90526f/Brotli-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:caf9ee9a5775f3111642d33b86237b05808dafcd6268faa492250e9b78046eb2", size = 446244, upload-time = "2023-09-07T14:03:59.319Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e5/18/c18c32ecea41b6c0004e15606e274006366fe19436b6adccc1ae7b2e50c2/Brotli-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70051525001750221daa10907c77830bc889cb6d865cc0b813d9db7fefc21451", size = 2906505, upload-time = "2023-09-07T14:04:01.327Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/08/c8/69ec0496b1ada7569b62d85893d928e865df29b90736558d6c98c2031208/Brotli-1.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f4bf76817c14aa98cc6697ac02f3972cb8c3da93e9ef16b9c66573a68014f91", size = 2944152, upload-time = "2023-09-07T14:04:03.033Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ab/fb/0517cea182219d6768113a38167ef6d4eb157a033178cc938033a552ed6d/Brotli-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0c5516f0aed654134a2fc936325cc2e642f8a0e096d075209672eb321cff408", size = 2919252, upload-time = "2023-09-07T14:04:04.675Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c7/53/73a3431662e33ae61a5c80b1b9d2d18f58dfa910ae8dd696e57d39f1a2f5/Brotli-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c3020404e0b5eefd7c9485ccf8393cfb75ec38ce75586e046573c9dc29967a0", size = 2845955, upload-time = "2023-09-07T14:04:06.585Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/55/ac/bd280708d9c5ebdbf9de01459e625a3e3803cce0784f47d633562cf40e83/Brotli-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4ed11165dd45ce798d99a136808a794a748d5dc38511303239d4e2363c0695dc", size = 2914304, upload-time = "2023-09-07T14:04:08.668Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/76/58/5c391b41ecfc4527d2cc3350719b02e87cb424ef8ba2023fb662f9bf743c/Brotli-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4093c631e96fdd49e0377a9c167bfd75b6d0bad2ace734c6eb20b348bc3ea180", size = 2814452, upload-time = "2023-09-07T14:04:10.736Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c7/4e/91b8256dfe99c407f174924b65a01f5305e303f486cc7a2e8a5d43c8bec3/Brotli-1.1.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e4c4629ddad63006efa0ef968c8e4751c5868ff0b1c5c40f76524e894c50248", size = 2938751, upload-time = "2023-09-07T14:04:12.875Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5a/a6/e2a39a5d3b412938362bbbeba5af904092bf3f95b867b4a3eb856104074e/Brotli-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:861bf317735688269936f755fa136a99d1ed526883859f86e41a5d43c61d8966", size = 2933757, upload-time = "2023-09-07T14:04:14.551Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/13/f0/358354786280a509482e0e77c1a5459e439766597d280f28cb097642fc26/Brotli-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:87a3044c3a35055527ac75e419dfa9f4f3667a1e887ee80360589eb8c90aabb9", size = 2936146, upload-time = "2024-10-18T12:32:27.257Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/80/f7/daf538c1060d3a88266b80ecc1d1c98b79553b3f117a485653f17070ea2a/Brotli-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c5529b34c1c9d937168297f2c1fde7ebe9ebdd5e121297ff9c043bdb2ae3d6fb", size = 2848055, upload-time = "2024-10-18T12:32:29.376Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ad/cf/0eaa0585c4077d3c2d1edf322d8e97aabf317941d3a72d7b3ad8bce004b0/Brotli-1.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ca63e1890ede90b2e4454f9a65135a4d387a4585ff8282bb72964fab893f2111", size = 3035102, upload-time = "2024-10-18T12:32:31.371Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d8/63/1c1585b2aa554fe6dbce30f0c18bdbc877fa9a1bf5ff17677d9cca0ac122/Brotli-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e79e6520141d792237c70bcd7a3b122d00f2613769ae0cb61c52e89fd3443839", size = 2930029, upload-time = "2024-10-18T12:32:33.293Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5f/3b/4e3fd1893eb3bbfef8e5a80d4508bec17a57bb92d586c85c12d28666bb13/Brotli-1.1.0-cp312-cp312-win32.whl", hash = "sha256:5f4d5ea15c9382135076d2fb28dde923352fe02951e66935a9efaac8f10e81b0", size = 333276, upload-time = "2023-09-07T14:04:16.49Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3d/d5/942051b45a9e883b5b6e98c041698b1eb2012d25e5948c58d6bf85b1bb43/Brotli-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:906bc3a79de8c4ae5b86d3d75a8b77e44404b0f4261714306e3ad248d8ab0951", size = 357255, upload-time = "2023-09-07T14:04:17.83Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0a/9f/fb37bb8ffc52a8da37b1c03c459a8cd55df7a57bdccd8831d500e994a0ca/Brotli-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8bf32b98b75c13ec7cf774164172683d6e7891088f6316e54425fde1efc276d5", size = 815681, upload-time = "2024-10-18T12:32:34.942Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/06/b3/dbd332a988586fefb0aa49c779f59f47cae76855c2d00f450364bb574cac/Brotli-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7bc37c4d6b87fb1017ea28c9508b36bbcb0c3d18b4260fcdf08b200c74a6aee8", size = 422475, upload-time = "2024-10-18T12:32:36.485Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bb/80/6aaddc2f63dbcf2d93c2d204e49c11a9ec93a8c7c63261e2b4bd35198283/Brotli-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c0ef38c7a7014ffac184db9e04debe495d317cc9c6fb10071f7fefd93100a4f", size = 2906173, upload-time = "2024-10-18T12:32:37.978Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ea/1d/e6ca79c96ff5b641df6097d299347507d39a9604bde8915e76bf026d6c77/Brotli-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91d7cc2a76b5567591d12c01f019dd7afce6ba8cba6571187e21e2fc418ae648", size = 2943803, upload-time = "2024-10-18T12:32:39.606Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ac/a3/d98d2472e0130b7dd3acdbb7f390d478123dbf62b7d32bda5c830a96116d/Brotli-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a93dde851926f4f2678e704fadeb39e16c35d8baebd5252c9fd94ce8ce68c4a0", size = 2918946, upload-time = "2024-10-18T12:32:41.679Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c4/a5/c69e6d272aee3e1423ed005d8915a7eaa0384c7de503da987f2d224d0721/Brotli-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0db75f47be8b8abc8d9e31bc7aad0547ca26f24a54e6fd10231d623f183d089", size = 2845707, upload-time = "2024-10-18T12:32:43.478Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/58/9f/4149d38b52725afa39067350696c09526de0125ebfbaab5acc5af28b42ea/Brotli-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6967ced6730aed543b8673008b5a391c3b1076d834ca438bbd70635c73775368", size = 2936231, upload-time = "2024-10-18T12:32:45.224Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5a/5a/145de884285611838a16bebfdb060c231c52b8f84dfbe52b852a15780386/Brotli-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7eedaa5d036d9336c95915035fb57422054014ebdeb6f3b42eac809928e40d0c", size = 2848157, upload-time = "2024-10-18T12:32:46.894Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/50/ae/408b6bfb8525dadebd3b3dd5b19d631da4f7d46420321db44cd99dcf2f2c/Brotli-1.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d487f5432bf35b60ed625d7e1b448e2dc855422e87469e3f450aa5552b0eb284", size = 3035122, upload-time = "2024-10-18T12:32:48.844Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/af/85/a94e5cfaa0ca449d8f91c3d6f78313ebf919a0dbd55a100c711c6e9655bc/Brotli-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:832436e59afb93e1836081a20f324cb185836c617659b07b129141a8426973c7", size = 2930206, upload-time = "2024-10-18T12:32:51.198Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c2/f0/a61d9262cd01351df22e57ad7c34f66794709acab13f34be2675f45bf89d/Brotli-1.1.0-cp313-cp313-win32.whl", hash = "sha256:43395e90523f9c23a3d5bdf004733246fba087f2948f87ab28015f12359ca6a0", size = 333804, upload-time = "2024-10-18T12:32:52.661Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7e/c1/ec214e9c94000d1c1974ec67ced1c970c148aa6b8d8373066123fc3dbf06/Brotli-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:9011560a466d2eb3f5a6e4929cf4a09be405c64154e12df0dd72713f6500e32b", size = 358517, upload-time = "2024-10-18T12:32:54.066Z" }, +] + +[[package]] +name = "certifi" +version = "2025.8.3" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" }, +] + +[[package]] +name = "cffi" +version = "1.17.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "pycparser" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621, upload-time = "2024-09-04T20:45:21.852Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178, upload-time = "2024-09-04T20:44:12.232Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840, upload-time = "2024-09-04T20:44:13.739Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803, upload-time = "2024-09-04T20:44:15.231Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850, upload-time = "2024-09-04T20:44:17.188Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729, upload-time = "2024-09-04T20:44:18.688Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256, upload-time = "2024-09-04T20:44:20.248Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424, upload-time = "2024-09-04T20:44:21.673Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568, upload-time = "2024-09-04T20:44:23.245Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736, upload-time = "2024-09-04T20:44:24.757Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448, upload-time = "2024-09-04T20:44:26.208Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976, upload-time = "2024-09-04T20:44:27.578Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989, upload-time = "2024-09-04T20:44:28.956Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802, upload-time = "2024-09-04T20:44:30.289Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792, upload-time = "2024-09-04T20:44:32.01Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893, upload-time = "2024-09-04T20:44:33.606Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810, upload-time = "2024-09-04T20:44:35.191Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200, upload-time = "2024-09-04T20:44:36.743Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447, upload-time = "2024-09-04T20:44:38.492Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358, upload-time = "2024-09-04T20:44:40.046Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469, upload-time = "2024-09-04T20:44:41.616Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475, upload-time = "2024-09-04T20:44:43.733Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009, upload-time = "2024-09-04T20:44:45.309Z" }, +] + +[[package]] +name = "chardet" +version = "5.2.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f3/0d/f7b6ab21ec75897ed80c17d79b15951a719226b9fababf1e40ea74d69079/chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/38/6f/f5fbc992a329ee4e0f288c1fe0e2ad9485ed064cac731ed2fe47dcc38cbf/chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.3" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", size = 122371, upload-time = "2025-08-09T07:57:28.46Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e9/5e/14c94999e418d9b87682734589404a25854d5f5d0408df68bc15b6ff54bb/charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1", size = 205655, upload-time = "2025-08-09T07:56:08.475Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7d/a8/c6ec5d389672521f644505a257f50544c074cf5fc292d5390331cd6fc9c3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884", size = 146223, upload-time = "2025-08-09T07:56:09.708Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fc/eb/a2ffb08547f4e1e5415fb69eb7db25932c52a52bed371429648db4d84fb1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018", size = 159366, upload-time = "2025-08-09T07:56:11.326Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/82/10/0fd19f20c624b278dddaf83b8464dcddc2456cb4b02bb902a6da126b87a1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392", size = 157104, upload-time = "2025-08-09T07:56:13.014Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/16/ab/0233c3231af734f5dfcf0844aa9582d5a1466c985bbed6cedab85af9bfe3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f", size = 151830, upload-time = "2025-08-09T07:56:14.428Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ae/02/e29e22b4e02839a0e4a06557b1999d0a47db3567e82989b5bb21f3fbbd9f/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154", size = 148854, upload-time = "2025-08-09T07:56:16.051Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/05/6b/e2539a0a4be302b481e8cafb5af8792da8093b486885a1ae4d15d452bcec/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491", size = 160670, upload-time = "2025-08-09T07:56:17.314Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/31/e7/883ee5676a2ef217a40ce0bffcc3d0dfbf9e64cbcfbdf822c52981c3304b/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93", size = 158501, upload-time = "2025-08-09T07:56:18.641Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c1/35/6525b21aa0db614cf8b5792d232021dca3df7f90a1944db934efa5d20bb1/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f", size = 153173, upload-time = "2025-08-09T07:56:20.289Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/50/ee/f4704bad8201de513fdc8aac1cabc87e38c5818c93857140e06e772b5892/charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37", size = 99822, upload-time = "2025-08-09T07:56:21.551Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/39/f5/3b3836ca6064d0992c58c7561c6b6eee1b3892e9665d650c803bd5614522/charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc", size = 107543, upload-time = "2025-08-09T07:56:23.115Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/65/ca/2135ac97709b400c7654b4b764daf5c5567c2da45a30cdd20f9eefe2d658/charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", size = 205326, upload-time = "2025-08-09T07:56:24.721Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/71/11/98a04c3c97dd34e49c7d247083af03645ca3730809a5509443f3c37f7c99/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", size = 146008, upload-time = "2025-08-09T07:56:26.004Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/60/f5/4659a4cb3c4ec146bec80c32d8bb16033752574c20b1252ee842a95d1a1e/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", size = 159196, upload-time = "2025-08-09T07:56:27.25Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/86/9e/f552f7a00611f168b9a5865a1414179b2c6de8235a4fa40189f6f79a1753/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", size = 156819, upload-time = "2025-08-09T07:56:28.515Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7e/95/42aa2156235cbc8fa61208aded06ef46111c4d3f0de233107b3f38631803/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", size = 151350, upload-time = "2025-08-09T07:56:29.716Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c2/a9/3865b02c56f300a6f94fc631ef54f0a8a29da74fb45a773dfd3dcd380af7/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", size = 148644, upload-time = "2025-08-09T07:56:30.984Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/77/d9/cbcf1a2a5c7d7856f11e7ac2d782aec12bdfea60d104e60e0aa1c97849dc/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9", size = 160468, upload-time = "2025-08-09T07:56:32.252Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f6/42/6f45efee8697b89fda4d50580f292b8f7f9306cb2971d4b53f8914e4d890/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", size = 158187, upload-time = "2025-08-09T07:56:33.481Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/70/99/f1c3bdcfaa9c45b3ce96f70b14f070411366fa19549c1d4832c935d8e2c3/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", size = 152699, upload-time = "2025-08-09T07:56:34.739Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a3/ad/b0081f2f99a4b194bcbb1934ef3b12aa4d9702ced80a37026b7607c72e58/charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", size = 99580, upload-time = "2025-08-09T07:56:35.981Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9a/8f/ae790790c7b64f925e5c953b924aaa42a243fb778fed9e41f147b2a5715a/charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", size = 107366, upload-time = "2025-08-09T07:56:37.339Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8e/91/b5a06ad970ddc7a0e513112d40113e834638f4ca1120eb727a249fb2715e/charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", size = 204342, upload-time = "2025-08-09T07:56:38.687Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ce/ec/1edc30a377f0a02689342f214455c3f6c2fbedd896a1d2f856c002fc3062/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", size = 145995, upload-time = "2025-08-09T07:56:40.048Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/17/e5/5e67ab85e6d22b04641acb5399c8684f4d37caf7558a53859f0283a650e9/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", size = 158640, upload-time = "2025-08-09T07:56:41.311Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f1/e5/38421987f6c697ee3722981289d554957c4be652f963d71c5e46a262e135/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", size = 156636, upload-time = "2025-08-09T07:56:43.195Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a0/e4/5a075de8daa3ec0745a9a3b54467e0c2967daaaf2cec04c845f73493e9a1/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", size = 150939, upload-time = "2025-08-09T07:56:44.819Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/02/f7/3611b32318b30974131db62b4043f335861d4d9b49adc6d57c1149cc49d4/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", size = 148580, upload-time = "2025-08-09T07:56:46.684Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7e/61/19b36f4bd67f2793ab6a99b979b4e4f3d8fc754cbdffb805335df4337126/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", size = 159870, upload-time = "2025-08-09T07:56:47.941Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/06/57/84722eefdd338c04cf3030ada66889298eaedf3e7a30a624201e0cbe424a/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", size = 157797, upload-time = "2025-08-09T07:56:49.756Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/72/2a/aff5dd112b2f14bcc3462c312dce5445806bfc8ab3a7328555da95330e4b/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", size = 152224, upload-time = "2025-08-09T07:56:51.369Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b7/8c/9839225320046ed279c6e839d51f028342eb77c91c89b8ef2549f951f3ec/charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", size = 100086, upload-time = "2025-08-09T07:56:52.722Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ee/7a/36fbcf646e41f710ce0a563c1c9a343c6edf9be80786edeb15b6f62e17db/charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", size = 107400, upload-time = "2025-08-09T07:56:55.172Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" }, +] + +[[package]] +name = "click" +version = "8.2.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "dataproperty" +version = "1.1.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "mbstrdecoder" }, + { name = "typepy", extra = ["datetime"] }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0b/81/8c8b64ae873cb9014815214c07b63b12e3b18835780fb342223cfe3fe7d8/dataproperty-1.1.0.tar.gz", hash = "sha256:b038437a4097d1a1c497695c3586ea34bea67fdd35372b9a50f30bf044d77d04", size = 42574, upload-time = "2024-12-31T14:37:26.033Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/21/c2/e12e95e289e6081a40454199ab213139ef16a528c7c86432de545b05a23a/DataProperty-1.1.0-py3-none-any.whl", hash = "sha256:c61fcb2e2deca35e6d1eb1f251a7f22f0dcde63e80e61f0cc18c19f42abfd25b", size = 27581, upload-time = "2024-12-31T14:37:22.657Z" }, +] + +[[package]] +name = "datasets" +version = "4.0.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "dill" }, + { name = "filelock" }, + { name = "fsspec", extra = ["http"] }, + { name = "huggingface-hub" }, + { name = "multiprocess" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "pyarrow" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "tqdm" }, + { name = "xxhash" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e3/9d/348ed92110ba5f9b70b51ca1078d4809767a835aa2b7ce7e74ad2b98323d/datasets-4.0.0.tar.gz", hash = "sha256:9657e7140a9050db13443ba21cb5de185af8af944479b00e7ff1e00a61c8dbf1", size = 569566, upload-time = "2025-07-09T14:35:52.431Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/eb/62/eb8157afb21bd229c864521c1ab4fa8e9b4f1b06bafdd8c4668a7a31b5dd/datasets-4.0.0-py3-none-any.whl", hash = "sha256:7ef95e62025fd122882dbce6cb904c8cd3fbc829de6669a5eb939c77d50e203d", size = 494825, upload-time = "2025-07-09T14:35:50.658Z" }, +] + +[[package]] +name = "dill" +version = "0.3.8" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/17/4d/ac7ffa80c69ea1df30a8aa11b3578692a5118e7cd1aa157e3ef73b092d15/dill-0.3.8.tar.gz", hash = "sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca", size = 184847, upload-time = "2024-01-27T23:42:16.145Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c9/7a/cef76fd8438a42f96db64ddaa85280485a9c395e7df3db8158cfec1eee34/dill-0.3.8-py3-none-any.whl", hash = "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7", size = 116252, upload-time = "2024-01-27T23:42:14.239Z" }, +] + +[[package]] +name = "discrete-diffusion-forcing" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "accelerate" }, + { name = "antlr4-python3-runtime" }, + { name = "gradio" }, + { name = "hf-xet" }, + { name = "lm-eval" }, + { name = "math-verify" }, + { name = "pydantic" }, + { name = "sympy" }, + { name = "transformers" }, +] + +[package.metadata] +requires-dist = [ + { name = "accelerate", specifier = "==0.34.2" }, + { name = "antlr4-python3-runtime", specifier = "==4.11" }, + { name = "gradio" }, + { name = "hf-xet" }, + { name = "lm-eval", specifier = "==0.4.8" }, + { name = "math-verify" }, + { name = "pydantic", specifier = "==2.10.6" }, + { name = "sympy" }, + { name = "transformers", specifier = "==4.49.0" }, +] + +[[package]] +name = "evaluate" +version = "0.4.5" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "datasets" }, + { name = "dill" }, + { name = "fsspec", extra = ["http"] }, + { name = "huggingface-hub" }, + { name = "multiprocess" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "requests" }, + { name = "tqdm" }, + { name = "xxhash" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/34/26/52b2c01247481b87d4cbef8980293a7cf833e6f644b4106ce6b1c6c14ee2/evaluate-0.4.5.tar.gz", hash = "sha256:8c870c016d63899d45b3d9206f3365fd332836ad81b3f335e89ff618d93e0051", size = 65820, upload-time = "2025-07-10T13:26:46.099Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/27/7e/de4f71df5e2992c2cfea6314c80c8d51a8c7a5b345a36428a67ca154325e/evaluate-0.4.5-py3-none-any.whl", hash = "sha256:ab1528b8199af20fa8670cc5bf8e5d8443929dfa2e3d7483b458d8fdff6933d1", size = 84103, upload-time = "2025-07-10T13:26:44.685Z" }, +] + +[[package]] +name = "fastapi" +version = "0.116.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/78/d7/6c8b3bfe33eeffa208183ec037fee0cce9f7f024089ab1c5d12ef04bd27c/fastapi-0.116.1.tar.gz", hash = "sha256:ed52cbf946abfd70c5a0dccb24673f0670deeb517a88b3544d03c2a6bf283143", size = 296485, upload-time = "2025-07-11T16:22:32.057Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e5/47/d63c60f59a59467fda0f93f46335c9d18526d7071f025cb5b89d5353ea42/fastapi-0.116.1-py3-none-any.whl", hash = "sha256:c46ac7c312df840f0c9e220f7964bada936781bc4e2e6eb71f1c4d7553786565", size = 95631, upload-time = "2025-07-11T16:22:30.485Z" }, +] + +[[package]] +name = "ffmpy" +version = "0.6.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0b/f6/67cadf1686030be511004e75fa1c1397f8f193cd4d15d4788edef7c28621/ffmpy-0.6.1.tar.gz", hash = "sha256:b5830fd05f72bace05b8fb28724d54a7a63c5119d7f74ca36a75df33f749142d", size = 4958, upload-time = "2025-07-22T12:08:22.276Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/74/d4/1806897b31c480efc4e97c22506ac46c716084f573aef780bb7fb7a16e8a/ffmpy-0.6.1-py3-none-any.whl", hash = "sha256:69a37e2d7d6feb840e233d5640f3499a8b0a8657336774c86e4c52a3219222d4", size = 5512, upload-time = "2025-07-22T12:08:21.176Z" }, +] + +[[package]] +name = "filelock" +version = "3.18.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075, upload-time = "2025-03-14T07:11:40.47Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215, upload-time = "2025-03-14T07:11:39.145Z" }, +] + +[[package]] +name = "frozenlist" +version = "1.7.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/79/b1/b64018016eeb087db503b038296fd782586432b9c077fc5c7839e9cb6ef6/frozenlist-1.7.0.tar.gz", hash = "sha256:2e310d81923c2437ea8670467121cc3e9b0f76d3043cc1d2331d56c7fb7a3a8f", size = 45078, upload-time = "2025-06-09T23:02:35.538Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ef/a2/c8131383f1e66adad5f6ecfcce383d584ca94055a34d683bbb24ac5f2f1c/frozenlist-1.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3dbf9952c4bb0e90e98aec1bd992b3318685005702656bc6f67c1a32b76787f2", size = 81424, upload-time = "2025-06-09T23:00:42.24Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4c/9d/02754159955088cb52567337d1113f945b9e444c4960771ea90eb73de8db/frozenlist-1.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1f5906d3359300b8a9bb194239491122e6cf1444c2efb88865426f170c262cdb", size = 47952, upload-time = "2025-06-09T23:00:43.481Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/01/7a/0046ef1bd6699b40acd2067ed6d6670b4db2f425c56980fa21c982c2a9db/frozenlist-1.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3dabd5a8f84573c8d10d8859a50ea2dec01eea372031929871368c09fa103478", size = 46688, upload-time = "2025-06-09T23:00:44.793Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d6/a2/a910bafe29c86997363fb4c02069df4ff0b5bc39d33c5198b4e9dd42d8f8/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa57daa5917f1738064f302bf2626281a1cb01920c32f711fbc7bc36111058a8", size = 243084, upload-time = "2025-06-09T23:00:46.125Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/64/3e/5036af9d5031374c64c387469bfcc3af537fc0f5b1187d83a1cf6fab1639/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c193dda2b6d49f4c4398962810fa7d7c78f032bf45572b3e04dd5249dff27e08", size = 233524, upload-time = "2025-06-09T23:00:47.73Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/06/39/6a17b7c107a2887e781a48ecf20ad20f1c39d94b2a548c83615b5b879f28/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe2b675cf0aaa6d61bf8fbffd3c274b3c9b7b1623beb3809df8a81399a4a9c4", size = 248493, upload-time = "2025-06-09T23:00:49.742Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/be/00/711d1337c7327d88c44d91dd0f556a1c47fb99afc060ae0ef66b4d24793d/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8fc5d5cda37f62b262405cf9652cf0856839c4be8ee41be0afe8858f17f4c94b", size = 244116, upload-time = "2025-06-09T23:00:51.352Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/24/fe/74e6ec0639c115df13d5850e75722750adabdc7de24e37e05a40527ca539/frozenlist-1.7.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0d5ce521d1dd7d620198829b87ea002956e4319002ef0bc8d3e6d045cb4646e", size = 224557, upload-time = "2025-06-09T23:00:52.855Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8d/db/48421f62a6f77c553575201e89048e97198046b793f4a089c79a6e3268bd/frozenlist-1.7.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:488d0a7d6a0008ca0db273c542098a0fa9e7dfaa7e57f70acef43f32b3f69dca", size = 241820, upload-time = "2025-06-09T23:00:54.43Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1d/fa/cb4a76bea23047c8462976ea7b7a2bf53997a0ca171302deae9d6dd12096/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:15a7eaba63983d22c54d255b854e8108e7e5f3e89f647fc854bd77a237e767df", size = 236542, upload-time = "2025-06-09T23:00:56.409Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5d/32/476a4b5cfaa0ec94d3f808f193301debff2ea42288a099afe60757ef6282/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1eaa7e9c6d15df825bf255649e05bd8a74b04a4d2baa1ae46d9c2d00b2ca2cb5", size = 249350, upload-time = "2025-06-09T23:00:58.468Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8d/ba/9a28042f84a6bf8ea5dbc81cfff8eaef18d78b2a1ad9d51c7bc5b029ad16/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4389e06714cfa9d47ab87f784a7c5be91d3934cd6e9a7b85beef808297cc025", size = 225093, upload-time = "2025-06-09T23:01:00.015Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bc/29/3a32959e68f9cf000b04e79ba574527c17e8842e38c91d68214a37455786/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:73bd45e1488c40b63fe5a7df892baf9e2a4d4bb6409a2b3b78ac1c6236178e01", size = 245482, upload-time = "2025-06-09T23:01:01.474Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/80/e8/edf2f9e00da553f07f5fa165325cfc302dead715cab6ac8336a5f3d0adc2/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99886d98e1643269760e5fe0df31e5ae7050788dd288947f7f007209b8c33f08", size = 249590, upload-time = "2025-06-09T23:01:02.961Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1c/80/9a0eb48b944050f94cc51ee1c413eb14a39543cc4f760ed12657a5a3c45a/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:290a172aae5a4c278c6da8a96222e6337744cd9c77313efe33d5670b9f65fc43", size = 237785, upload-time = "2025-06-09T23:01:05.095Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f3/74/87601e0fb0369b7a2baf404ea921769c53b7ae00dee7dcfe5162c8c6dbf0/frozenlist-1.7.0-cp312-cp312-win32.whl", hash = "sha256:426c7bc70e07cfebc178bc4c2bf2d861d720c4fff172181eeb4a4c41d4ca2ad3", size = 39487, upload-time = "2025-06-09T23:01:06.54Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0b/15/c026e9a9fc17585a9d461f65d8593d281fedf55fbf7eb53f16c6df2392f9/frozenlist-1.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:563b72efe5da92e02eb68c59cb37205457c977aa7a449ed1b37e6939e5c47c6a", size = 43874, upload-time = "2025-06-09T23:01:07.752Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/24/90/6b2cebdabdbd50367273c20ff6b57a3dfa89bd0762de02c3a1eb42cb6462/frozenlist-1.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee80eeda5e2a4e660651370ebffd1286542b67e268aa1ac8d6dbe973120ef7ee", size = 79791, upload-time = "2025-06-09T23:01:09.368Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/83/2e/5b70b6a3325363293fe5fc3ae74cdcbc3e996c2a11dde2fd9f1fb0776d19/frozenlist-1.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d1a81c85417b914139e3a9b995d4a1c84559afc839a93cf2cb7f15e6e5f6ed2d", size = 47165, upload-time = "2025-06-09T23:01:10.653Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f4/25/a0895c99270ca6966110f4ad98e87e5662eab416a17e7fd53c364bf8b954/frozenlist-1.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cbb65198a9132ebc334f237d7b0df163e4de83fb4f2bdfe46c1e654bdb0c5d43", size = 45881, upload-time = "2025-06-09T23:01:12.296Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/19/7c/71bb0bbe0832793c601fff68cd0cf6143753d0c667f9aec93d3c323f4b55/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dab46c723eeb2c255a64f9dc05b8dd601fde66d6b19cdb82b2e09cc6ff8d8b5d", size = 232409, upload-time = "2025-06-09T23:01:13.641Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c0/45/ed2798718910fe6eb3ba574082aaceff4528e6323f9a8570be0f7028d8e9/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6aeac207a759d0dedd2e40745575ae32ab30926ff4fa49b1635def65806fddee", size = 225132, upload-time = "2025-06-09T23:01:15.264Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ba/e2/8417ae0f8eacb1d071d4950f32f229aa6bf68ab69aab797b72a07ea68d4f/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bd8c4e58ad14b4fa7802b8be49d47993182fdd4023393899632c88fd8cd994eb", size = 237638, upload-time = "2025-06-09T23:01:16.752Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f8/b7/2ace5450ce85f2af05a871b8c8719b341294775a0a6c5585d5e6170f2ce7/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04fb24d104f425da3540ed83cbfc31388a586a7696142004c577fa61c6298c3f", size = 233539, upload-time = "2025-06-09T23:01:18.202Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/46/b9/6989292c5539553dba63f3c83dc4598186ab2888f67c0dc1d917e6887db6/frozenlist-1.7.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a5c505156368e4ea6b53b5ac23c92d7edc864537ff911d2fb24c140bb175e60", size = 215646, upload-time = "2025-06-09T23:01:19.649Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/72/31/bc8c5c99c7818293458fe745dab4fd5730ff49697ccc82b554eb69f16a24/frozenlist-1.7.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bd7eb96a675f18aa5c553eb7ddc24a43c8c18f22e1f9925528128c052cdbe00", size = 232233, upload-time = "2025-06-09T23:01:21.175Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/59/52/460db4d7ba0811b9ccb85af996019f5d70831f2f5f255f7cc61f86199795/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:05579bf020096fe05a764f1f84cd104a12f78eaab68842d036772dc6d4870b4b", size = 227996, upload-time = "2025-06-09T23:01:23.098Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ba/c9/f4b39e904c03927b7ecf891804fd3b4df3db29b9e487c6418e37988d6e9d/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:376b6222d114e97eeec13d46c486facd41d4f43bab626b7c3f6a8b4e81a5192c", size = 242280, upload-time = "2025-06-09T23:01:24.808Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b8/33/3f8d6ced42f162d743e3517781566b8481322be321b486d9d262adf70bfb/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0aa7e176ebe115379b5b1c95b4096fb1c17cce0847402e227e712c27bdb5a949", size = 217717, upload-time = "2025-06-09T23:01:26.28Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3e/e8/ad683e75da6ccef50d0ab0c2b2324b32f84fc88ceee778ed79b8e2d2fe2e/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3fbba20e662b9c2130dc771e332a99eff5da078b2b2648153a40669a6d0e36ca", size = 236644, upload-time = "2025-06-09T23:01:27.887Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b2/14/8d19ccdd3799310722195a72ac94ddc677541fb4bef4091d8e7775752360/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f3f4410a0a601d349dd406b5713fec59b4cee7e71678d5b17edda7f4655a940b", size = 238879, upload-time = "2025-06-09T23:01:29.524Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ce/13/c12bf657494c2fd1079a48b2db49fa4196325909249a52d8f09bc9123fd7/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e2cdfaaec6a2f9327bf43c933c0319a7c429058e8537c508964a133dffee412e", size = 232502, upload-time = "2025-06-09T23:01:31.287Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d7/8b/e7f9dfde869825489382bc0d512c15e96d3964180c9499efcec72e85db7e/frozenlist-1.7.0-cp313-cp313-win32.whl", hash = "sha256:5fc4df05a6591c7768459caba1b342d9ec23fa16195e744939ba5914596ae3e1", size = 39169, upload-time = "2025-06-09T23:01:35.503Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/35/89/a487a98d94205d85745080a37860ff5744b9820a2c9acbcdd9440bfddf98/frozenlist-1.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:52109052b9791a3e6b5d1b65f4b909703984b770694d3eb64fad124c835d7cba", size = 43219, upload-time = "2025-06-09T23:01:36.784Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/56/d5/5c4cf2319a49eddd9dd7145e66c4866bdc6f3dbc67ca3d59685149c11e0d/frozenlist-1.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a6f86e4193bb0e235ef6ce3dde5cbabed887e0b11f516ce8a0f4d3b33078ec2d", size = 84345, upload-time = "2025-06-09T23:01:38.295Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a4/7d/ec2c1e1dc16b85bc9d526009961953df9cec8481b6886debb36ec9107799/frozenlist-1.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:82d664628865abeb32d90ae497fb93df398a69bb3434463d172b80fc25b0dd7d", size = 48880, upload-time = "2025-06-09T23:01:39.887Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/69/86/f9596807b03de126e11e7d42ac91e3d0b19a6599c714a1989a4e85eeefc4/frozenlist-1.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:912a7e8375a1c9a68325a902f3953191b7b292aa3c3fb0d71a216221deca460b", size = 48498, upload-time = "2025-06-09T23:01:41.318Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5e/cb/df6de220f5036001005f2d726b789b2c0b65f2363b104bbc16f5be8084f8/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9537c2777167488d539bc5de2ad262efc44388230e5118868e172dd4a552b146", size = 292296, upload-time = "2025-06-09T23:01:42.685Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/83/1f/de84c642f17c8f851a2905cee2dae401e5e0daca9b5ef121e120e19aa825/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f34560fb1b4c3e30ba35fa9a13894ba39e5acfc5f60f57d8accde65f46cc5e74", size = 273103, upload-time = "2025-06-09T23:01:44.166Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/88/3c/c840bfa474ba3fa13c772b93070893c6e9d5c0350885760376cbe3b6c1b3/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:acd03d224b0175f5a850edc104ac19040d35419eddad04e7cf2d5986d98427f1", size = 292869, upload-time = "2025-06-09T23:01:45.681Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a6/1c/3efa6e7d5a39a1d5ef0abeb51c48fb657765794a46cf124e5aca2c7a592c/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2038310bc582f3d6a09b3816ab01737d60bf7b1ec70f5356b09e84fb7408ab1", size = 291467, upload-time = "2025-06-09T23:01:47.234Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4f/00/d5c5e09d4922c395e2f2f6b79b9a20dab4b67daaf78ab92e7729341f61f6/frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8c05e4c8e5f36e5e088caa1bf78a687528f83c043706640a92cb76cd6999384", size = 266028, upload-time = "2025-06-09T23:01:48.819Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4e/27/72765be905619dfde25a7f33813ac0341eb6b076abede17a2e3fbfade0cb/frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:765bb588c86e47d0b68f23c1bee323d4b703218037765dcf3f25c838c6fecceb", size = 284294, upload-time = "2025-06-09T23:01:50.394Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/88/67/c94103a23001b17808eb7dd1200c156bb69fb68e63fcf0693dde4cd6228c/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:32dc2e08c67d86d0969714dd484fd60ff08ff81d1a1e40a77dd34a387e6ebc0c", size = 281898, upload-time = "2025-06-09T23:01:52.234Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/42/34/a3e2c00c00f9e2a9db5653bca3fec306349e71aff14ae45ecc6d0951dd24/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:c0303e597eb5a5321b4de9c68e9845ac8f290d2ab3f3e2c864437d3c5a30cd65", size = 290465, upload-time = "2025-06-09T23:01:53.788Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bb/73/f89b7fbce8b0b0c095d82b008afd0590f71ccb3dee6eee41791cf8cd25fd/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:a47f2abb4e29b3a8d0b530f7c3598badc6b134562b1a5caee867f7c62fee51e3", size = 266385, upload-time = "2025-06-09T23:01:55.769Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cd/45/e365fdb554159462ca12df54bc59bfa7a9a273ecc21e99e72e597564d1ae/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:3d688126c242a6fabbd92e02633414d40f50bb6002fa4cf995a1d18051525657", size = 288771, upload-time = "2025-06-09T23:01:57.4Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/00/11/47b6117002a0e904f004d70ec5194fe9144f117c33c851e3d51c765962d0/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:4e7e9652b3d367c7bd449a727dc79d5043f48b88d0cbfd4f9f1060cf2b414104", size = 288206, upload-time = "2025-06-09T23:01:58.936Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/40/37/5f9f3c3fd7f7746082ec67bcdc204db72dad081f4f83a503d33220a92973/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1a85e345b4c43db8b842cab1feb41be5cc0b10a1830e6295b69d7310f99becaf", size = 282620, upload-time = "2025-06-09T23:02:00.493Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0b/31/8fbc5af2d183bff20f21aa743b4088eac4445d2bb1cdece449ae80e4e2d1/frozenlist-1.7.0-cp313-cp313t-win32.whl", hash = "sha256:3a14027124ddb70dfcee5148979998066897e79f89f64b13328595c4bdf77c81", size = 43059, upload-time = "2025-06-09T23:02:02.072Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bb/ed/41956f52105b8dbc26e457c5705340c67c8cc2b79f394b79bffc09d0e938/frozenlist-1.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3bf8010d71d4507775f658e9823210b7427be36625b387221642725b515dcf3e", size = 47516, upload-time = "2025-06-09T23:02:03.779Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ee/45/b82e3c16be2182bff01179db177fe144d58b5dc787a7d4492c6ed8b9317f/frozenlist-1.7.0-py3-none-any.whl", hash = "sha256:9a5af342e34f7e97caf8c995864c7a396418ae2859cc6fdf1b1073020d516a7e", size = 13106, upload-time = "2025-06-09T23:02:34.204Z" }, +] + +[[package]] +name = "fsspec" +version = "2025.3.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/34/f4/5721faf47b8c499e776bc34c6a8fc17efdf7fdef0b00f398128bc5dcb4ac/fsspec-2025.3.0.tar.gz", hash = "sha256:a935fd1ea872591f2b5148907d103488fc523295e6c64b835cfad8c3eca44972", size = 298491, upload-time = "2025-03-07T21:47:56.461Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/56/53/eb690efa8513166adef3e0669afd31e95ffde69fb3c52ec2ac7223ed6018/fsspec-2025.3.0-py3-none-any.whl", hash = "sha256:efb87af3efa9103f94ca91a7f8cb7a4df91af9f74fc106c9c7ea0efd7277c1b3", size = 193615, upload-time = "2025-03-07T21:47:54.809Z" }, +] + +[package.optional-dependencies] +http = [ + { name = "aiohttp" }, +] + +[[package]] +name = "gradio" +version = "5.42.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "aiofiles" }, + { name = "anyio" }, + { name = "audioop-lts", marker = "python_full_version >= '3.13'" }, + { name = "brotli" }, + { name = "fastapi" }, + { name = "ffmpy" }, + { name = "gradio-client" }, + { name = "groovy" }, + { name = "httpx" }, + { name = "huggingface-hub" }, + { name = "jinja2" }, + { name = "markupsafe" }, + { name = "numpy" }, + { name = "orjson" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "pillow" }, + { name = "pydantic" }, + { name = "pydub" }, + { name = "python-multipart" }, + { name = "pyyaml" }, + { name = "ruff", marker = "sys_platform != 'emscripten'" }, + { name = "safehttpx" }, + { name = "semantic-version" }, + { name = "starlette", marker = "sys_platform != 'emscripten'" }, + { name = "tomlkit" }, + { name = "typer", marker = "sys_platform != 'emscripten'" }, + { name = "typing-extensions" }, + { name = "urllib3", marker = "sys_platform == 'emscripten'" }, + { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c3/17/0903bce14b1f80893ae0f3e29329159cf4cf8ddb11ee039f5b7ddb846ace/gradio-5.42.0.tar.gz", hash = "sha256:74c8e18a3e6b7bd26396e8ed949521c0cde9bed68a15f589a9706f7cbcb8685f", size = 71665861, upload-time = "2025-08-08T20:22:08.427Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d6/b6/be0898391fe9b34d591df564bb8f3790a69fd2e4d4f30c93c76abbcf48f2/gradio-5.42.0-py3-none-any.whl", hash = "sha256:3e11c076f4eaa2d545fb176551a2a72e951374882b8543aeebd45739085da5d2", size = 59678676, upload-time = "2025-08-08T20:22:03.166Z" }, +] + +[[package]] +name = "gradio-client" +version = "1.11.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "fsspec" }, + { name = "httpx" }, + { name = "huggingface-hub" }, + { name = "packaging" }, + { name = "typing-extensions" }, + { name = "websockets" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e2/cf/30be9f91c506cbec5d425f3bd56cd762d69d4517197ddd9e9d69b735d821/gradio_client-1.11.1.tar.gz", hash = "sha256:0d4885fea23b0d5ff8f4c34824839e0aa7fd1ba4f120928a7fc3a6ebde42abab", size = 322518, upload-time = "2025-08-08T20:22:17.17Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c1/fe/b9d63453c4c3044ee96b4c7ac6b4331c543ca8d9195b2cd9ba299ecb6509/gradio_client-1.11.1-py3-none-any.whl", hash = "sha256:eb7870c2eb0c18f22613f0635a50ca805fdf6e8ddeb71d9f06b398b876372487", size = 324526, upload-time = "2025-08-08T20:22:16.035Z" }, +] + +[[package]] +name = "groovy" +version = "0.1.2" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/52/36/bbdede67400277bef33d3ec0e6a31750da972c469f75966b4930c753218f/groovy-0.1.2.tar.gz", hash = "sha256:25c1dc09b3f9d7e292458aa762c6beb96ea037071bf5e917fc81fb78d2231083", size = 17325, upload-time = "2025-02-28T20:24:56.068Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/28/27/3d6dcadc8a3214d8522c1e7f6a19554e33659be44546d44a2f7572ac7d2a/groovy-0.1.2-py3-none-any.whl", hash = "sha256:7f7975bab18c729a257a8b1ae9dcd70b7cafb1720481beae47719af57c35fa64", size = 14090, upload-time = "2025-02-28T20:24:55.152Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "hf-xet" +version = "1.1.7" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b2/0a/a0f56735940fde6dd627602fec9ab3bad23f66a272397560abd65aba416e/hf_xet-1.1.7.tar.gz", hash = "sha256:20cec8db4561338824a3b5f8c19774055b04a8df7fff0cb1ff2cb1a0c1607b80", size = 477719, upload-time = "2025-08-06T00:30:55.741Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b1/7c/8d7803995caf14e7d19a392a486a040f923e2cfeff824e9b800b92072f76/hf_xet-1.1.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:60dae4b44d520819e54e216a2505685248ec0adbdb2dd4848b17aa85a0375cde", size = 2761743, upload-time = "2025-08-06T00:30:50.634Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/51/a3/fa5897099454aa287022a34a30e68dbff0e617760f774f8bd1db17f06bd4/hf_xet-1.1.7-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:b109f4c11e01c057fc82004c9e51e6cdfe2cb230637644ade40c599739067b2e", size = 2624331, upload-time = "2025-08-06T00:30:49.212Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/86/50/2446a132267e60b8a48b2e5835d6e24fd988000d0f5b9b15ebd6d64ef769/hf_xet-1.1.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6efaaf1a5a9fc3a501d3e71e88a6bfebc69ee3a716d0e713a931c8b8d920038f", size = 3183844, upload-time = "2025-08-06T00:30:47.582Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/20/8f/ccc670616bb9beee867c6bb7139f7eab2b1370fe426503c25f5cbb27b148/hf_xet-1.1.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:751571540f9c1fbad9afcf222a5fb96daf2384bf821317b8bfb0c59d86078513", size = 3074209, upload-time = "2025-08-06T00:30:45.509Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/21/0a/4c30e1eb77205565b854f5e4a82cf1f056214e4dc87f2918ebf83d47ae14/hf_xet-1.1.7-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:18b61bbae92d56ae731b92087c44efcac216071182c603fc535f8e29ec4b09b8", size = 3239602, upload-time = "2025-08-06T00:30:52.41Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f5/1e/fc7e9baf14152662ef0b35fa52a6e889f770a7ed14ac239de3c829ecb47e/hf_xet-1.1.7-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:713f2bff61b252f8523739969f247aa354ad8e6d869b8281e174e2ea1bb8d604", size = 3348184, upload-time = "2025-08-06T00:30:54.105Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a3/73/e354eae84ceff117ec3560141224724794828927fcc013c5b449bf0b8745/hf_xet-1.1.7-cp37-abi3-win_amd64.whl", hash = "sha256:2e356da7d284479ae0f1dea3cf5a2f74fdf925d6dca84ac4341930d892c7cb34", size = 2820008, upload-time = "2025-08-06T00:30:57.056Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "huggingface-hub" +version = "0.34.4" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "filelock" }, + { name = "fsspec" }, + { name = "hf-xet", marker = "platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/45/c9/bdbe19339f76d12985bc03572f330a01a93c04dffecaaea3061bdd7fb892/huggingface_hub-0.34.4.tar.gz", hash = "sha256:a4228daa6fb001be3f4f4bdaf9a0db00e1739235702848df00885c9b5742c85c", size = 459768, upload-time = "2025-08-08T09:14:52.365Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/39/7b/bb06b061991107cd8783f300adff3e7b7f284e330fd82f507f2a1417b11d/huggingface_hub-0.34.4-py3-none-any.whl", hash = "sha256:9b365d781739c93ff90c359844221beef048403f1bc1f1c123c191257c3c890a", size = 561452, upload-time = "2025-08-08T09:14:50.159Z" }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "joblib" +version = "1.5.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/dc/fe/0f5a938c54105553436dbff7a61dc4fed4b1b2c98852f8833beaf4d5968f/joblib-1.5.1.tar.gz", hash = "sha256:f4f86e351f39fe3d0d32a9f2c3d8af1ee4cec285aafcb27003dda5205576b444", size = 330475, upload-time = "2025-05-23T12:04:37.097Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7d/4f/1195bbac8e0c2acc5f740661631d8d750dc38d4a32b23ee5df3cde6f4e0d/joblib-1.5.1-py3-none-any.whl", hash = "sha256:4719a31f054c7d766948dcd83e9613686b27114f190f717cec7eaa2084f8a74a", size = 307746, upload-time = "2025-05-23T12:04:35.124Z" }, +] + +[[package]] +name = "jsonlines" +version = "4.0.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "attrs" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/35/87/bcda8e46c88d0e34cad2f09ee2d0c7f5957bccdb9791b0b934ec84d84be4/jsonlines-4.0.0.tar.gz", hash = "sha256:0c6d2c09117550c089995247f605ae4cf77dd1533041d366351f6f298822ea74" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f8/62/d9ba6323b9202dd2fe166beab8a86d29465c41a0288cbe229fac60c1ab8d/jsonlines-4.0.0-py3-none-any.whl", hash = "sha256:185b334ff2ca5a91362993f42e83588a360cf95ce4b71a73548502bda52a7c55" }, +] + +[[package]] +name = "latex2sympy2-extended" +version = "1.10.2" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "antlr4-python3-runtime" }, + { name = "sympy" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f4/de/472f9115c14c6f6d8a5889cabe3418283d708bde62ce00402c29441deed4/latex2sympy2_extended-1.10.2.tar.gz", hash = "sha256:41a517ffcc5a140e910a7d1646ce6ff440817e5f9d48fc8279d88bd0925bc389", size = 206188, upload-time = "2025-07-02T15:26:06.225Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ab/60/dfbbf40e3a371388c0e03ff65b01319b7d4023e883df6d7261125772ffdc/latex2sympy2_extended-1.10.2-py3-none-any.whl", hash = "sha256:f910442c5b02a466c1046f47d05cc5285181068b882399281f30102715337fb7", size = 207855, upload-time = "2025-07-02T15:26:04.88Z" }, +] + +[[package]] +name = "lm-eval" +version = "0.4.8" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "accelerate" }, + { name = "datasets" }, + { name = "dill" }, + { name = "evaluate" }, + { name = "jsonlines" }, + { name = "more-itertools" }, + { name = "numexpr" }, + { name = "peft" }, + { name = "pybind11" }, + { name = "pytablewriter" }, + { name = "rouge-score" }, + { name = "sacrebleu" }, + { name = "scikit-learn" }, + { name = "sqlitedict" }, + { name = "torch" }, + { name = "tqdm-multiprocess" }, + { name = "transformers" }, + { name = "word2number" }, + { name = "zstandard" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ba/b2/fe23d318c5f645636f9f776c373d1ba68aa55c998894660613a09b0fe2ec/lm_eval-0.4.8.tar.gz", hash = "sha256:3a7ffff20347315bf2318796b90ec9f7dd819836567085b5f57fdcc8bc450b4d", size = 1360915, upload-time = "2025-03-05T03:04:14.338Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c3/0b/36d6117f644f3685e6b87005ecd7051d01e9cdcf617e8e671102c1546de2/lm_eval-0.4.8-py3-none-any.whl", hash = "sha256:2ba377e067affcfcd27b73d6c045f8ce9ff88accd56176d9a86af3c12b0e01f6", size = 3888006, upload-time = "2025-03-05T03:04:12.109Z" }, +] + +[[package]] +name = "lxml" +version = "6.0.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c5/ed/60eb6fa2923602fba988d9ca7c5cdbd7cf25faa795162ed538b527a35411/lxml-6.0.0.tar.gz", hash = "sha256:032e65120339d44cdc3efc326c9f660f5f7205f3a535c1fdbf898b29ea01fb72", size = 4096938, upload-time = "2025-06-26T16:28:19.373Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/89/c3/d01d735c298d7e0ddcedf6f028bf556577e5ab4f4da45175ecd909c79378/lxml-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78718d8454a6e928470d511bf8ac93f469283a45c354995f7d19e77292f26108", size = 8429515, upload-time = "2025-06-26T16:26:06.776Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/06/37/0e3eae3043d366b73da55a86274a590bae76dc45aa004b7042e6f97803b1/lxml-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:84ef591495ffd3f9dcabffd6391db7bb70d7230b5c35ef5148354a134f56f2be", size = 4601387, upload-time = "2025-06-26T16:26:09.511Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a3/28/e1a9a881e6d6e29dda13d633885d13acb0058f65e95da67841c8dd02b4a8/lxml-6.0.0-cp312-cp312-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:2930aa001a3776c3e2601cb8e0a15d21b8270528d89cc308be4843ade546b9ab", size = 5228928, upload-time = "2025-06-26T16:26:12.337Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9a/55/2cb24ea48aa30c99f805921c1c7860c1f45c0e811e44ee4e6a155668de06/lxml-6.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:219e0431ea8006e15005767f0351e3f7f9143e793e58519dc97fe9e07fae5563", size = 4952289, upload-time = "2025-06-28T18:47:25.602Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/31/c0/b25d9528df296b9a3306ba21ff982fc5b698c45ab78b94d18c2d6ae71fd9/lxml-6.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bd5913b4972681ffc9718bc2d4c53cde39ef81415e1671ff93e9aa30b46595e7", size = 5111310, upload-time = "2025-06-28T18:47:28.136Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e9/af/681a8b3e4f668bea6e6514cbcb297beb6de2b641e70f09d3d78655f4f44c/lxml-6.0.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:390240baeb9f415a82eefc2e13285016f9c8b5ad71ec80574ae8fa9605093cd7", size = 5025457, upload-time = "2025-06-26T16:26:15.068Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/99/b6/3a7971aa05b7be7dfebc7ab57262ec527775c2c3c5b2f43675cac0458cad/lxml-6.0.0-cp312-cp312-manylinux_2_27_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d6e200909a119626744dd81bae409fc44134389e03fbf1d68ed2a55a2fb10991", size = 5657016, upload-time = "2025-07-03T19:19:06.008Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/69/f8/693b1a10a891197143c0673fcce5b75fc69132afa81a36e4568c12c8faba/lxml-6.0.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ca50bd612438258a91b5b3788c6621c1f05c8c478e7951899f492be42defc0da", size = 5257565, upload-time = "2025-06-26T16:26:17.906Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a8/96/e08ff98f2c6426c98c8964513c5dab8d6eb81dadcd0af6f0c538ada78d33/lxml-6.0.0-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:c24b8efd9c0f62bad0439283c2c795ef916c5a6b75f03c17799775c7ae3c0c9e", size = 4713390, upload-time = "2025-06-26T16:26:20.292Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a8/83/6184aba6cc94d7413959f6f8f54807dc318fdcd4985c347fe3ea6937f772/lxml-6.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:afd27d8629ae94c5d863e32ab0e1d5590371d296b87dae0a751fb22bf3685741", size = 5066103, upload-time = "2025-06-26T16:26:22.765Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ee/01/8bf1f4035852d0ff2e36a4d9aacdbcc57e93a6cd35a54e05fa984cdf73ab/lxml-6.0.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:54c4855eabd9fc29707d30141be99e5cd1102e7d2258d2892314cf4c110726c3", size = 4791428, upload-time = "2025-06-26T16:26:26.461Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/29/31/c0267d03b16954a85ed6b065116b621d37f559553d9339c7dcc4943a76f1/lxml-6.0.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c907516d49f77f6cd8ead1322198bdfd902003c3c330c77a1c5f3cc32a0e4d16", size = 5678523, upload-time = "2025-07-03T19:19:09.837Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5c/f7/5495829a864bc5f8b0798d2b52a807c89966523140f3d6fa3a58ab6720ea/lxml-6.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:36531f81c8214e293097cd2b7873f178997dae33d3667caaae8bdfb9666b76c0", size = 5281290, upload-time = "2025-06-26T16:26:29.406Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/79/56/6b8edb79d9ed294ccc4e881f4db1023af56ba451909b9ce79f2a2cd7c532/lxml-6.0.0-cp312-cp312-win32.whl", hash = "sha256:690b20e3388a7ec98e899fd54c924e50ba6693874aa65ef9cb53de7f7de9d64a", size = 3613495, upload-time = "2025-06-26T16:26:31.588Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0b/1e/cc32034b40ad6af80b6fd9b66301fc0f180f300002e5c3eb5a6110a93317/lxml-6.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:310b719b695b3dd442cdfbbe64936b2f2e231bb91d998e99e6f0daf991a3eba3", size = 4014711, upload-time = "2025-06-26T16:26:33.723Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/55/10/dc8e5290ae4c94bdc1a4c55865be7e1f31dfd857a88b21cbba68b5fea61b/lxml-6.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:8cb26f51c82d77483cdcd2b4a53cda55bbee29b3c2f3ddeb47182a2a9064e4eb", size = 3674431, upload-time = "2025-06-26T16:26:35.959Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/79/21/6e7c060822a3c954ff085e5e1b94b4a25757c06529eac91e550f3f5cd8b8/lxml-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6da7cd4f405fd7db56e51e96bff0865b9853ae70df0e6720624049da76bde2da", size = 8414372, upload-time = "2025-06-26T16:26:39.079Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a4/f6/051b1607a459db670fc3a244fa4f06f101a8adf86cda263d1a56b3a4f9d5/lxml-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b34339898bb556a2351a1830f88f751679f343eabf9cf05841c95b165152c9e7", size = 4593940, upload-time = "2025-06-26T16:26:41.891Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8e/74/dd595d92a40bda3c687d70d4487b2c7eff93fd63b568acd64fedd2ba00fe/lxml-6.0.0-cp313-cp313-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:51a5e4c61a4541bd1cd3ba74766d0c9b6c12d6a1a4964ef60026832aac8e79b3", size = 5214329, upload-time = "2025-06-26T16:26:44.669Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/52/46/3572761efc1bd45fcafb44a63b3b0feeb5b3f0066886821e94b0254f9253/lxml-6.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d18a25b19ca7307045581b18b3ec9ead2b1db5ccd8719c291f0cd0a5cec6cb81", size = 4947559, upload-time = "2025-06-28T18:47:31.091Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/94/8a/5e40de920e67c4f2eef9151097deb9b52d86c95762d8ee238134aff2125d/lxml-6.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d4f0c66df4386b75d2ab1e20a489f30dc7fd9a06a896d64980541506086be1f1", size = 5102143, upload-time = "2025-06-28T18:47:33.612Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7c/4b/20555bdd75d57945bdabfbc45fdb1a36a1a0ff9eae4653e951b2b79c9209/lxml-6.0.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f4b481b6cc3a897adb4279216695150bbe7a44c03daba3c894f49d2037e0a24", size = 5021931, upload-time = "2025-06-26T16:26:47.503Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b6/6e/cf03b412f3763d4ca23b25e70c96a74cfece64cec3addf1c4ec639586b13/lxml-6.0.0-cp313-cp313-manylinux_2_27_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8a78d6c9168f5bcb20971bf3329c2b83078611fbe1f807baadc64afc70523b3a", size = 5645469, upload-time = "2025-07-03T19:19:13.32Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d4/dd/39c8507c16db6031f8c1ddf70ed95dbb0a6d466a40002a3522c128aba472/lxml-6.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ae06fbab4f1bb7db4f7c8ca9897dc8db4447d1a2b9bee78474ad403437bcc29", size = 5247467, upload-time = "2025-06-26T16:26:49.998Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4d/56/732d49def0631ad633844cfb2664563c830173a98d5efd9b172e89a4800d/lxml-6.0.0-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:1fa377b827ca2023244a06554c6e7dc6828a10aaf74ca41965c5d8a4925aebb4", size = 4720601, upload-time = "2025-06-26T16:26:52.564Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8f/7f/6b956fab95fa73462bca25d1ea7fc8274ddf68fb8e60b78d56c03b65278e/lxml-6.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1676b56d48048a62ef77a250428d1f31f610763636e0784ba67a9740823988ca", size = 5060227, upload-time = "2025-06-26T16:26:55.054Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/97/06/e851ac2924447e8b15a294855caf3d543424364a143c001014d22c8ca94c/lxml-6.0.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:0e32698462aacc5c1cf6bdfebc9c781821b7e74c79f13e5ffc8bfe27c42b1abf", size = 4790637, upload-time = "2025-06-26T16:26:57.384Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/06/d4/fd216f3cd6625022c25b336c7570d11f4a43adbaf0a56106d3d496f727a7/lxml-6.0.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4d6036c3a296707357efb375cfc24bb64cd955b9ec731abf11ebb1e40063949f", size = 5662049, upload-time = "2025-07-03T19:19:16.409Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/52/03/0e764ce00b95e008d76b99d432f1807f3574fb2945b496a17807a1645dbd/lxml-6.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7488a43033c958637b1a08cddc9188eb06d3ad36582cebc7d4815980b47e27ef", size = 5272430, upload-time = "2025-06-26T16:27:00.031Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5f/01/d48cc141bc47bc1644d20fe97bbd5e8afb30415ec94f146f2f76d0d9d098/lxml-6.0.0-cp313-cp313-win32.whl", hash = "sha256:5fcd7d3b1d8ecb91445bd71b9c88bdbeae528fefee4f379895becfc72298d181", size = 3612896, upload-time = "2025-06-26T16:27:04.251Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f4/87/6456b9541d186ee7d4cb53bf1b9a0d7f3b1068532676940fdd594ac90865/lxml-6.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:2f34687222b78fff795feeb799a7d44eca2477c3d9d3a46ce17d51a4f383e32e", size = 4013132, upload-time = "2025-06-26T16:27:06.415Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b7/42/85b3aa8f06ca0d24962f8100f001828e1f1f1a38c954c16e71154ed7d53a/lxml-6.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:21db1ec5525780fd07251636eb5f7acb84003e9382c72c18c542a87c416ade03", size = 3672642, upload-time = "2025-06-26T16:27:09.888Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload-time = "2024-10-18T15:21:17.133Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload-time = "2024-10-18T15:21:18.064Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload-time = "2024-10-18T15:21:18.859Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload-time = "2024-10-18T15:21:19.671Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, +] + +[[package]] +name = "math-verify" +version = "0.8.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "latex2sympy2-extended" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/35/b5/b1db6fa6b6c28ebbe1889ee11a4703a72a2ca7750ec415f4559c758cf01a/math_verify-0.8.0.tar.gz", hash = "sha256:3295e0adb94bfe553ff6e3189c44f1916a85aa24ab5d1900f2086a706e28f7c4", size = 60191, upload-time = "2025-07-02T15:52:07.209Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fe/9f/59979f699b5c97334298f1295bc9fcdc9904d98d2276479bffff863d23b1/math_verify-0.8.0-py3-none-any.whl", hash = "sha256:31ca651296d817a9bb3fd58ca1fd0d192dcea709b1e5ecf2d0a4514c16f89087", size = 29994, upload-time = "2025-07-02T15:52:05.023Z" }, +] + +[[package]] +name = "mbstrdecoder" +version = "1.1.4" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "chardet" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/31/ab/05ae008357c8bdb6245ebf8a101d99f26c096e0ea20800b318153da23796/mbstrdecoder-1.1.4.tar.gz", hash = "sha256:8105ef9cf6b7d7d69fe7fd6b68a2d8f281ca9b365d7a9b670be376b2e6c81b21", size = 14527, upload-time = "2025-01-18T10:07:31.089Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/30/ac/5ce64a1d4cce00390beab88622a290420401f1cabf05caf2fc0995157c21/mbstrdecoder-1.1.4-py3-none-any.whl", hash = "sha256:03dae4ec50ec0d2ff4743e63fdbd5e0022815857494d35224b60775d3d934a8c", size = 7933, upload-time = "2025-01-18T10:07:29.562Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "more-itertools" +version = "10.7.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ce/a0/834b0cebabbfc7e311f30b46c8188790a37f89fc8d756660346fe5abfd09/more_itertools-10.7.0.tar.gz", hash = "sha256:9fddd5403be01a94b204faadcff459ec3568cf110265d3c54323e1e866ad29d3", size = 127671, upload-time = "2025-04-22T14:17:41.838Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2b/9f/7ba6f94fc1e9ac3d2b853fdff3035fb2fa5afbed898c4a72b8a020610594/more_itertools-10.7.0-py3-none-any.whl", hash = "sha256:d43980384673cb07d2f7d2d918c616b30c659c089ee23953f601d6609c67510e", size = 65278, upload-time = "2025-04-22T14:17:40.49Z" }, +] + +[[package]] +name = "mpmath" +version = "1.3.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" }, +] + +[[package]] +name = "multidict" +version = "6.6.3" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3d/2c/5dad12e82fbdf7470f29bff2171484bf07cb3b16ada60a6589af8f376440/multidict-6.6.3.tar.gz", hash = "sha256:798a9eb12dab0a6c2e29c1de6f3468af5cb2da6053a20dfa3344907eed0937cc", size = 101006, upload-time = "2025-06-30T15:53:46.929Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0e/a0/6b57988ea102da0623ea814160ed78d45a2645e4bbb499c2896d12833a70/multidict-6.6.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:056bebbeda16b2e38642d75e9e5310c484b7c24e3841dc0fb943206a72ec89d6", size = 76514, upload-time = "2025-06-30T15:51:48.728Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/07/7a/d1e92665b0850c6c0508f101f9cf0410c1afa24973e1115fe9c6a185ebf7/multidict-6.6.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e5f481cccb3c5c5e5de5d00b5141dc589c1047e60d07e85bbd7dea3d4580d63f", size = 45394, upload-time = "2025-06-30T15:51:49.986Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/52/6f/dd104490e01be6ef8bf9573705d8572f8c2d2c561f06e3826b081d9e6591/multidict-6.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:10bea2ee839a759ee368b5a6e47787f399b41e70cf0c20d90dfaf4158dfb4e55", size = 43590, upload-time = "2025-06-30T15:51:51.331Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/44/fe/06e0e01b1b0611e6581b7fd5a85b43dacc08b6cea3034f902f383b0873e5/multidict-6.6.3-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:2334cfb0fa9549d6ce2c21af2bfbcd3ac4ec3646b1b1581c88e3e2b1779ec92b", size = 237292, upload-time = "2025-06-30T15:51:52.584Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ce/71/4f0e558fb77696b89c233c1ee2d92f3e1d5459070a0e89153c9e9e804186/multidict-6.6.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8fee016722550a2276ca2cb5bb624480e0ed2bd49125b2b73b7010b9090e888", size = 258385, upload-time = "2025-06-30T15:51:53.913Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e3/25/cca0e68228addad24903801ed1ab42e21307a1b4b6dd2cf63da5d3ae082a/multidict-6.6.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5511cb35f5c50a2db21047c875eb42f308c5583edf96bd8ebf7d770a9d68f6d", size = 242328, upload-time = "2025-06-30T15:51:55.672Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6e/a3/46f2d420d86bbcb8fe660b26a10a219871a0fbf4d43cb846a4031533f3e0/multidict-6.6.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:712b348f7f449948e0a6c4564a21c7db965af900973a67db432d724619b3c680", size = 268057, upload-time = "2025-06-30T15:51:57.037Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9e/73/1c743542fe00794a2ec7466abd3f312ccb8fad8dff9f36d42e18fb1ec33e/multidict-6.6.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e4e15d2138ee2694e038e33b7c3da70e6b0ad8868b9f8094a72e1414aeda9c1a", size = 269341, upload-time = "2025-06-30T15:51:59.111Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a4/11/6ec9dcbe2264b92778eeb85407d1df18812248bf3506a5a1754bc035db0c/multidict-6.6.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8df25594989aebff8a130f7899fa03cbfcc5d2b5f4a461cf2518236fe6f15961", size = 256081, upload-time = "2025-06-30T15:52:00.533Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9b/2b/631b1e2afeb5f1696846d747d36cda075bfdc0bc7245d6ba5c319278d6c4/multidict-6.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:159ca68bfd284a8860f8d8112cf0521113bffd9c17568579e4d13d1f1dc76b65", size = 253581, upload-time = "2025-06-30T15:52:02.43Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bf/0e/7e3b93f79efeb6111d3bf9a1a69e555ba1d07ad1c11bceb56b7310d0d7ee/multidict-6.6.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e098c17856a8c9ade81b4810888c5ad1914099657226283cab3062c0540b0643", size = 250750, upload-time = "2025-06-30T15:52:04.26Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ad/9e/086846c1d6601948e7de556ee464a2d4c85e33883e749f46b9547d7b0704/multidict-6.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:67c92ed673049dec52d7ed39f8cf9ebbadf5032c774058b4406d18c8f8fe7063", size = 251548, upload-time = "2025-06-30T15:52:06.002Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8c/7b/86ec260118e522f1a31550e87b23542294880c97cfbf6fb18cc67b044c66/multidict-6.6.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:bd0578596e3a835ef451784053cfd327d607fc39ea1a14812139339a18a0dbc3", size = 262718, upload-time = "2025-06-30T15:52:07.707Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8c/bd/22ce8f47abb0be04692c9fc4638508b8340987b18691aa7775d927b73f72/multidict-6.6.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:346055630a2df2115cd23ae271910b4cae40f4e336773550dca4889b12916e75", size = 259603, upload-time = "2025-06-30T15:52:09.58Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/07/9c/91b7ac1691be95cd1f4a26e36a74b97cda6aa9820632d31aab4410f46ebd/multidict-6.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:555ff55a359302b79de97e0468e9ee80637b0de1fce77721639f7cd9440b3a10", size = 251351, upload-time = "2025-06-30T15:52:10.947Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6f/5c/4d7adc739884f7a9fbe00d1eac8c034023ef8bad71f2ebe12823ca2e3649/multidict-6.6.3-cp312-cp312-win32.whl", hash = "sha256:73ab034fb8d58ff85c2bcbadc470efc3fafeea8affcf8722855fb94557f14cc5", size = 41860, upload-time = "2025-06-30T15:52:12.334Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6a/a3/0fbc7afdf7cb1aa12a086b02959307848eb6bcc8f66fcb66c0cb57e2a2c1/multidict-6.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:04cbcce84f63b9af41bad04a54d4cc4e60e90c35b9e6ccb130be2d75b71f8c17", size = 45982, upload-time = "2025-06-30T15:52:13.6Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b8/95/8c825bd70ff9b02462dc18d1295dd08d3e9e4eb66856d292ffa62cfe1920/multidict-6.6.3-cp312-cp312-win_arm64.whl", hash = "sha256:0f1130b896ecb52d2a1e615260f3ea2af55fa7dc3d7c3003ba0c3121a759b18b", size = 43210, upload-time = "2025-06-30T15:52:14.893Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/52/1d/0bebcbbb4f000751fbd09957257903d6e002943fc668d841a4cf2fb7f872/multidict-6.6.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:540d3c06d48507357a7d57721e5094b4f7093399a0106c211f33540fdc374d55", size = 75843, upload-time = "2025-06-30T15:52:16.155Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/07/8f/cbe241b0434cfe257f65c2b1bcf9e8d5fb52bc708c5061fb29b0fed22bdf/multidict-6.6.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9c19cea2a690f04247d43f366d03e4eb110a0dc4cd1bbeee4d445435428ed35b", size = 45053, upload-time = "2025-06-30T15:52:17.429Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/32/d2/0b3b23f9dbad5b270b22a3ac3ea73ed0a50ef2d9a390447061178ed6bdb8/multidict-6.6.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7af039820cfd00effec86bda5d8debef711a3e86a1d3772e85bea0f243a4bd65", size = 43273, upload-time = "2025-06-30T15:52:19.346Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fd/fe/6eb68927e823999e3683bc49678eb20374ba9615097d085298fd5b386564/multidict-6.6.3-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:500b84f51654fdc3944e936f2922114349bf8fdcac77c3092b03449f0e5bc2b3", size = 237124, upload-time = "2025-06-30T15:52:20.773Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e7/ab/320d8507e7726c460cb77117848b3834ea0d59e769f36fdae495f7669929/multidict-6.6.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3fc723ab8a5c5ed6c50418e9bfcd8e6dceba6c271cee6728a10a4ed8561520c", size = 256892, upload-time = "2025-06-30T15:52:22.242Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/76/60/38ee422db515ac69834e60142a1a69111ac96026e76e8e9aa347fd2e4591/multidict-6.6.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:94c47ea3ade005b5976789baaed66d4de4480d0a0bf31cef6edaa41c1e7b56a6", size = 240547, upload-time = "2025-06-30T15:52:23.736Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/27/fb/905224fde2dff042b030c27ad95a7ae744325cf54b890b443d30a789b80e/multidict-6.6.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dbc7cf464cc6d67e83e136c9f55726da3a30176f020a36ead246eceed87f1cd8", size = 266223, upload-time = "2025-06-30T15:52:25.185Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/76/35/dc38ab361051beae08d1a53965e3e1a418752fc5be4d3fb983c5582d8784/multidict-6.6.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:900eb9f9da25ada070f8ee4a23f884e0ee66fe4e1a38c3af644256a508ad81ca", size = 267262, upload-time = "2025-06-30T15:52:26.969Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1f/a3/0a485b7f36e422421b17e2bbb5a81c1af10eac1d4476f2ff92927c730479/multidict-6.6.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c6df517cf177da5d47ab15407143a89cd1a23f8b335f3a28d57e8b0a3dbb884", size = 254345, upload-time = "2025-06-30T15:52:28.467Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b4/59/bcdd52c1dab7c0e0d75ff19cac751fbd5f850d1fc39172ce809a74aa9ea4/multidict-6.6.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4ef421045f13879e21c994b36e728d8e7d126c91a64b9185810ab51d474f27e7", size = 252248, upload-time = "2025-06-30T15:52:29.938Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bb/a4/2d96aaa6eae8067ce108d4acee6f45ced5728beda55c0f02ae1072c730d1/multidict-6.6.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:6c1e61bb4f80895c081790b6b09fa49e13566df8fbff817da3f85b3a8192e36b", size = 250115, upload-time = "2025-06-30T15:52:31.416Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/25/d2/ed9f847fa5c7d0677d4f02ea2c163d5e48573de3f57bacf5670e43a5ffaa/multidict-6.6.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e5e8523bb12d7623cd8300dbd91b9e439a46a028cd078ca695eb66ba31adee3c", size = 249649, upload-time = "2025-06-30T15:52:32.996Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1f/af/9155850372563fc550803d3f25373308aa70f59b52cff25854086ecb4a79/multidict-6.6.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:ef58340cc896219e4e653dade08fea5c55c6df41bcc68122e3be3e9d873d9a7b", size = 261203, upload-time = "2025-06-30T15:52:34.521Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/36/2f/c6a728f699896252cf309769089568a33c6439626648843f78743660709d/multidict-6.6.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fc9dc435ec8699e7b602b94fe0cd4703e69273a01cbc34409af29e7820f777f1", size = 258051, upload-time = "2025-06-30T15:52:35.999Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d0/60/689880776d6b18fa2b70f6cc74ff87dd6c6b9b47bd9cf74c16fecfaa6ad9/multidict-6.6.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9e864486ef4ab07db5e9cb997bad2b681514158d6954dd1958dfb163b83d53e6", size = 249601, upload-time = "2025-06-30T15:52:37.473Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/75/5e/325b11f2222a549019cf2ef879c1f81f94a0d40ace3ef55cf529915ba6cc/multidict-6.6.3-cp313-cp313-win32.whl", hash = "sha256:5633a82fba8e841bc5c5c06b16e21529573cd654f67fd833650a215520a6210e", size = 41683, upload-time = "2025-06-30T15:52:38.927Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b1/ad/cf46e73f5d6e3c775cabd2a05976547f3f18b39bee06260369a42501f053/multidict-6.6.3-cp313-cp313-win_amd64.whl", hash = "sha256:e93089c1570a4ad54c3714a12c2cef549dc9d58e97bcded193d928649cab78e9", size = 45811, upload-time = "2025-06-30T15:52:40.207Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c5/c9/2e3fe950db28fb7c62e1a5f46e1e38759b072e2089209bc033c2798bb5ec/multidict-6.6.3-cp313-cp313-win_arm64.whl", hash = "sha256:c60b401f192e79caec61f166da9c924e9f8bc65548d4246842df91651e83d600", size = 43056, upload-time = "2025-06-30T15:52:41.575Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3a/58/aaf8114cf34966e084a8cc9517771288adb53465188843d5a19862cb6dc3/multidict-6.6.3-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:02fd8f32d403a6ff13864b0851f1f523d4c988051eea0471d4f1fd8010f11134", size = 82811, upload-time = "2025-06-30T15:52:43.281Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/71/af/5402e7b58a1f5b987a07ad98f2501fdba2a4f4b4c30cf114e3ce8db64c87/multidict-6.6.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f3aa090106b1543f3f87b2041eef3c156c8da2aed90c63a2fbed62d875c49c37", size = 48304, upload-time = "2025-06-30T15:52:45.026Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/39/65/ab3c8cafe21adb45b24a50266fd747147dec7847425bc2a0f6934b3ae9ce/multidict-6.6.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e924fb978615a5e33ff644cc42e6aa241effcf4f3322c09d4f8cebde95aff5f8", size = 46775, upload-time = "2025-06-30T15:52:46.459Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/49/ba/9fcc1b332f67cc0c0c8079e263bfab6660f87fe4e28a35921771ff3eea0d/multidict-6.6.3-cp313-cp313t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:b9fe5a0e57c6dbd0e2ce81ca66272282c32cd11d31658ee9553849d91289e1c1", size = 229773, upload-time = "2025-06-30T15:52:47.88Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a4/14/0145a251f555f7c754ce2dcbcd012939bbd1f34f066fa5d28a50e722a054/multidict-6.6.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b24576f208793ebae00280c59927c3b7c2a3b1655e443a25f753c4611bc1c373", size = 250083, upload-time = "2025-06-30T15:52:49.366Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9e/d4/d5c0bd2bbb173b586c249a151a26d2fb3ec7d53c96e42091c9fef4e1f10c/multidict-6.6.3-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:135631cb6c58eac37d7ac0df380294fecdc026b28837fa07c02e459c7fb9c54e", size = 228980, upload-time = "2025-06-30T15:52:50.903Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/21/32/c9a2d8444a50ec48c4733ccc67254100c10e1c8ae8e40c7a2d2183b59b97/multidict-6.6.3-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:274d416b0df887aef98f19f21578653982cfb8a05b4e187d4a17103322eeaf8f", size = 257776, upload-time = "2025-06-30T15:52:52.764Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/68/d0/14fa1699f4ef629eae08ad6201c6b476098f5efb051b296f4c26be7a9fdf/multidict-6.6.3-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e252017a817fad7ce05cafbe5711ed40faeb580e63b16755a3a24e66fa1d87c0", size = 256882, upload-time = "2025-06-30T15:52:54.596Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/da/88/84a27570fbe303c65607d517a5f147cd2fc046c2d1da02b84b17b9bdc2aa/multidict-6.6.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e4cc8d848cd4fe1cdee28c13ea79ab0ed37fc2e89dd77bac86a2e7959a8c3bc", size = 247816, upload-time = "2025-06-30T15:52:56.175Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1c/60/dca352a0c999ce96a5d8b8ee0b2b9f729dcad2e0b0c195f8286269a2074c/multidict-6.6.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9e236a7094b9c4c1b7585f6b9cca34b9d833cf079f7e4c49e6a4a6ec9bfdc68f", size = 245341, upload-time = "2025-06-30T15:52:57.752Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/50/ef/433fa3ed06028f03946f3993223dada70fb700f763f70c00079533c34578/multidict-6.6.3-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:e0cb0ab69915c55627c933f0b555a943d98ba71b4d1c57bc0d0a66e2567c7471", size = 235854, upload-time = "2025-06-30T15:52:59.74Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1b/1f/487612ab56fbe35715320905215a57fede20de7db40a261759690dc80471/multidict-6.6.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:81ef2f64593aba09c5212a3d0f8c906a0d38d710a011f2f42759704d4557d3f2", size = 243432, upload-time = "2025-06-30T15:53:01.602Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/da/6f/ce8b79de16cd885c6f9052c96a3671373d00c59b3ee635ea93e6e81b8ccf/multidict-6.6.3-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:b9cbc60010de3562545fa198bfc6d3825df430ea96d2cc509c39bd71e2e7d648", size = 252731, upload-time = "2025-06-30T15:53:03.517Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bb/fe/a2514a6aba78e5abefa1624ca85ae18f542d95ac5cde2e3815a9fbf369aa/multidict-6.6.3-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:70d974eaaa37211390cd02ef93b7e938de564bbffa866f0b08d07e5e65da783d", size = 247086, upload-time = "2025-06-30T15:53:05.48Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8c/22/b788718d63bb3cce752d107a57c85fcd1a212c6c778628567c9713f9345a/multidict-6.6.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3713303e4a6663c6d01d648a68f2848701001f3390a030edaaf3fc949c90bf7c", size = 243338, upload-time = "2025-06-30T15:53:07.522Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/22/d6/fdb3d0670819f2228f3f7d9af613d5e652c15d170c83e5f1c94fbc55a25b/multidict-6.6.3-cp313-cp313t-win32.whl", hash = "sha256:639ecc9fe7cd73f2495f62c213e964843826f44505a3e5d82805aa85cac6f89e", size = 47812, upload-time = "2025-06-30T15:53:09.263Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b6/d6/a9d2c808f2c489ad199723197419207ecbfbc1776f6e155e1ecea9c883aa/multidict-6.6.3-cp313-cp313t-win_amd64.whl", hash = "sha256:9f97e181f344a0ef3881b573d31de8542cc0dbc559ec68c8f8b5ce2c2e91646d", size = 53011, upload-time = "2025-06-30T15:53:11.038Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f2/40/b68001cba8188dd267590a111f9661b6256debc327137667e832bf5d66e8/multidict-6.6.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ce8b7693da41a3c4fde5871c738a81490cea5496c671d74374c8ab889e1834fb", size = 45254, upload-time = "2025-06-30T15:53:12.421Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d8/30/9aec301e9772b098c1f5c0ca0279237c9766d94b97802e9888010c64b0ed/multidict-6.6.3-py3-none-any.whl", hash = "sha256:8db10f29c7541fc5da4defd8cd697e1ca429db743fa716325f236079b96f775a", size = 12313, upload-time = "2025-06-30T15:53:45.437Z" }, +] + +[[package]] +name = "multiprocess" +version = "0.70.16" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "dill" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b5/ae/04f39c5d0d0def03247c2893d6f2b83c136bf3320a2154d7b8858f2ba72d/multiprocess-0.70.16.tar.gz", hash = "sha256:161af703d4652a0e1410be6abccecde4a7ddffd19341be0a7011b94aeb171ac1", size = 1772603, upload-time = "2024-01-28T18:52:34.85Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bc/f7/7ec7fddc92e50714ea3745631f79bd9c96424cb2702632521028e57d3a36/multiprocess-0.70.16-py310-none-any.whl", hash = "sha256:c4a9944c67bd49f823687463660a2d6daae94c289adff97e0f9d696ba6371d02", size = 134824, upload-time = "2024-01-28T18:52:26.062Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/50/15/b56e50e8debaf439f44befec5b2af11db85f6e0f344c3113ae0be0593a91/multiprocess-0.70.16-py311-none-any.whl", hash = "sha256:af4cabb0dac72abfb1e794fa7855c325fd2b55a10a44628a3c1ad3311c04127a", size = 143519, upload-time = "2024-01-28T18:52:28.115Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0a/7d/a988f258104dcd2ccf1ed40fdc97e26c4ac351eeaf81d76e266c52d84e2f/multiprocess-0.70.16-py312-none-any.whl", hash = "sha256:fc0544c531920dde3b00c29863377f87e1632601092ea2daca74e4beb40faa2e", size = 146741, upload-time = "2024-01-28T18:52:29.395Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ea/89/38df130f2c799090c978b366cfdf5b96d08de5b29a4a293df7f7429fa50b/multiprocess-0.70.16-py38-none-any.whl", hash = "sha256:a71d82033454891091a226dfc319d0cfa8019a4e888ef9ca910372a446de4435", size = 132628, upload-time = "2024-01-28T18:52:30.853Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/da/d9/f7f9379981e39b8c2511c9e0326d212accacb82f12fbfdc1aa2ce2a7b2b6/multiprocess-0.70.16-py39-none-any.whl", hash = "sha256:a0bafd3ae1b732eac64be2e72038231c1ba97724b60b09400d68f229fcc2fbf3", size = 133351, upload-time = "2024-01-28T18:52:31.981Z" }, +] + +[[package]] +name = "networkx" +version = "3.5" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6c/4f/ccdb8ad3a38e583f214547fd2f7ff1fc160c43a75af88e6aec213404b96a/networkx-3.5.tar.gz", hash = "sha256:d4c6f9cf81f52d69230866796b82afbccdec3db7ae4fbd1b65ea750feed50037", size = 2471065, upload-time = "2025-05-29T11:35:07.804Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/eb/8d/776adee7bbf76365fdd7f2552710282c79a4ead5d2a46408c9043a2b70ba/networkx-3.5-py3-none-any.whl", hash = "sha256:0030d386a9a06dee3565298b4a734b68589749a544acbb6c412dc9e2489ec6ec", size = 2034406, upload-time = "2025-05-29T11:35:04.961Z" }, +] + +[[package]] +name = "nltk" +version = "3.9.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "click" }, + { name = "joblib" }, + { name = "regex" }, + { name = "tqdm" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3c/87/db8be88ad32c2d042420b6fd9ffd4a149f9a0d7f0e86b3f543be2eeeedd2/nltk-3.9.1.tar.gz", hash = "sha256:87d127bd3de4bd89a4f81265e5fa59cb1b199b27440175370f7417d2bc7ae868", size = 2904691, upload-time = "2024-08-18T19:48:37.769Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4d/66/7d9e26593edda06e8cb531874633f7c2372279c3b0f46235539fe546df8b/nltk-3.9.1-py3-none-any.whl", hash = "sha256:4fa26829c5b00715afe3061398a8989dc643b92ce7dd93fb4585a70930d168a1", size = 1505442, upload-time = "2024-08-18T19:48:21.909Z" }, +] + +[[package]] +name = "numexpr" +version = "2.11.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d2/8f/2cc977e91adbfbcdb6b49fdb9147e1d1c7566eb2c0c1e737e9a47020b5ca/numexpr-2.11.0.tar.gz", hash = "sha256:75b2c01a4eda2e7c357bc67a3f5c3dd76506c15b5fd4dc42845ef2e182181bad", size = 108960, upload-time = "2025-06-09T11:05:56.79Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/38/45/7a0e5a0b800d92e73825494ac695fa05a52c7fc7088d69a336880136b437/numexpr-2.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4229060be866813122385c608bbd3ea48fe0b33e91f2756810d28c1cdbfc98f1", size = 147494, upload-time = "2025-06-09T11:05:17.015Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/74/46/3a26b84e44f4739ec98de0ede4b95b4b8096f721e22d0e97517eeb02017e/numexpr-2.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:097aa8835d32d6ac52f2be543384019b4b134d1fb67998cbfc4271155edfe54a", size = 136832, upload-time = "2025-06-09T11:05:18.55Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/75/05/e3076ff25d4a108b47640c169c0a64811748c43b63d9cc052ea56de1631e/numexpr-2.11.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f082321c244ff5d0e252071fb2c4fe02063a45934144a1456a5370ca139bec2", size = 412618, upload-time = "2025-06-09T11:05:20.093Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/70/e8/15e0e077a004db0edd530da96c60c948689c888c464ee5d14b82405ebd86/numexpr-2.11.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7a19435ca3d7dd502b8d8dce643555eb1b6013989e3f7577857289f6db6be16", size = 403363, upload-time = "2025-06-09T11:05:21.217Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/10/14/f22afb3a7ae41d03ba87f62d00fbcfb76389f9cc91b7a82593c39c509318/numexpr-2.11.0-cp312-cp312-win32.whl", hash = "sha256:f326218262c8d8537887cc4bbd613c8409d62f2cac799835c0360e0d9cefaa5c", size = 153307, upload-time = "2025-06-09T11:05:22.855Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/18/70/abc585269424582b3cd6db261e33b2ec96b5d4971da3edb29fc9b62a8926/numexpr-2.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:0a184e5930c77ab91dd9beee4df403b825cd9dfc4e9ba4670d31c9fcb4e2c08e", size = 146337, upload-time = "2025-06-09T11:05:23.976Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/74/63/dbf4fb6c48006d413a82db138d03c3c007d0ed0684f693c4b77196448660/numexpr-2.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:eb766218abad05c7c3ddad5367d0ec702d6152cb4a48d9fd56a6cef6abade70c", size = 147495, upload-time = "2025-06-09T11:05:25.105Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3a/e4/2fbbf5b9121f54722dc4d4dfc75bc0b4e8ee2675f92ec86ee5697aecc53f/numexpr-2.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2036be213a6a1b5ce49acf60de99b911a0f9d174aab7679dde1fae315134f826", size = 136839, upload-time = "2025-06-09T11:05:26.171Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a8/3f/aa36415919c90f712a11127eaa7c0c8d045768d62a484a29364e4801c383/numexpr-2.11.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:096ec768bee2ef14ac757b4178e3c5f05e5f1cb6cae83b2eea9b4ba3ec1a86dd", size = 416240, upload-time = "2025-06-09T11:05:27.634Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b9/7d/4911f40d3610fc5557029f0d1f20ef9f571488319567ac4d8ee6d0978ee6/numexpr-2.11.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a1719788a787808c15c9bb98b6ff0c97d64a0e59c1a6ebe36d4ae4d7c5c09b95", size = 406641, upload-time = "2025-06-09T11:05:29.408Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6f/bc/d00e717e77691c410c6c461d7880b4c498896874316acc0e044d7eafacbf/numexpr-2.11.0-cp313-cp313-win32.whl", hash = "sha256:6b5fdfc86cbf5373ea67d554cc6f08863825ea8e928416bed8d5285e387420c6", size = 153313, upload-time = "2025-06-09T11:05:30.633Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/52/a2/93346789e6d73a76fdb68171904ade25c112f25df363a8f602c6b21bc220/numexpr-2.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:5ff337b36db141a1a0b49f01282783744f49f0d401cc83a512fc5596eb7db5c6", size = 146340, upload-time = "2025-06-09T11:05:31.771Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0b/20/c0e3aaf3cc4497e5253df2523a55c83b9d316cb5c9d5caaa4a1156cef6e3/numexpr-2.11.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:b9854fa70edbe93242b8bb4840e58d1128c45766d9a70710f05b4f67eb0feb6e", size = 148206, upload-time = "2025-06-09T11:05:33.3Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/de/49/22fd38ac990ba333f25b771305a5ffcd98c771f4d278868661ffb26deac1/numexpr-2.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:321736cb98f090ce864b58cc5c37661cb5548e394e0fe24d5f2c7892a89070c3", size = 137573, upload-time = "2025-06-09T11:05:34.422Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fb/1e/50074e472e9e6bea4fe430869708d9ede333a187d8d0740e70d5a9560aad/numexpr-2.11.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5cc434eb4a4df2fe442bcc50df114e82ff7aa234657baf873b2c9cf3f851e8e", size = 426674, upload-time = "2025-06-09T11:05:35.553Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8e/6d/7ccbc72b950653df62d29e2531c811ed80cfff93c927a5bfd86a71edb4da/numexpr-2.11.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:238d19465a272ada3967600fada55e4c6900485aefb42122a78dfcaf2efca65f", size = 416037, upload-time = "2025-06-09T11:05:36.601Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/31/7c/bbccad2734dd4b251cc6bdff8cf5ded18b5383f5a05aa8de7bf02acbb65b/numexpr-2.11.0-cp313-cp313t-win32.whl", hash = "sha256:0db4c2dcad09f9594b45fce794f4b903345195a8c216e252de2aa92884fd81a8", size = 153967, upload-time = "2025-06-09T11:05:37.907Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/75/d7/41287384e413e8d20457d35e264d9c9754e65eb13a988af51ceb7057f61b/numexpr-2.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a69b5c02014448a412012752dc46091902d28932c3be0c6e02e73cecceffb700", size = 147207, upload-time = "2025-06-09T11:05:39.011Z" }, +] + +[[package]] +name = "numpy" +version = "2.3.2" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/37/7d/3fec4199c5ffb892bed55cff901e4f39a58c81df9c44c280499e92cad264/numpy-2.3.2.tar.gz", hash = "sha256:e0486a11ec30cdecb53f184d496d1c6a20786c81e55e41640270130056f8ee48", size = 20489306, upload-time = "2025-07-24T21:32:07.553Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/00/6d/745dd1c1c5c284d17725e5c802ca4d45cfc6803519d777f087b71c9f4069/numpy-2.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bc3186bea41fae9d8e90c2b4fb5f0a1f5a690682da79b92574d63f56b529080b", size = 20956420, upload-time = "2025-07-24T20:28:18.002Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bc/96/e7b533ea5740641dd62b07a790af5d9d8fec36000b8e2d0472bd7574105f/numpy-2.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f4f0215edb189048a3c03bd5b19345bdfa7b45a7a6f72ae5945d2a28272727f", size = 14184660, upload-time = "2025-07-24T20:28:39.522Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2b/53/102c6122db45a62aa20d1b18c9986f67e6b97e0d6fbc1ae13e3e4c84430c/numpy-2.3.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b1224a734cd509f70816455c3cffe13a4f599b1bf7130f913ba0e2c0b2006c0", size = 5113382, upload-time = "2025-07-24T20:28:48.544Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2b/21/376257efcbf63e624250717e82b4fae93d60178f09eb03ed766dbb48ec9c/numpy-2.3.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3dcf02866b977a38ba3ec10215220609ab9667378a9e2150615673f3ffd6c73b", size = 6647258, upload-time = "2025-07-24T20:28:59.104Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/91/ba/f4ebf257f08affa464fe6036e13f2bf9d4642a40228781dc1235da81be9f/numpy-2.3.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:572d5512df5470f50ada8d1972c5f1082d9a0b7aa5944db8084077570cf98370", size = 14281409, upload-time = "2025-07-24T20:40:30.298Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/59/ef/f96536f1df42c668cbacb727a8c6da7afc9c05ece6d558927fb1722693e1/numpy-2.3.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8145dd6d10df13c559d1e4314df29695613575183fa2e2d11fac4c208c8a1f73", size = 16641317, upload-time = "2025-07-24T20:40:56.625Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f6/a7/af813a7b4f9a42f498dde8a4c6fcbff8100eed00182cc91dbaf095645f38/numpy-2.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:103ea7063fa624af04a791c39f97070bf93b96d7af7eb23530cd087dc8dbe9dc", size = 16056262, upload-time = "2025-07-24T20:41:20.797Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8b/5d/41c4ef8404caaa7f05ed1cfb06afe16a25895260eacbd29b4d84dff2920b/numpy-2.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc927d7f289d14f5e037be917539620603294454130b6de200091e23d27dc9be", size = 18579342, upload-time = "2025-07-24T20:41:50.753Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a1/4f/9950e44c5a11636f4a3af6e825ec23003475cc9a466edb7a759ed3ea63bd/numpy-2.3.2-cp312-cp312-win32.whl", hash = "sha256:d95f59afe7f808c103be692175008bab926b59309ade3e6d25009e9a171f7036", size = 6320610, upload-time = "2025-07-24T20:42:01.551Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7c/2f/244643a5ce54a94f0a9a2ab578189c061e4a87c002e037b0829dd77293b6/numpy-2.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:9e196ade2400c0c737d93465327d1ae7c06c7cb8a1756121ebf54b06ca183c7f", size = 12786292, upload-time = "2025-07-24T20:42:20.738Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/54/cd/7b5f49d5d78db7badab22d8323c1b6ae458fbf86c4fdfa194ab3cd4eb39b/numpy-2.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:ee807923782faaf60d0d7331f5e86da7d5e3079e28b291973c545476c2b00d07", size = 10194071, upload-time = "2025-07-24T20:42:36.657Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1c/c0/c6bb172c916b00700ed3bf71cb56175fd1f7dbecebf8353545d0b5519f6c/numpy-2.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c8d9727f5316a256425892b043736d63e89ed15bbfe6556c5ff4d9d4448ff3b3", size = 20949074, upload-time = "2025-07-24T20:43:07.813Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/20/4e/c116466d22acaf4573e58421c956c6076dc526e24a6be0903219775d862e/numpy-2.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:efc81393f25f14d11c9d161e46e6ee348637c0a1e8a54bf9dedc472a3fae993b", size = 14177311, upload-time = "2025-07-24T20:43:29.335Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/78/45/d4698c182895af189c463fc91d70805d455a227261d950e4e0f1310c2550/numpy-2.3.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:dd937f088a2df683cbb79dda9a772b62a3e5a8a7e76690612c2737f38c6ef1b6", size = 5106022, upload-time = "2025-07-24T20:43:37.999Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9f/76/3e6880fef4420179309dba72a8c11f6166c431cf6dee54c577af8906f914/numpy-2.3.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:11e58218c0c46c80509186e460d79fbdc9ca1eb8d8aee39d8f2dc768eb781089", size = 6640135, upload-time = "2025-07-24T20:43:49.28Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/34/fa/87ff7f25b3c4ce9085a62554460b7db686fef1e0207e8977795c7b7d7ba1/numpy-2.3.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5ad4ebcb683a1f99f4f392cc522ee20a18b2bb12a2c1c42c3d48d5a1adc9d3d2", size = 14278147, upload-time = "2025-07-24T20:44:10.328Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1d/0f/571b2c7a3833ae419fe69ff7b479a78d313581785203cc70a8db90121b9a/numpy-2.3.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:938065908d1d869c7d75d8ec45f735a034771c6ea07088867f713d1cd3bbbe4f", size = 16635989, upload-time = "2025-07-24T20:44:34.88Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/24/5a/84ae8dca9c9a4c592fe11340b36a86ffa9fd3e40513198daf8a97839345c/numpy-2.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:66459dccc65d8ec98cc7df61307b64bf9e08101f9598755d42d8ae65d9a7a6ee", size = 16053052, upload-time = "2025-07-24T20:44:58.872Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/57/7c/e5725d99a9133b9813fcf148d3f858df98511686e853169dbaf63aec6097/numpy-2.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a7af9ed2aa9ec5950daf05bb11abc4076a108bd3c7db9aa7251d5f107079b6a6", size = 18577955, upload-time = "2025-07-24T20:45:26.714Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ae/11/7c546fcf42145f29b71e4d6f429e96d8d68e5a7ba1830b2e68d7418f0bbd/numpy-2.3.2-cp313-cp313-win32.whl", hash = "sha256:906a30249315f9c8e17b085cc5f87d3f369b35fedd0051d4a84686967bdbbd0b", size = 6311843, upload-time = "2025-07-24T20:49:24.444Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/aa/6f/a428fd1cb7ed39b4280d057720fed5121b0d7754fd2a9768640160f5517b/numpy-2.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:c63d95dc9d67b676e9108fe0d2182987ccb0f11933c1e8959f42fa0da8d4fa56", size = 12782876, upload-time = "2025-07-24T20:49:43.227Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/65/85/4ea455c9040a12595fb6c43f2c217257c7b52dd0ba332c6a6c1d28b289fe/numpy-2.3.2-cp313-cp313-win_arm64.whl", hash = "sha256:b05a89f2fb84d21235f93de47129dd4f11c16f64c87c33f5e284e6a3a54e43f2", size = 10192786, upload-time = "2025-07-24T20:49:59.443Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/80/23/8278f40282d10c3f258ec3ff1b103d4994bcad78b0cba9208317f6bb73da/numpy-2.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4e6ecfeddfa83b02318f4d84acf15fbdbf9ded18e46989a15a8b6995dfbf85ab", size = 21047395, upload-time = "2025-07-24T20:45:58.821Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1f/2d/624f2ce4a5df52628b4ccd16a4f9437b37c35f4f8a50d00e962aae6efd7a/numpy-2.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:508b0eada3eded10a3b55725b40806a4b855961040180028f52580c4729916a2", size = 14300374, upload-time = "2025-07-24T20:46:20.207Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f6/62/ff1e512cdbb829b80a6bd08318a58698867bca0ca2499d101b4af063ee97/numpy-2.3.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:754d6755d9a7588bdc6ac47dc4ee97867271b17cee39cb87aef079574366db0a", size = 5228864, upload-time = "2025-07-24T20:46:30.58Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7d/8e/74bc18078fff03192d4032cfa99d5a5ca937807136d6f5790ce07ca53515/numpy-2.3.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a9f66e7d2b2d7712410d3bc5684149040ef5f19856f20277cd17ea83e5006286", size = 6737533, upload-time = "2025-07-24T20:46:46.111Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/19/ea/0731efe2c9073ccca5698ef6a8c3667c4cf4eea53fcdcd0b50140aba03bc/numpy-2.3.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de6ea4e5a65d5a90c7d286ddff2b87f3f4ad61faa3db8dabe936b34c2275b6f8", size = 14352007, upload-time = "2025-07-24T20:47:07.1Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cf/90/36be0865f16dfed20f4bc7f75235b963d5939707d4b591f086777412ff7b/numpy-2.3.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3ef07ec8cbc8fc9e369c8dcd52019510c12da4de81367d8b20bc692aa07573a", size = 16701914, upload-time = "2025-07-24T20:47:32.459Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/94/30/06cd055e24cb6c38e5989a9e747042b4e723535758e6153f11afea88c01b/numpy-2.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:27c9f90e7481275c7800dc9c24b7cc40ace3fdb970ae4d21eaff983a32f70c91", size = 16132708, upload-time = "2025-07-24T20:47:58.129Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9a/14/ecede608ea73e58267fd7cb78f42341b3b37ba576e778a1a06baffbe585c/numpy-2.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:07b62978075b67eee4065b166d000d457c82a1efe726cce608b9db9dd66a73a5", size = 18651678, upload-time = "2025-07-24T20:48:25.402Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/40/f3/2fe6066b8d07c3685509bc24d56386534c008b462a488b7f503ba82b8923/numpy-2.3.2-cp313-cp313t-win32.whl", hash = "sha256:c771cfac34a4f2c0de8e8c97312d07d64fd8f8ed45bc9f5726a7e947270152b5", size = 6441832, upload-time = "2025-07-24T20:48:37.181Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0b/ba/0937d66d05204d8f28630c9c60bc3eda68824abde4cf756c4d6aad03b0c6/numpy-2.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:72dbebb2dcc8305c431b2836bcc66af967df91be793d63a24e3d9b741374c450", size = 12927049, upload-time = "2025-07-24T20:48:56.24Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e9/ed/13542dd59c104d5e654dfa2ac282c199ba64846a74c2c4bcdbc3a0f75df1/numpy-2.3.2-cp313-cp313t-win_arm64.whl", hash = "sha256:72c6df2267e926a6d5286b0a6d556ebe49eae261062059317837fda12ddf0c1a", size = 10262935, upload-time = "2025-07-24T20:49:13.136Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c9/7c/7659048aaf498f7611b783e000c7268fcc4dcf0ce21cd10aad7b2e8f9591/numpy-2.3.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:448a66d052d0cf14ce9865d159bfc403282c9bc7bb2a31b03cc18b651eca8b1a", size = 20950906, upload-time = "2025-07-24T20:50:30.346Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/80/db/984bea9d4ddf7112a04cfdfb22b1050af5757864cfffe8e09e44b7f11a10/numpy-2.3.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:546aaf78e81b4081b2eba1d105c3b34064783027a06b3ab20b6eba21fb64132b", size = 14185607, upload-time = "2025-07-24T20:50:51.923Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e4/76/b3d6f414f4eca568f469ac112a3b510938d892bc5a6c190cb883af080b77/numpy-2.3.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:87c930d52f45df092f7578889711a0768094debf73cfcde105e2d66954358125", size = 5114110, upload-time = "2025-07-24T20:51:01.041Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9e/d2/6f5e6826abd6bca52392ed88fe44a4b52aacb60567ac3bc86c67834c3a56/numpy-2.3.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:8dc082ea901a62edb8f59713c6a7e28a85daddcb67454c839de57656478f5b19", size = 6642050, upload-time = "2025-07-24T20:51:11.64Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c4/43/f12b2ade99199e39c73ad182f103f9d9791f48d885c600c8e05927865baf/numpy-2.3.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:af58de8745f7fa9ca1c0c7c943616c6fe28e75d0c81f5c295810e3c83b5be92f", size = 14296292, upload-time = "2025-07-24T20:51:33.488Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5d/f9/77c07d94bf110a916b17210fac38680ed8734c236bfed9982fd8524a7b47/numpy-2.3.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed5527c4cf10f16c6d0b6bee1f89958bccb0ad2522c8cadc2efd318bcd545f5", size = 16638913, upload-time = "2025-07-24T20:51:58.517Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9b/d1/9d9f2c8ea399cc05cfff8a7437453bd4e7d894373a93cdc46361bbb49a7d/numpy-2.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:095737ed986e00393ec18ec0b21b47c22889ae4b0cd2d5e88342e08b01141f58", size = 16071180, upload-time = "2025-07-24T20:52:22.827Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4c/41/82e2c68aff2a0c9bf315e47d61951099fed65d8cb2c8d9dc388cb87e947e/numpy-2.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5e40e80299607f597e1a8a247ff8d71d79c5b52baa11cc1cce30aa92d2da6e0", size = 18576809, upload-time = "2025-07-24T20:52:51.015Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/14/14/4b4fd3efb0837ed252d0f583c5c35a75121038a8c4e065f2c259be06d2d8/numpy-2.3.2-cp314-cp314-win32.whl", hash = "sha256:7d6e390423cc1f76e1b8108c9b6889d20a7a1f59d9a60cac4a050fa734d6c1e2", size = 6366410, upload-time = "2025-07-24T20:56:44.949Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/11/9e/b4c24a6b8467b61aced5c8dc7dcfce23621baa2e17f661edb2444a418040/numpy-2.3.2-cp314-cp314-win_amd64.whl", hash = "sha256:b9d0878b21e3918d76d2209c924ebb272340da1fb51abc00f986c258cd5e957b", size = 12918821, upload-time = "2025-07-24T20:57:06.479Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0e/0f/0dc44007c70b1007c1cef86b06986a3812dd7106d8f946c09cfa75782556/numpy-2.3.2-cp314-cp314-win_arm64.whl", hash = "sha256:2738534837c6a1d0c39340a190177d7d66fdf432894f469728da901f8f6dc910", size = 10477303, upload-time = "2025-07-24T20:57:22.879Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8b/3e/075752b79140b78ddfc9c0a1634d234cfdbc6f9bbbfa6b7504e445ad7d19/numpy-2.3.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:4d002ecf7c9b53240be3bb69d80f86ddbd34078bae04d87be81c1f58466f264e", size = 21047524, upload-time = "2025-07-24T20:53:22.086Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fe/6d/60e8247564a72426570d0e0ea1151b95ce5bd2f1597bb878a18d32aec855/numpy-2.3.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:293b2192c6bcce487dbc6326de5853787f870aeb6c43f8f9c6496db5b1781e45", size = 14300519, upload-time = "2025-07-24T20:53:44.053Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4d/73/d8326c442cd428d47a067070c3ac6cc3b651a6e53613a1668342a12d4479/numpy-2.3.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:0a4f2021a6da53a0d580d6ef5db29947025ae8b35b3250141805ea9a32bbe86b", size = 5228972, upload-time = "2025-07-24T20:53:53.81Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/34/2e/e71b2d6dad075271e7079db776196829019b90ce3ece5c69639e4f6fdc44/numpy-2.3.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9c144440db4bf3bb6372d2c3e49834cc0ff7bb4c24975ab33e01199e645416f2", size = 6737439, upload-time = "2025-07-24T20:54:04.742Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/15/b0/d004bcd56c2c5e0500ffc65385eb6d569ffd3363cb5e593ae742749b2daa/numpy-2.3.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f92d6c2a8535dc4fe4419562294ff957f83a16ebdec66df0805e473ffaad8bd0", size = 14352479, upload-time = "2025-07-24T20:54:25.819Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/11/e3/285142fcff8721e0c99b51686426165059874c150ea9ab898e12a492e291/numpy-2.3.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cefc2219baa48e468e3db7e706305fcd0c095534a192a08f31e98d83a7d45fb0", size = 16702805, upload-time = "2025-07-24T20:54:50.814Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/33/c3/33b56b0e47e604af2c7cd065edca892d180f5899599b76830652875249a3/numpy-2.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:76c3e9501ceb50b2ff3824c3589d5d1ab4ac857b0ee3f8f49629d0de55ecf7c2", size = 16133830, upload-time = "2025-07-24T20:55:17.306Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6e/ae/7b1476a1f4d6a48bc669b8deb09939c56dd2a439db1ab03017844374fb67/numpy-2.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:122bf5ed9a0221b3419672493878ba4967121514b1d7d4656a7580cd11dddcbf", size = 18652665, upload-time = "2025-07-24T20:55:46.665Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/14/ba/5b5c9978c4bb161034148ade2de9db44ec316fab89ce8c400db0e0c81f86/numpy-2.3.2-cp314-cp314t-win32.whl", hash = "sha256:6f1ae3dcb840edccc45af496f312528c15b1f79ac318169d094e85e4bb35fdf1", size = 6514777, upload-time = "2025-07-24T20:55:57.66Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/eb/46/3dbaf0ae7c17cdc46b9f662c56da2054887b8d9e737c1476f335c83d33db/numpy-2.3.2-cp314-cp314t-win_amd64.whl", hash = "sha256:087ffc25890d89a43536f75c5fe8770922008758e8eeeef61733957041ed2f9b", size = 13111856, upload-time = "2025-07-24T20:56:17.318Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c1/9e/1652778bce745a67b5fe05adde60ed362d38eb17d919a540e813d30f6874/numpy-2.3.2-cp314-cp314t-win_arm64.whl", hash = "sha256:092aeb3449833ea9c0bf0089d70c29ae480685dd2377ec9cdbbb620257f84631", size = 10544226, upload-time = "2025-07-24T20:56:34.509Z" }, +] + +[[package]] +name = "nvidia-cublas-cu12" +version = "12.8.4.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/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" }, +] + +[[package]] +name = "nvidia-cuda-cupti-cu12" +version = "12.8.90" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/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" }, +] + +[[package]] +name = "nvidia-cuda-nvrtc-cu12" +version = "12.8.93" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/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" }, +] + +[[package]] +name = "nvidia-cuda-runtime-cu12" +version = "12.8.90" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/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" }, +] + +[[package]] +name = "nvidia-cudnn-cu12" +version = "9.10.2.21" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "nvidia-cublas-cu12" }, +] +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/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" }, +] + +[[package]] +name = "nvidia-cufft-cu12" +version = "11.3.3.83" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "nvidia-nvjitlink-cu12" }, +] +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/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" }, +] + +[[package]] +name = "nvidia-cufile-cu12" +version = "1.13.1.3" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/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" }, +] + +[[package]] +name = "nvidia-curand-cu12" +version = "10.3.9.90" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/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" }, +] + +[[package]] +name = "nvidia-cusolver-cu12" +version = "11.7.3.90" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "nvidia-cublas-cu12" }, + { name = "nvidia-cusparse-cu12" }, + { name = "nvidia-nvjitlink-cu12" }, +] +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/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" }, +] + +[[package]] +name = "nvidia-cusparse-cu12" +version = "12.5.8.93" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "nvidia-nvjitlink-cu12" }, +] +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/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" }, +] + +[[package]] +name = "nvidia-cusparselt-cu12" +version = "0.7.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/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" }, +] + +[[package]] +name = "nvidia-nccl-cu12" +version = "2.27.3" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5c/5b/4e4fff7bad39adf89f735f2bc87248c81db71205b62bcc0d5ca5b606b3c3/nvidia_nccl_cu12-2.27.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:adf27ccf4238253e0b826bce3ff5fa532d65fc42322c8bfdfaf28024c0fbe039", size = 322364134, upload-time = "2025-06-03T21:58:04.013Z" }, +] + +[[package]] +name = "nvidia-nvjitlink-cu12" +version = "12.8.93" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/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" }, +] + +[[package]] +name = "nvidia-nvtx-cu12" +version = "12.8.90" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/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" }, +] + +[[package]] +name = "orjson" +version = "3.11.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/19/3b/fd9ff8ff64ae3900f11554d5cfc835fb73e501e043c420ad32ec574fe27f/orjson-3.11.1.tar.gz", hash = "sha256:48d82770a5fd88778063604c566f9c7c71820270c9cc9338d25147cbf34afd96", size = 5393373, upload-time = "2025-07-25T14:33:52.898Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/98/77/e55513826b712807caadb2b733eee192c1df105c6bbf0d965c253b72f124/orjson-3.11.1-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:2b7c8be96db3a977367250c6367793a3c5851a6ca4263f92f0b48d00702f9910", size = 240955, upload-time = "2025-07-25T14:32:34.056Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c9/88/a78132dddcc9c3b80a9fa050b3516bb2c996a9d78ca6fb47c8da2a80a696/orjson-3.11.1-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:72e18088f567bd4a45db5e3196677d9ed1605e356e500c8e32dd6e303167a13d", size = 129294, upload-time = "2025-07-25T14:32:35.323Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/09/02/6591e0dcb2af6bceea96cb1b5f4b48c1445492a3ef2891ac4aa306bb6f73/orjson-3.11.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d346e2ae1ce17888f7040b65a5a4a0c9734cb20ffbd228728661e020b4c8b3a5", size = 132310, upload-time = "2025-07-25T14:32:36.53Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e9/36/c1cfbc617bcfa4835db275d5e0fe9bbdbe561a4b53d3b2de16540ec29c50/orjson-3.11.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4bda5426ebb02ceb806a7d7ec9ba9ee5e0c93fca62375151a7b1c00bc634d06b", size = 128529, upload-time = "2025-07-25T14:32:37.817Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7c/bd/91a156c5df3aaf1d68b2ab5be06f1969955a8d3e328d7794f4338ac1d017/orjson-3.11.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10506cebe908542c4f024861102673db534fd2e03eb9b95b30d94438fa220abf", size = 130925, upload-time = "2025-07-25T14:32:39.03Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a3/4c/a65cc24e9a5f87c9833a50161ab97b5edbec98bec99dfbba13827549debc/orjson-3.11.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45202ee3f5494644e064c41abd1320497fb92fd31fc73af708708af664ac3b56", size = 132432, upload-time = "2025-07-25T14:32:40.619Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2e/4d/3fc3e5d7115f4f7d01b481e29e5a79bcbcc45711a2723242787455424f40/orjson-3.11.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5adaf01b92e0402a9ac5c3ebe04effe2bbb115f0914a0a53d34ea239a746289", size = 135069, upload-time = "2025-07-25T14:32:41.84Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/dc/c6/7585aa8522af896060dc0cd7c336ba6c574ae854416811ee6642c505cc95/orjson-3.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6162a1a757a1f1f4a94bc6ffac834a3602e04ad5db022dd8395a54ed9dd51c81", size = 131045, upload-time = "2025-07-25T14:32:43.085Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6a/4e/b8a0a943793d2708ebc39e743c943251e08ee0f3279c880aefd8e9cb0c70/orjson-3.11.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:78404206977c9f946613d3f916727c189d43193e708d760ea5d4b2087d6b0968", size = 130597, upload-time = "2025-07-25T14:32:44.336Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/72/2b/7d30e2aed2f585d5d385fb45c71d9b16ba09be58c04e8767ae6edc6c9282/orjson-3.11.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:db48f8e81072e26df6cdb0e9fff808c28597c6ac20a13d595756cf9ba1fed48a", size = 404207, upload-time = "2025-07-25T14:32:45.612Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1b/7e/772369ec66fcbce79477f0891918309594cd00e39b67a68d4c445d2ab754/orjson-3.11.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0c1e394e67ced6bb16fea7054d99fbdd99a539cf4d446d40378d4c06e0a8548d", size = 146628, upload-time = "2025-07-25T14:32:46.981Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b4/c8/62bdb59229d7e393ae309cef41e32cc1f0b567b21dfd0742da70efb8b40c/orjson-3.11.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e7a840752c93d4eecd1378e9bb465c3703e127b58f675cd5c620f361b6cf57a4", size = 135449, upload-time = "2025-07-25T14:32:48.727Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/02/47/1c99aa60e19f781424eabeaacd9e999eafe5b59c81ead4273b773f0f3af1/orjson-3.11.1-cp312-cp312-win32.whl", hash = "sha256:4537b0e09f45d2b74cb69c7f39ca1e62c24c0488d6bf01cd24673c74cd9596bf", size = 136653, upload-time = "2025-07-25T14:32:50.622Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/31/9a/132999929a2892ab07e916669accecc83e5bff17e11a1186b4c6f23231f0/orjson-3.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:dbee6b050062540ae404530cacec1bf25e56e8d87d8d9b610b935afeb6725cae", size = 131426, upload-time = "2025-07-25T14:32:51.883Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9c/77/d984ee5a1ca341090902e080b187721ba5d1573a8d9759e0c540975acfb2/orjson-3.11.1-cp312-cp312-win_arm64.whl", hash = "sha256:f55e557d4248322d87c4673e085c7634039ff04b47bfc823b87149ae12bef60d", size = 126635, upload-time = "2025-07-25T14:32:53.2Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c9/e9/880ef869e6f66279ce3a381a32afa0f34e29a94250146911eee029e56efc/orjson-3.11.1-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:53cfefe4af059e65aabe9683f76b9c88bf34b4341a77d329227c2424e0e59b0e", size = 240835, upload-time = "2025-07-25T14:32:54.507Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f0/1f/52039ef3d03eeea21763b46bc99ebe11d9de8510c72b7b5569433084a17e/orjson-3.11.1-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:93d5abed5a6f9e1b6f9b5bf6ed4423c11932b5447c2f7281d3b64e0f26c6d064", size = 129226, upload-time = "2025-07-25T14:32:55.908Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ee/da/59fdffc9465a760be2cd3764ef9cd5535eec8f095419f972fddb123b6d0e/orjson-3.11.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dbf06642f3db2966df504944cdd0eb68ca2717f0353bb20b20acd78109374a6", size = 132261, upload-time = "2025-07-25T14:32:57.538Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bb/5c/8610911c7e969db7cf928c8baac4b2f1e68d314bc3057acf5ca64f758435/orjson-3.11.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dddf4e78747fa7f2188273f84562017a3c4f0824485b78372513c1681ea7a894", size = 128614, upload-time = "2025-07-25T14:32:58.808Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f7/a1/a1db9d4310d014c90f3b7e9b72c6fb162cba82c5f46d0b345669eaebdd3a/orjson-3.11.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fa3fe8653c9f57f0e16f008e43626485b6723b84b2f741f54d1258095b655912", size = 130968, upload-time = "2025-07-25T14:33:00.038Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/56/ff/11acd1fd7c38ea7a1b5d6bf582ae3da05931bee64620995eb08fd63c77fe/orjson-3.11.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6334d2382aff975a61f6f4d1c3daf39368b887c7de08f7c16c58f485dcf7adb2", size = 132439, upload-time = "2025-07-25T14:33:01.354Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/70/f9/bb564dd9450bf8725e034a8ad7f4ae9d4710a34caf63b85ce1c0c6d40af0/orjson-3.11.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a3d0855b643f259ee0cb76fe3df4c04483354409a520a902b067c674842eb6b8", size = 135299, upload-time = "2025-07-25T14:33:03.079Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/94/bb/c8eafe6051405e241dda3691db4d9132d3c3462d1d10a17f50837dd130b4/orjson-3.11.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0eacdfeefd0a79987926476eb16e0245546bedeb8febbbbcf4b653e79257a8e4", size = 131004, upload-time = "2025-07-25T14:33:04.416Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a2/40/bed8d7dcf1bd2df8813bf010a25f645863a2f75e8e0ebdb2b55784cf1a62/orjson-3.11.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0ed07faf9e4873518c60480325dcbc16d17c59a165532cccfb409b4cdbaeff24", size = 130583, upload-time = "2025-07-25T14:33:05.768Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/57/e7/cfa2eb803ad52d74fbb5424a429b5be164e51d23f1d853e5e037173a5c48/orjson-3.11.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:d6d308dd578ae3658f62bb9eba54801533225823cd3248c902be1ebc79b5e014", size = 404218, upload-time = "2025-07-25T14:33:07.117Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d5/21/bc703af5bc6e9c7e18dcf4404dcc4ec305ab9bb6c82d3aee5952c0c56abf/orjson-3.11.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c4aa13ca959ba6b15c0a98d3d204b850f9dc36c08c9ce422ffb024eb30d6e058", size = 146605, upload-time = "2025-07-25T14:33:08.55Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8f/fe/d26a0150534c4965a06f556aa68bf3c3b82999d5d7b0facd3af7b390c4af/orjson-3.11.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:be3d0653322abc9b68e5bcdaee6cfd58fcbe9973740ab222b87f4d687232ab1f", size = 135434, upload-time = "2025-07-25T14:33:09.967Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/89/b6/1cb28365f08cbcffc464f8512320c6eb6db6a653f03d66de47ea3c19385f/orjson-3.11.1-cp313-cp313-win32.whl", hash = "sha256:4dd34e7e2518de8d7834268846f8cab7204364f427c56fb2251e098da86f5092", size = 136596, upload-time = "2025-07-25T14:33:11.333Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f9/35/7870d0d3ed843652676d84d8a6038791113eacc85237b673b925802826b8/orjson-3.11.1-cp313-cp313-win_amd64.whl", hash = "sha256:d6895d32032b6362540e6d0694b19130bb4f2ad04694002dce7d8af588ca5f77", size = 131319, upload-time = "2025-07-25T14:33:12.614Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b7/3e/5bcd50fd865eb664d4edfdaaaff51e333593ceb5695a22c0d0a0d2b187ba/orjson-3.11.1-cp313-cp313-win_arm64.whl", hash = "sha256:bb7c36d5d3570fcbb01d24fa447a21a7fe5a41141fd88e78f7994053cc4e28f4", size = 126613, upload-time = "2025-07-25T14:33:13.927Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/61/d8/0a5cd31ed100b4e569e143cb0cddefc21f0bcb8ce284f44bca0bb0e10f3d/orjson-3.11.1-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:7b71ef394327b3d0b39f6ea7ade2ecda2731a56c6a7cbf0d6a7301203b92a89b", size = 240819, upload-time = "2025-07-25T14:33:15.223Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b9/95/7eb2c76c92192ceca16bc81845ff100bbb93f568b4b94d914b6a4da47d61/orjson-3.11.1-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:77c0fe28ed659b62273995244ae2aa430e432c71f86e4573ab16caa2f2e3ca5e", size = 129218, upload-time = "2025-07-25T14:33:16.637Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/da/84/e6b67f301b18adbbc346882f456bea44daebbd032ba725dbd7b741e3a7f1/orjson-3.11.1-cp314-cp314-manylinux_2_34_aarch64.whl", hash = "sha256:1495692f1f1ba2467df429343388a0ed259382835922e124c0cfdd56b3d1f727", size = 132238, upload-time = "2025-07-25T14:33:17.934Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/84/78/a45a86e29d9b2f391f9d00b22da51bc4b46b86b788fd42df2c5fcf3e8005/orjson-3.11.1-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:08c6a762fca63ca4dc04f66c48ea5d2428db55839fec996890e1bfaf057b658c", size = 130998, upload-time = "2025-07-25T14:33:19.282Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ea/8f/6eb3ee6760d93b2ce996a8529164ee1f5bafbdf64b74c7314b68db622b32/orjson-3.11.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9e26794fe3976810b2c01fda29bd9ac7c91a3c1284b29cc9a383989f7b614037", size = 130559, upload-time = "2025-07-25T14:33:20.589Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1b/78/9572ae94bdba6813917c9387e7834224c011ea6b4530ade07d718fd31598/orjson-3.11.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:4b4b4f8f0b1d3ef8dc73e55363a0ffe012a42f4e2f1a140bf559698dca39b3fa", size = 404231, upload-time = "2025-07-25T14:33:22.019Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1f/a3/68381ad0757e084927c5ee6cfdeab1c6c89405949ee493db557e60871c4c/orjson-3.11.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:848be553ea35aa89bfefbed2e27c8a41244c862956ab8ba00dc0b27e84fd58de", size = 146658, upload-time = "2025-07-25T14:33:23.675Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/00/db/fac56acf77aab778296c3f541a3eec643266f28ecd71d6c0cba251e47655/orjson-3.11.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c964c29711a4b1df52f8d9966f015402a6cf87753a406c1c4405c407dd66fd45", size = 135443, upload-time = "2025-07-25T14:33:25.04Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/76/b1/326fa4b87426197ead61c1eec2eeb3babc9eb33b480ac1f93894e40c8c08/orjson-3.11.1-cp314-cp314-win32.whl", hash = "sha256:33aada2e6b6bc9c540d396528b91e666cedb383740fee6e6a917f561b390ecb1", size = 136643, upload-time = "2025-07-25T14:33:26.449Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0f/8e/2987ae2109f3bfd39680f8a187d1bc09ad7f8fb019dcdc719b08c7242ade/orjson-3.11.1-cp314-cp314-win_amd64.whl", hash = "sha256:68e10fd804e44e36188b9952543e3fa22f5aa8394da1b5283ca2b423735c06e8", size = 131324, upload-time = "2025-07-25T14:33:27.896Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/21/5f/253e08e6974752b124fbf3a4de3ad53baa766b0cb4a333d47706d307e396/orjson-3.11.1-cp314-cp314-win_arm64.whl", hash = "sha256:f3cf6c07f8b32127d836be8e1c55d4f34843f7df346536da768e9f73f22078a1", size = 126605, upload-time = "2025-07-25T14:33:29.244Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pandas" +version = "2.3.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d1/6f/75aa71f8a14267117adeeed5d21b204770189c0a0025acbdc03c337b28fc/pandas-2.3.1.tar.gz", hash = "sha256:0a95b9ac964fe83ce317827f80304d37388ea77616b1425f0ae41c9d2d0d7bb2", size = 4487493, upload-time = "2025-07-07T19:20:04.079Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/46/de/b8445e0f5d217a99fe0eeb2f4988070908979bec3587c0633e5428ab596c/pandas-2.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:689968e841136f9e542020698ee1c4fbe9caa2ed2213ae2388dc7b81721510d3", size = 11588172, upload-time = "2025-07-07T19:18:52.054Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1e/e0/801cdb3564e65a5ac041ab99ea6f1d802a6c325bb6e58c79c06a3f1cd010/pandas-2.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:025e92411c16cbe5bb2a4abc99732a6b132f439b8aab23a59fa593eb00704232", size = 10717365, upload-time = "2025-07-07T19:18:54.785Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/51/a5/c76a8311833c24ae61a376dbf360eb1b1c9247a5d9c1e8b356563b31b80c/pandas-2.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b7ff55f31c4fcb3e316e8f7fa194566b286d6ac430afec0d461163312c5841e", size = 11280411, upload-time = "2025-07-07T19:18:57.045Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/da/01/e383018feba0a1ead6cf5fe8728e5d767fee02f06a3d800e82c489e5daaf/pandas-2.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7dcb79bf373a47d2a40cf7232928eb7540155abbc460925c2c96d2d30b006eb4", size = 11988013, upload-time = "2025-07-07T19:18:59.771Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5b/14/cec7760d7c9507f11c97d64f29022e12a6cc4fc03ac694535e89f88ad2ec/pandas-2.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:56a342b231e8862c96bdb6ab97170e203ce511f4d0429589c8ede1ee8ece48b8", size = 12767210, upload-time = "2025-07-07T19:19:02.944Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/50/b9/6e2d2c6728ed29fb3d4d4d302504fb66f1a543e37eb2e43f352a86365cdf/pandas-2.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ca7ed14832bce68baef331f4d7f294411bed8efd032f8109d690df45e00c4679", size = 13440571, upload-time = "2025-07-07T19:19:06.82Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/80/a5/3a92893e7399a691bad7664d977cb5e7c81cf666c81f89ea76ba2bff483d/pandas-2.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:ac942bfd0aca577bef61f2bc8da8147c4ef6879965ef883d8e8d5d2dc3e744b8", size = 10987601, upload-time = "2025-07-07T19:19:09.589Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/32/ed/ff0a67a2c5505e1854e6715586ac6693dd860fbf52ef9f81edee200266e7/pandas-2.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9026bd4a80108fac2239294a15ef9003c4ee191a0f64b90f170b40cfb7cf2d22", size = 11531393, upload-time = "2025-07-07T19:19:12.245Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c7/db/d8f24a7cc9fb0972adab0cc80b6817e8bef888cfd0024eeb5a21c0bb5c4a/pandas-2.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6de8547d4fdb12421e2d047a2c446c623ff4c11f47fddb6b9169eb98ffba485a", size = 10668750, upload-time = "2025-07-07T19:19:14.612Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0f/b0/80f6ec783313f1e2356b28b4fd8d2148c378370045da918c73145e6aab50/pandas-2.3.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:782647ddc63c83133b2506912cc6b108140a38a37292102aaa19c81c83db2928", size = 11342004, upload-time = "2025-07-07T19:19:16.857Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e9/e2/20a317688435470872885e7fc8f95109ae9683dec7c50be29b56911515a5/pandas-2.3.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ba6aff74075311fc88504b1db890187a3cd0f887a5b10f5525f8e2ef55bfdb9", size = 12050869, upload-time = "2025-07-07T19:19:19.265Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/55/79/20d746b0a96c67203a5bee5fb4e00ac49c3e8009a39e1f78de264ecc5729/pandas-2.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e5635178b387bd2ba4ac040f82bc2ef6e6b500483975c4ebacd34bec945fda12", size = 12750218, upload-time = "2025-07-07T19:19:21.547Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7c/0f/145c8b41e48dbf03dd18fdd7f24f8ba95b8254a97a3379048378f33e7838/pandas-2.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6f3bf5ec947526106399a9e1d26d40ee2b259c66422efdf4de63c848492d91bb", size = 13416763, upload-time = "2025-07-07T19:19:23.939Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b2/c0/54415af59db5cdd86a3d3bf79863e8cc3fa9ed265f0745254061ac09d5f2/pandas-2.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:1c78cf43c8fde236342a1cb2c34bcff89564a7bfed7e474ed2fffa6aed03a956", size = 10987482, upload-time = "2025-07-07T19:19:42.699Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/48/64/2fd2e400073a1230e13b8cd604c9bc95d9e3b962e5d44088ead2e8f0cfec/pandas-2.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8dfc17328e8da77be3cf9f47509e5637ba8f137148ed0e9b5241e1baf526e20a", size = 12029159, upload-time = "2025-07-07T19:19:26.362Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d8/0a/d84fd79b0293b7ef88c760d7dca69828d867c89b6d9bc52d6a27e4d87316/pandas-2.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ec6c851509364c59a5344458ab935e6451b31b818be467eb24b0fe89bd05b6b9", size = 11393287, upload-time = "2025-07-07T19:19:29.157Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/50/ae/ff885d2b6e88f3c7520bb74ba319268b42f05d7e583b5dded9837da2723f/pandas-2.3.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:911580460fc4884d9b05254b38a6bfadddfcc6aaef856fb5859e7ca202e45275", size = 11309381, upload-time = "2025-07-07T19:19:31.436Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/85/86/1fa345fc17caf5d7780d2699985c03dbe186c68fee00b526813939062bb0/pandas-2.3.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f4d6feeba91744872a600e6edbbd5b033005b431d5ae8379abee5bcfa479fab", size = 11883998, upload-time = "2025-07-07T19:19:34.267Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/81/aa/e58541a49b5e6310d89474333e994ee57fea97c8aaa8fc7f00b873059bbf/pandas-2.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fe37e757f462d31a9cd7580236a82f353f5713a80e059a29753cf938c6775d96", size = 12704705, upload-time = "2025-07-07T19:19:36.856Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d5/f9/07086f5b0f2a19872554abeea7658200824f5835c58a106fa8f2ae96a46c/pandas-2.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5db9637dbc24b631ff3707269ae4559bce4b7fd75c1c4d7e13f40edc42df4444", size = 13189044, upload-time = "2025-07-07T19:19:39.999Z" }, +] + +[[package]] +name = "pathvalidate" +version = "3.3.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fa/2a/52a8da6fe965dea6192eb716b357558e103aea0a1e9a8352ad575a8406ca/pathvalidate-3.3.1.tar.gz", hash = "sha256:b18c07212bfead624345bb8e1d6141cdcf15a39736994ea0b94035ad2b1ba177", size = 63262, upload-time = "2025-06-15T09:07:20.736Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9a/70/875f4a23bfc4731703a5835487d0d2fb999031bd415e7d17c0ae615c18b7/pathvalidate-3.3.1-py3-none-any.whl", hash = "sha256:5263baab691f8e1af96092fa5137ee17df5bdfbd6cff1fcac4d6ef4bc2e1735f", size = 24305, upload-time = "2025-06-15T09:07:19.117Z" }, +] + +[[package]] +name = "peft" +version = "0.17.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "accelerate" }, + { name = "huggingface-hub" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "psutil" }, + { name = "pyyaml" }, + { name = "safetensors" }, + { name = "torch" }, + { name = "tqdm" }, + { name = "transformers" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/62/60/65efe0186e3d4cf52c187f56daf6ebaac162f3166f4f5c8f65f72240658f/peft-0.17.0.tar.gz", hash = "sha256:cb647a931c8da434446d6a196c402b1c2d087b12e050e098215f906f12771f1e", size = 565680, upload-time = "2025-08-01T17:04:29.475Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/80/da/36118931e155e7f403889be1e20054fffc9bb48a8e27e2c34b0f7d42c6d8/peft-0.17.0-py3-none-any.whl", hash = "sha256:0190c28bdbdc24c3de549f2b42cd182cbb89d3e82aa3069c6db690c4ef6fccbb", size = 503901, upload-time = "2025-08-01T17:04:27.256Z" }, +] + +[[package]] +name = "pillow" +version = "11.3.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f3/0d/d0d6dea55cd152ce3d6767bb38a8fc10e33796ba4ba210cbab9354b6d238/pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523", size = 47113069, upload-time = "2025-07-01T09:16:30.666Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/40/fe/1bc9b3ee13f68487a99ac9529968035cca2f0a51ec36892060edcc51d06a/pillow-11.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4", size = 5278800, upload-time = "2025-07-01T09:14:17.648Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2c/32/7e2ac19b5713657384cec55f89065fb306b06af008cfd87e572035b27119/pillow-11.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69", size = 4686296, upload-time = "2025-07-01T09:14:19.828Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8e/1e/b9e12bbe6e4c2220effebc09ea0923a07a6da1e1f1bfbc8d7d29a01ce32b/pillow-11.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb76541cba2f958032d79d143b98a3a6b3ea87f0959bbe256c0b5e416599fd5d", size = 5871726, upload-time = "2025-07-03T13:10:04.448Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8d/33/e9200d2bd7ba00dc3ddb78df1198a6e80d7669cce6c2bdbeb2530a74ec58/pillow-11.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67172f2944ebba3d4a7b54f2e95c786a3a50c21b88456329314caaa28cda70f6", size = 7644652, upload-time = "2025-07-03T13:10:10.391Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/41/f1/6f2427a26fc683e00d985bc391bdd76d8dd4e92fac33d841127eb8fb2313/pillow-11.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7", size = 5977787, upload-time = "2025-07-01T09:14:21.63Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e4/c9/06dd4a38974e24f932ff5f98ea3c546ce3f8c995d3f0985f8e5ba48bba19/pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024", size = 6645236, upload-time = "2025-07-01T09:14:23.321Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/40/e7/848f69fb79843b3d91241bad658e9c14f39a32f71a301bcd1d139416d1be/pillow-11.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809", size = 6086950, upload-time = "2025-07-01T09:14:25.237Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0b/1a/7cff92e695a2a29ac1958c2a0fe4c0b2393b60aac13b04a4fe2735cad52d/pillow-11.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d", size = 6723358, upload-time = "2025-07-01T09:14:27.053Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/26/7d/73699ad77895f69edff76b0f332acc3d497f22f5d75e5360f78cbcaff248/pillow-11.3.0-cp312-cp312-win32.whl", hash = "sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149", size = 6275079, upload-time = "2025-07-01T09:14:30.104Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8c/ce/e7dfc873bdd9828f3b6e5c2bbb74e47a98ec23cc5c74fc4e54462f0d9204/pillow-11.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d", size = 6986324, upload-time = "2025-07-01T09:14:31.899Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/16/8f/b13447d1bf0b1f7467ce7d86f6e6edf66c0ad7cf44cf5c87a37f9bed9936/pillow-11.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542", size = 2423067, upload-time = "2025-07-01T09:14:33.709Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1e/93/0952f2ed8db3a5a4c7a11f91965d6184ebc8cd7cbb7941a260d5f018cd2d/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd", size = 2128328, upload-time = "2025-07-01T09:14:35.276Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4b/e8/100c3d114b1a0bf4042f27e0f87d2f25e857e838034e98ca98fe7b8c0a9c/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8", size = 2170652, upload-time = "2025-07-01T09:14:37.203Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/aa/86/3f758a28a6e381758545f7cdb4942e1cb79abd271bea932998fc0db93cb6/pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f", size = 2227443, upload-time = "2025-07-01T09:14:39.344Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/01/f4/91d5b3ffa718df2f53b0dc109877993e511f4fd055d7e9508682e8aba092/pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c", size = 5278474, upload-time = "2025-07-01T09:14:41.843Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f9/0e/37d7d3eca6c879fbd9dba21268427dffda1ab00d4eb05b32923d4fbe3b12/pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd", size = 4686038, upload-time = "2025-07-01T09:14:44.008Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ff/b0/3426e5c7f6565e752d81221af9d3676fdbb4f352317ceafd42899aaf5d8a/pillow-11.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e", size = 5864407, upload-time = "2025-07-03T13:10:15.628Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fc/c1/c6c423134229f2a221ee53f838d4be9d82bab86f7e2f8e75e47b6bf6cd77/pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1", size = 7639094, upload-time = "2025-07-03T13:10:21.857Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ba/c9/09e6746630fe6372c67c648ff9deae52a2bc20897d51fa293571977ceb5d/pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805", size = 5973503, upload-time = "2025-07-01T09:14:45.698Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d5/1c/a2a29649c0b1983d3ef57ee87a66487fdeb45132df66ab30dd37f7dbe162/pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8", size = 6642574, upload-time = "2025-07-01T09:14:47.415Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/36/de/d5cc31cc4b055b6c6fd990e3e7f0f8aaf36229a2698501bcb0cdf67c7146/pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2", size = 6084060, upload-time = "2025-07-01T09:14:49.636Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d5/ea/502d938cbaeec836ac28a9b730193716f0114c41325db428e6b280513f09/pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b", size = 6721407, upload-time = "2025-07-01T09:14:51.962Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/45/9c/9c5e2a73f125f6cbc59cc7087c8f2d649a7ae453f83bd0362ff7c9e2aee2/pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3", size = 6273841, upload-time = "2025-07-01T09:14:54.142Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/23/85/397c73524e0cd212067e0c969aa245b01d50183439550d24d9f55781b776/pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51", size = 6978450, upload-time = "2025-07-01T09:14:56.436Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/17/d2/622f4547f69cd173955194b78e4d19ca4935a1b0f03a302d655c9f6aae65/pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580", size = 2423055, upload-time = "2025-07-01T09:14:58.072Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/dd/80/a8a2ac21dda2e82480852978416cfacd439a4b490a501a288ecf4fe2532d/pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e", size = 5281110, upload-time = "2025-07-01T09:14:59.79Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/44/d6/b79754ca790f315918732e18f82a8146d33bcd7f4494380457ea89eb883d/pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d", size = 4689547, upload-time = "2025-07-01T09:15:01.648Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/49/20/716b8717d331150cb00f7fdd78169c01e8e0c219732a78b0e59b6bdb2fd6/pillow-11.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced", size = 5901554, upload-time = "2025-07-03T13:10:27.018Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/74/cf/a9f3a2514a65bb071075063a96f0a5cf949c2f2fce683c15ccc83b1c1cab/pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c", size = 7669132, upload-time = "2025-07-03T13:10:33.01Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/98/3c/da78805cbdbee9cb43efe8261dd7cc0b4b93f2ac79b676c03159e9db2187/pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8", size = 6005001, upload-time = "2025-07-01T09:15:03.365Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6c/fa/ce044b91faecf30e635321351bba32bab5a7e034c60187fe9698191aef4f/pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59", size = 6668814, upload-time = "2025-07-01T09:15:05.655Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7b/51/90f9291406d09bf93686434f9183aba27b831c10c87746ff49f127ee80cb/pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe", size = 6113124, upload-time = "2025-07-01T09:15:07.358Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cd/5a/6fec59b1dfb619234f7636d4157d11fb4e196caeee220232a8d2ec48488d/pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c", size = 6747186, upload-time = "2025-07-01T09:15:09.317Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/49/6b/00187a044f98255225f172de653941e61da37104a9ea60e4f6887717e2b5/pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788", size = 6277546, upload-time = "2025-07-01T09:15:11.311Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e8/5c/6caaba7e261c0d75bab23be79f1d06b5ad2a2ae49f028ccec801b0e853d6/pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31", size = 6985102, upload-time = "2025-07-01T09:15:13.164Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f3/7e/b623008460c09a0cb38263c93b828c666493caee2eb34ff67f778b87e58c/pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e", size = 2424803, upload-time = "2025-07-01T09:15:15.695Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/73/f4/04905af42837292ed86cb1b1dabe03dce1edc008ef14c473c5c7e1443c5d/pillow-11.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12", size = 5278520, upload-time = "2025-07-01T09:15:17.429Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/41/b0/33d79e377a336247df6348a54e6d2a2b85d644ca202555e3faa0cf811ecc/pillow-11.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a", size = 4686116, upload-time = "2025-07-01T09:15:19.423Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/49/2d/ed8bc0ab219ae8768f529597d9509d184fe8a6c4741a6864fea334d25f3f/pillow-11.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0743841cabd3dba6a83f38a92672cccbd69af56e3e91777b0ee7f4dba4385632", size = 5864597, upload-time = "2025-07-03T13:10:38.404Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b5/3d/b932bb4225c80b58dfadaca9d42d08d0b7064d2d1791b6a237f87f661834/pillow-11.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673", size = 7638246, upload-time = "2025-07-03T13:10:44.987Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/09/b5/0487044b7c096f1b48f0d7ad416472c02e0e4bf6919541b111efd3cae690/pillow-11.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027", size = 5973336, upload-time = "2025-07-01T09:15:21.237Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a8/2d/524f9318f6cbfcc79fbc004801ea6b607ec3f843977652fdee4857a7568b/pillow-11.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77", size = 6642699, upload-time = "2025-07-01T09:15:23.186Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6f/d2/a9a4f280c6aefedce1e8f615baaa5474e0701d86dd6f1dede66726462bbd/pillow-11.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874", size = 6083789, upload-time = "2025-07-01T09:15:25.1Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fe/54/86b0cd9dbb683a9d5e960b66c7379e821a19be4ac5810e2e5a715c09a0c0/pillow-11.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a", size = 6720386, upload-time = "2025-07-01T09:15:27.378Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e7/95/88efcaf384c3588e24259c4203b909cbe3e3c2d887af9e938c2022c9dd48/pillow-11.3.0-cp314-cp314-win32.whl", hash = "sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214", size = 6370911, upload-time = "2025-07-01T09:15:29.294Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2e/cc/934e5820850ec5eb107e7b1a72dd278140731c669f396110ebc326f2a503/pillow-11.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635", size = 7117383, upload-time = "2025-07-01T09:15:31.128Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d6/e9/9c0a616a71da2a5d163aa37405e8aced9a906d574b4a214bede134e731bc/pillow-11.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6", size = 2511385, upload-time = "2025-07-01T09:15:33.328Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1a/33/c88376898aff369658b225262cd4f2659b13e8178e7534df9e6e1fa289f6/pillow-11.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae", size = 5281129, upload-time = "2025-07-01T09:15:35.194Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1f/70/d376247fb36f1844b42910911c83a02d5544ebd2a8bad9efcc0f707ea774/pillow-11.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653", size = 4689580, upload-time = "2025-07-01T09:15:37.114Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/eb/1c/537e930496149fbac69efd2fc4329035bbe2e5475b4165439e3be9cb183b/pillow-11.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ee92f2fd10f4adc4b43d07ec5e779932b4eb3dbfbc34790ada5a6669bc095aa6", size = 5902860, upload-time = "2025-07-03T13:10:50.248Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bd/57/80f53264954dcefeebcf9dae6e3eb1daea1b488f0be8b8fef12f79a3eb10/pillow-11.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36", size = 7670694, upload-time = "2025-07-03T13:10:56.432Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/70/ff/4727d3b71a8578b4587d9c276e90efad2d6fe0335fd76742a6da08132e8c/pillow-11.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b", size = 6005888, upload-time = "2025-07-01T09:15:39.436Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/05/ae/716592277934f85d3be51d7256f3636672d7b1abfafdc42cf3f8cbd4b4c8/pillow-11.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477", size = 6670330, upload-time = "2025-07-01T09:15:41.269Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e7/bb/7fe6cddcc8827b01b1a9766f5fdeb7418680744f9082035bdbabecf1d57f/pillow-11.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50", size = 6114089, upload-time = "2025-07-01T09:15:43.13Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8b/f5/06bfaa444c8e80f1a8e4bff98da9c83b37b5be3b1deaa43d27a0db37ef84/pillow-11.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b", size = 6748206, upload-time = "2025-07-01T09:15:44.937Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f0/77/bc6f92a3e8e6e46c0ca78abfffec0037845800ea38c73483760362804c41/pillow-11.3.0-cp314-cp314t-win32.whl", hash = "sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12", size = 6377370, upload-time = "2025-07-01T09:15:46.673Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4a/82/3a721f7d69dca802befb8af08b7c79ebcab461007ce1c18bd91a5d5896f9/pillow-11.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db", size = 7121500, upload-time = "2025-07-01T09:15:48.512Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/89/c7/5572fa4a3f45740eaab6ae86fcdf7195b55beac1371ac8c619d880cfe948/pillow-11.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa", size = 2512835, upload-time = "2025-07-01T09:15:50.399Z" }, +] + +[[package]] +name = "portalocker" +version = "3.2.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "pywin32", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5e/77/65b857a69ed876e1951e88aaba60f5ce6120c33703f7cb61a3c894b8c1b6/portalocker-3.2.0.tar.gz", hash = "sha256:1f3002956a54a8c3730586c5c77bf18fae4149e07eaf1c29fc3faf4d5a3f89ac", size = 95644, upload-time = "2025-06-14T13:20:40.03Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4b/a6/38c8e2f318bf67d338f4d629e93b0b4b9af331f455f0390ea8ce4a099b26/portalocker-3.2.0-py3-none-any.whl", hash = "sha256:3cdc5f565312224bc570c49337bd21428bba0ef363bbcf58b9ef4a9f11779968", size = 22424, upload-time = "2025-06-14T13:20:38.083Z" }, +] + +[[package]] +name = "propcache" +version = "0.3.2" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a6/16/43264e4a779dd8588c21a70f0709665ee8f611211bdd2c87d952cfa7c776/propcache-0.3.2.tar.gz", hash = "sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168", size = 44139, upload-time = "2025-06-09T22:56:06.081Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a8/42/9ca01b0a6f48e81615dca4765a8f1dd2c057e0540f6116a27dc5ee01dfb6/propcache-0.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8de106b6c84506b31c27168582cd3cb3000a6412c16df14a8628e5871ff83c10", size = 73674, upload-time = "2025-06-09T22:54:30.551Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/af/6e/21293133beb550f9c901bbece755d582bfaf2176bee4774000bd4dd41884/propcache-0.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:28710b0d3975117239c76600ea351934ac7b5ff56e60953474342608dbbb6154", size = 43570, upload-time = "2025-06-09T22:54:32.296Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0c/c8/0393a0a3a2b8760eb3bde3c147f62b20044f0ddac81e9d6ed7318ec0d852/propcache-0.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce26862344bdf836650ed2487c3d724b00fbfec4233a1013f597b78c1cb73615", size = 43094, upload-time = "2025-06-09T22:54:33.929Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/37/2c/489afe311a690399d04a3e03b069225670c1d489eb7b044a566511c1c498/propcache-0.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bca54bd347a253af2cf4544bbec232ab982f4868de0dd684246b67a51bc6b1db", size = 226958, upload-time = "2025-06-09T22:54:35.186Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9d/ca/63b520d2f3d418c968bf596839ae26cf7f87bead026b6192d4da6a08c467/propcache-0.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55780d5e9a2ddc59711d727226bb1ba83a22dd32f64ee15594b9392b1f544eb1", size = 234894, upload-time = "2025-06-09T22:54:36.708Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/11/60/1d0ed6fff455a028d678df30cc28dcee7af77fa2b0e6962ce1df95c9a2a9/propcache-0.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:035e631be25d6975ed87ab23153db6a73426a48db688070d925aa27e996fe93c", size = 233672, upload-time = "2025-06-09T22:54:38.062Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/37/7c/54fd5301ef38505ab235d98827207176a5c9b2aa61939b10a460ca53e123/propcache-0.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee6f22b6eaa39297c751d0e80c0d3a454f112f5c6481214fcf4c092074cecd67", size = 224395, upload-time = "2025-06-09T22:54:39.634Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ee/1a/89a40e0846f5de05fdc6779883bf46ba980e6df4d2ff8fb02643de126592/propcache-0.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ca3aee1aa955438c4dba34fc20a9f390e4c79967257d830f137bd5a8a32ed3b", size = 212510, upload-time = "2025-06-09T22:54:41.565Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5e/33/ca98368586c9566a6b8d5ef66e30484f8da84c0aac3f2d9aec6d31a11bd5/propcache-0.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4f30862869fa2b68380d677cc1c5fcf1e0f2b9ea0cf665812895c75d0ca3b8", size = 222949, upload-time = "2025-06-09T22:54:43.038Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ba/11/ace870d0aafe443b33b2f0b7efdb872b7c3abd505bfb4890716ad7865e9d/propcache-0.3.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b77ec3c257d7816d9f3700013639db7491a434644c906a2578a11daf13176251", size = 217258, upload-time = "2025-06-09T22:54:44.376Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5b/d2/86fd6f7adffcfc74b42c10a6b7db721d1d9ca1055c45d39a1a8f2a740a21/propcache-0.3.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cab90ac9d3f14b2d5050928483d3d3b8fb6b4018893fc75710e6aa361ecb2474", size = 213036, upload-time = "2025-06-09T22:54:46.243Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/07/94/2d7d1e328f45ff34a0a284cf5a2847013701e24c2a53117e7c280a4316b3/propcache-0.3.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0b504d29f3c47cf6b9e936c1852246c83d450e8e063d50562115a6be6d3a2535", size = 227684, upload-time = "2025-06-09T22:54:47.63Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b7/05/37ae63a0087677e90b1d14710e532ff104d44bc1efa3b3970fff99b891dc/propcache-0.3.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:ce2ac2675a6aa41ddb2a0c9cbff53780a617ac3d43e620f8fd77ba1c84dcfc06", size = 234562, upload-time = "2025-06-09T22:54:48.982Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a4/7c/3f539fcae630408d0bd8bf3208b9a647ccad10976eda62402a80adf8fc34/propcache-0.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b4239611205294cc433845b914131b2a1f03500ff3c1ed093ed216b82621e1", size = 222142, upload-time = "2025-06-09T22:54:50.424Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7c/d2/34b9eac8c35f79f8a962546b3e97e9d4b990c420ee66ac8255d5d9611648/propcache-0.3.2-cp312-cp312-win32.whl", hash = "sha256:df4a81b9b53449ebc90cc4deefb052c1dd934ba85012aa912c7ea7b7e38b60c1", size = 37711, upload-time = "2025-06-09T22:54:52.072Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/19/61/d582be5d226cf79071681d1b46b848d6cb03d7b70af7063e33a2787eaa03/propcache-0.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7046e79b989d7fe457bb755844019e10f693752d169076138abf17f31380800c", size = 41479, upload-time = "2025-06-09T22:54:53.234Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/dc/d1/8c747fafa558c603c4ca19d8e20b288aa0c7cda74e9402f50f31eb65267e/propcache-0.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ca592ed634a73ca002967458187109265e980422116c0a107cf93d81f95af945", size = 71286, upload-time = "2025-06-09T22:54:54.369Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/61/99/d606cb7986b60d89c36de8a85d58764323b3a5ff07770a99d8e993b3fa73/propcache-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9ecb0aad4020e275652ba3975740f241bd12a61f1a784df044cf7477a02bc252", size = 42425, upload-time = "2025-06-09T22:54:55.642Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8c/96/ef98f91bbb42b79e9bb82bdd348b255eb9d65f14dbbe3b1594644c4073f7/propcache-0.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f08f1cc28bd2eade7a8a3d2954ccc673bb02062e3e7da09bc75d843386b342f", size = 41846, upload-time = "2025-06-09T22:54:57.246Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5b/ad/3f0f9a705fb630d175146cd7b1d2bf5555c9beaed54e94132b21aac098a6/propcache-0.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1a342c834734edb4be5ecb1e9fb48cb64b1e2320fccbd8c54bf8da8f2a84c33", size = 208871, upload-time = "2025-06-09T22:54:58.975Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3a/38/2085cda93d2c8b6ec3e92af2c89489a36a5886b712a34ab25de9fbca7992/propcache-0.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a544caaae1ac73f1fecfae70ded3e93728831affebd017d53449e3ac052ac1e", size = 215720, upload-time = "2025-06-09T22:55:00.471Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/61/c1/d72ea2dc83ac7f2c8e182786ab0fc2c7bd123a1ff9b7975bee671866fe5f/propcache-0.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:310d11aa44635298397db47a3ebce7db99a4cc4b9bbdfcf6c98a60c8d5261cf1", size = 215203, upload-time = "2025-06-09T22:55:01.834Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/af/81/b324c44ae60c56ef12007105f1460d5c304b0626ab0cc6b07c8f2a9aa0b8/propcache-0.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c1396592321ac83157ac03a2023aa6cc4a3cc3cfdecb71090054c09e5a7cce3", size = 206365, upload-time = "2025-06-09T22:55:03.199Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/09/73/88549128bb89e66d2aff242488f62869014ae092db63ccea53c1cc75a81d/propcache-0.3.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cabf5b5902272565e78197edb682017d21cf3b550ba0460ee473753f28d23c1", size = 196016, upload-time = "2025-06-09T22:55:04.518Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b9/3f/3bdd14e737d145114a5eb83cb172903afba7242f67c5877f9909a20d948d/propcache-0.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0a2f2235ac46a7aa25bdeb03a9e7060f6ecbd213b1f9101c43b3090ffb971ef6", size = 205596, upload-time = "2025-06-09T22:55:05.942Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0f/ca/2f4aa819c357d3107c3763d7ef42c03980f9ed5c48c82e01e25945d437c1/propcache-0.3.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:92b69e12e34869a6970fd2f3da91669899994b47c98f5d430b781c26f1d9f387", size = 200977, upload-time = "2025-06-09T22:55:07.792Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cd/4a/e65276c7477533c59085251ae88505caf6831c0e85ff8b2e31ebcbb949b1/propcache-0.3.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:54e02207c79968ebbdffc169591009f4474dde3b4679e16634d34c9363ff56b4", size = 197220, upload-time = "2025-06-09T22:55:09.173Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7c/54/fc7152e517cf5578278b242396ce4d4b36795423988ef39bb8cd5bf274c8/propcache-0.3.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4adfb44cb588001f68c5466579d3f1157ca07f7504fc91ec87862e2b8e556b88", size = 210642, upload-time = "2025-06-09T22:55:10.62Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b9/80/abeb4a896d2767bf5f1ea7b92eb7be6a5330645bd7fb844049c0e4045d9d/propcache-0.3.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fd3e6019dc1261cd0291ee8919dd91fbab7b169bb76aeef6c716833a3f65d206", size = 212789, upload-time = "2025-06-09T22:55:12.029Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b3/db/ea12a49aa7b2b6d68a5da8293dcf50068d48d088100ac016ad92a6a780e6/propcache-0.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4c181cad81158d71c41a2bce88edce078458e2dd5ffee7eddd6b05da85079f43", size = 205880, upload-time = "2025-06-09T22:55:13.45Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d1/e5/9076a0bbbfb65d1198007059c65639dfd56266cf8e477a9707e4b1999ff4/propcache-0.3.2-cp313-cp313-win32.whl", hash = "sha256:8a08154613f2249519e549de2330cf8e2071c2887309a7b07fb56098f5170a02", size = 37220, upload-time = "2025-06-09T22:55:15.284Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d3/f5/b369e026b09a26cd77aa88d8fffd69141d2ae00a2abaaf5380d2603f4b7f/propcache-0.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e41671f1594fc4ab0a6dec1351864713cb3a279910ae8b58f884a88a0a632c05", size = 40678, upload-time = "2025-06-09T22:55:16.445Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a4/3a/6ece377b55544941a08d03581c7bc400a3c8cd3c2865900a68d5de79e21f/propcache-0.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9a3cf035bbaf035f109987d9d55dc90e4b0e36e04bbbb95af3055ef17194057b", size = 76560, upload-time = "2025-06-09T22:55:17.598Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0c/da/64a2bb16418740fa634b0e9c3d29edff1db07f56d3546ca2d86ddf0305e1/propcache-0.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:156c03d07dc1323d8dacaa221fbe028c5c70d16709cdd63502778e6c3ccca1b0", size = 44676, upload-time = "2025-06-09T22:55:18.922Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/36/7b/f025e06ea51cb72c52fb87e9b395cced02786610b60a3ed51da8af017170/propcache-0.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74413c0ba02ba86f55cf60d18daab219f7e531620c15f1e23d95563f505efe7e", size = 44701, upload-time = "2025-06-09T22:55:20.106Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a4/00/faa1b1b7c3b74fc277f8642f32a4c72ba1d7b2de36d7cdfb676db7f4303e/propcache-0.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f066b437bb3fa39c58ff97ab2ca351db465157d68ed0440abecb21715eb24b28", size = 276934, upload-time = "2025-06-09T22:55:21.5Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/74/ab/935beb6f1756e0476a4d5938ff44bf0d13a055fed880caf93859b4f1baf4/propcache-0.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1304b085c83067914721e7e9d9917d41ad87696bf70f0bc7dee450e9c71ad0a", size = 278316, upload-time = "2025-06-09T22:55:22.918Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f8/9d/994a5c1ce4389610838d1caec74bdf0e98b306c70314d46dbe4fcf21a3e2/propcache-0.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab50cef01b372763a13333b4e54021bdcb291fc9a8e2ccb9c2df98be51bcde6c", size = 282619, upload-time = "2025-06-09T22:55:24.651Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2b/00/a10afce3d1ed0287cef2e09506d3be9822513f2c1e96457ee369adb9a6cd/propcache-0.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad3b2a085ec259ad2c2842666b2a0a49dea8463579c606426128925af1ed725", size = 265896, upload-time = "2025-06-09T22:55:26.049Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2e/a8/2aa6716ffa566ca57c749edb909ad27884680887d68517e4be41b02299f3/propcache-0.3.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:261fa020c1c14deafd54c76b014956e2f86991af198c51139faf41c4d5e83892", size = 252111, upload-time = "2025-06-09T22:55:27.381Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/36/4f/345ca9183b85ac29c8694b0941f7484bf419c7f0fea2d1e386b4f7893eed/propcache-0.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:46d7f8aa79c927e5f987ee3a80205c987717d3659f035c85cf0c3680526bdb44", size = 268334, upload-time = "2025-06-09T22:55:28.747Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3e/ca/fcd54f78b59e3f97b3b9715501e3147f5340167733d27db423aa321e7148/propcache-0.3.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:6d8f3f0eebf73e3c0ff0e7853f68be638b4043c65a70517bb575eff54edd8dbe", size = 255026, upload-time = "2025-06-09T22:55:30.184Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8b/95/8e6a6bbbd78ac89c30c225210a5c687790e532ba4088afb8c0445b77ef37/propcache-0.3.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:03c89c1b14a5452cf15403e291c0ccd7751d5b9736ecb2c5bab977ad6c5bcd81", size = 250724, upload-time = "2025-06-09T22:55:31.646Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ee/b0/0dd03616142baba28e8b2d14ce5df6631b4673850a3d4f9c0f9dd714a404/propcache-0.3.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:0cc17efde71e12bbaad086d679ce575268d70bc123a5a71ea7ad76f70ba30bba", size = 268868, upload-time = "2025-06-09T22:55:33.209Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c5/98/2c12407a7e4fbacd94ddd32f3b1e3d5231e77c30ef7162b12a60e2dd5ce3/propcache-0.3.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:acdf05d00696bc0447e278bb53cb04ca72354e562cf88ea6f9107df8e7fd9770", size = 271322, upload-time = "2025-06-09T22:55:35.065Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/35/91/9cb56efbb428b006bb85db28591e40b7736847b8331d43fe335acf95f6c8/propcache-0.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4445542398bd0b5d32df908031cb1b30d43ac848e20470a878b770ec2dcc6330", size = 265778, upload-time = "2025-06-09T22:55:36.45Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9a/4c/b0fe775a2bdd01e176b14b574be679d84fc83958335790f7c9a686c1f468/propcache-0.3.2-cp313-cp313t-win32.whl", hash = "sha256:f86e5d7cd03afb3a1db8e9f9f6eff15794e79e791350ac48a8c924e6f439f394", size = 41175, upload-time = "2025-06-09T22:55:38.436Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a4/ff/47f08595e3d9b5e149c150f88d9714574f1a7cbd89fe2817158a952674bf/propcache-0.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9704bedf6e7cbe3c65eca4379a9b53ee6a83749f047808cbb5044d40d7d72198", size = 44857, upload-time = "2025-06-09T22:55:39.687Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cc/35/cc0aaecf278bb4575b8555f2b137de5ab821595ddae9da9d3cd1da4072c7/propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f", size = 12663, upload-time = "2025-06-09T22:56:04.484Z" }, +] + +[[package]] +name = "psutil" +version = "7.0.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003, upload-time = "2025-02-13T21:54:07.946Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", size = 238051, upload-time = "2025-02-13T21:54:12.36Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", size = 239535, upload-time = "2025-02-13T21:54:16.07Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", size = 275004, upload-time = "2025-02-13T21:54:18.662Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", size = 277986, upload-time = "2025-02-13T21:54:21.811Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", size = 279544, upload-time = "2025-02-13T21:54:24.68Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053, upload-time = "2025-02-13T21:54:34.31Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885, upload-time = "2025-02-13T21:54:37.486Z" }, +] + +[[package]] +name = "pyarrow" +version = "21.0.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ef/c2/ea068b8f00905c06329a3dfcd40d0fcc2b7d0f2e355bdb25b65e0a0e4cd4/pyarrow-21.0.0.tar.gz", hash = "sha256:5051f2dccf0e283ff56335760cbc8622cf52264d67e359d5569541ac11b6d5bc", size = 1133487, upload-time = "2025-07-18T00:57:31.761Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ca/d4/d4f817b21aacc30195cf6a46ba041dd1be827efa4a623cc8bf39a1c2a0c0/pyarrow-21.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:3a302f0e0963db37e0a24a70c56cf91a4faa0bca51c23812279ca2e23481fccd", size = 31160305, upload-time = "2025-07-18T00:55:35.373Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a2/9c/dcd38ce6e4b4d9a19e1d36914cb8e2b1da4e6003dd075474c4cfcdfe0601/pyarrow-21.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:b6b27cf01e243871390474a211a7922bfbe3bda21e39bc9160daf0da3fe48876", size = 32684264, upload-time = "2025-07-18T00:55:39.303Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4f/74/2a2d9f8d7a59b639523454bec12dba35ae3d0a07d8ab529dc0809f74b23c/pyarrow-21.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:e72a8ec6b868e258a2cd2672d91f2860ad532d590ce94cdf7d5e7ec674ccf03d", size = 41108099, upload-time = "2025-07-18T00:55:42.889Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ad/90/2660332eeb31303c13b653ea566a9918484b6e4d6b9d2d46879a33ab0622/pyarrow-21.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b7ae0bbdc8c6674259b25bef5d2a1d6af5d39d7200c819cf99e07f7dfef1c51e", size = 42829529, upload-time = "2025-07-18T00:55:47.069Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/33/27/1a93a25c92717f6aa0fca06eb4700860577d016cd3ae51aad0e0488ac899/pyarrow-21.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:58c30a1729f82d201627c173d91bd431db88ea74dcaa3885855bc6203e433b82", size = 43367883, upload-time = "2025-07-18T00:55:53.069Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/05/d9/4d09d919f35d599bc05c6950095e358c3e15148ead26292dfca1fb659b0c/pyarrow-21.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:072116f65604b822a7f22945a7a6e581cfa28e3454fdcc6939d4ff6090126623", size = 45133802, upload-time = "2025-07-18T00:55:57.714Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/71/30/f3795b6e192c3ab881325ffe172e526499eb3780e306a15103a2764916a2/pyarrow-21.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:cf56ec8b0a5c8c9d7021d6fd754e688104f9ebebf1bf4449613c9531f5346a18", size = 26203175, upload-time = "2025-07-18T00:56:01.364Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/16/ca/c7eaa8e62db8fb37ce942b1ea0c6d7abfe3786ca193957afa25e71b81b66/pyarrow-21.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:e99310a4ebd4479bcd1964dff9e14af33746300cb014aa4a3781738ac63baf4a", size = 31154306, upload-time = "2025-07-18T00:56:04.42Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ce/e8/e87d9e3b2489302b3a1aea709aaca4b781c5252fcb812a17ab6275a9a484/pyarrow-21.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:d2fe8e7f3ce329a71b7ddd7498b3cfac0eeb200c2789bd840234f0dc271a8efe", size = 32680622, upload-time = "2025-07-18T00:56:07.505Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/84/52/79095d73a742aa0aba370c7942b1b655f598069489ab387fe47261a849e1/pyarrow-21.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:f522e5709379d72fb3da7785aa489ff0bb87448a9dc5a75f45763a795a089ebd", size = 41104094, upload-time = "2025-07-18T00:56:10.994Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/89/4b/7782438b551dbb0468892a276b8c789b8bbdb25ea5c5eb27faadd753e037/pyarrow-21.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:69cbbdf0631396e9925e048cfa5bce4e8c3d3b41562bbd70c685a8eb53a91e61", size = 42825576, upload-time = "2025-07-18T00:56:15.569Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b3/62/0f29de6e0a1e33518dec92c65be0351d32d7ca351e51ec5f4f837a9aab91/pyarrow-21.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:731c7022587006b755d0bdb27626a1a3bb004bb56b11fb30d98b6c1b4718579d", size = 43368342, upload-time = "2025-07-18T00:56:19.531Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/90/c7/0fa1f3f29cf75f339768cc698c8ad4ddd2481c1742e9741459911c9ac477/pyarrow-21.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dc56bc708f2d8ac71bd1dcb927e458c93cec10b98eb4120206a4091db7b67b99", size = 45131218, upload-time = "2025-07-18T00:56:23.347Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/01/63/581f2076465e67b23bc5a37d4a2abff8362d389d29d8105832e82c9c811c/pyarrow-21.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:186aa00bca62139f75b7de8420f745f2af12941595bbbfa7ed3870ff63e25636", size = 26087551, upload-time = "2025-07-18T00:56:26.758Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c9/ab/357d0d9648bb8241ee7348e564f2479d206ebe6e1c47ac5027c2e31ecd39/pyarrow-21.0.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:a7a102574faa3f421141a64c10216e078df467ab9576684d5cd696952546e2da", size = 31290064, upload-time = "2025-07-18T00:56:30.214Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3f/8a/5685d62a990e4cac2043fc76b4661bf38d06efed55cf45a334b455bd2759/pyarrow-21.0.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:1e005378c4a2c6db3ada3ad4c217b381f6c886f0a80d6a316fe586b90f77efd7", size = 32727837, upload-time = "2025-07-18T00:56:33.935Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fc/de/c0828ee09525c2bafefd3e736a248ebe764d07d0fd762d4f0929dbc516c9/pyarrow-21.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:65f8e85f79031449ec8706b74504a316805217b35b6099155dd7e227eef0d4b6", size = 41014158, upload-time = "2025-07-18T00:56:37.528Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6e/26/a2865c420c50b7a3748320b614f3484bfcde8347b2639b2b903b21ce6a72/pyarrow-21.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:3a81486adc665c7eb1a2bde0224cfca6ceaba344a82a971ef059678417880eb8", size = 42667885, upload-time = "2025-07-18T00:56:41.483Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0a/f9/4ee798dc902533159250fb4321267730bc0a107d8c6889e07c3add4fe3a5/pyarrow-21.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fc0d2f88b81dcf3ccf9a6ae17f89183762c8a94a5bdcfa09e05cfe413acf0503", size = 43276625, upload-time = "2025-07-18T00:56:48.002Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5a/da/e02544d6997037a4b0d22d8e5f66bc9315c3671371a8b18c79ade1cefe14/pyarrow-21.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6299449adf89df38537837487a4f8d3bd91ec94354fdd2a7d30bc11c48ef6e79", size = 44951890, upload-time = "2025-07-18T00:56:52.568Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e5/4e/519c1bc1876625fe6b71e9a28287c43ec2f20f73c658b9ae1d485c0c206e/pyarrow-21.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:222c39e2c70113543982c6b34f3077962b44fca38c0bd9e68bb6781534425c10", size = 26371006, upload-time = "2025-07-18T00:56:56.379Z" }, +] + +[[package]] +name = "pybind11" +version = "3.0.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ef/83/698d120e257a116f2472c710932023ad779409adf2734d2e940f34eea2c5/pybind11-3.0.0.tar.gz", hash = "sha256:c3f07bce3ada51c3e4b76badfa85df11688d12c46111f9d242bc5c9415af7862", size = 544819, upload-time = "2025-07-10T16:52:09.335Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/41/9c/85f50a5476832c3efc67b6d7997808388236ae4754bf53e1749b3bc27577/pybind11-3.0.0-py3-none-any.whl", hash = "sha256:7c5cac504da5a701b5163f0e6a7ba736c713a096a5378383c5b4b064b753f607", size = 292118, upload-time = "2025-07-10T16:52:07.828Z" }, +] + +[[package]] +name = "pycparser" +version = "2.22" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736, upload-time = "2024-03-30T13:22:22.564Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552, upload-time = "2024-03-30T13:22:20.476Z" }, +] + +[[package]] +name = "pydantic" +version = "2.10.6" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b7/ae/d5220c5c52b158b1de7ca89fc5edb72f304a70a4c540c84c8844bf4008de/pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236", size = 761681, upload-time = "2025-01-24T01:42:12.693Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f4/3c/8cc1cc84deffa6e25d2d0c688ebb80635dfdbf1dbea3e30c541c8cf4d860/pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584", size = 431696, upload-time = "2025-01-24T01:42:10.371Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.27.2" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fc/01/f3e5ac5e7c25833db5eb555f7b7ab24cd6f8c322d3a3ad2d67a952dc0abc/pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39", size = 413443, upload-time = "2024-12-18T11:31:54.917Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d6/74/51c8a5482ca447871c93e142d9d4a92ead74de6c8dc5e66733e22c9bba89/pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0", size = 1893127, upload-time = "2024-12-18T11:28:30.346Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d3/f3/c97e80721735868313c58b89d2de85fa80fe8dfeeed84dc51598b92a135e/pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef", size = 1811340, upload-time = "2024-12-18T11:28:32.521Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9e/91/840ec1375e686dbae1bd80a9e46c26a1e0083e1186abc610efa3d9a36180/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7", size = 1822900, upload-time = "2024-12-18T11:28:34.507Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f6/31/4240bc96025035500c18adc149aa6ffdf1a0062a4b525c932065ceb4d868/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934", size = 1869177, upload-time = "2024-12-18T11:28:36.488Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fa/20/02fbaadb7808be578317015c462655c317a77a7c8f0ef274bc016a784c54/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6", size = 2038046, upload-time = "2024-12-18T11:28:39.409Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/06/86/7f306b904e6c9eccf0668248b3f272090e49c275bc488a7b88b0823444a4/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c", size = 2685386, upload-time = "2024-12-18T11:28:41.221Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8d/f0/49129b27c43396581a635d8710dae54a791b17dfc50c70164866bbf865e3/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2", size = 1997060, upload-time = "2024-12-18T11:28:44.709Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0d/0f/943b4af7cd416c477fd40b187036c4f89b416a33d3cc0ab7b82708a667aa/pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4", size = 2004870, upload-time = "2024-12-18T11:28:46.839Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/35/40/aea70b5b1a63911c53a4c8117c0a828d6790483f858041f47bab0b779f44/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3", size = 1999822, upload-time = "2024-12-18T11:28:48.896Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f2/b3/807b94fd337d58effc5498fd1a7a4d9d59af4133e83e32ae39a96fddec9d/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4", size = 2130364, upload-time = "2024-12-18T11:28:50.755Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fc/df/791c827cd4ee6efd59248dca9369fb35e80a9484462c33c6649a8d02b565/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57", size = 2158303, upload-time = "2024-12-18T11:28:54.122Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9b/67/4e197c300976af185b7cef4c02203e175fb127e414125916bf1128b639a9/pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc", size = 1834064, upload-time = "2024-12-18T11:28:56.074Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1f/ea/cd7209a889163b8dcca139fe32b9687dd05249161a3edda62860430457a5/pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9", size = 1989046, upload-time = "2024-12-18T11:28:58.107Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bc/49/c54baab2f4658c26ac633d798dab66b4c3a9bbf47cff5284e9c182f4137a/pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b", size = 1885092, upload-time = "2024-12-18T11:29:01.335Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/41/b1/9bc383f48f8002f99104e3acff6cba1231b29ef76cfa45d1506a5cad1f84/pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b", size = 1892709, upload-time = "2024-12-18T11:29:03.193Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/10/6c/e62b8657b834f3eb2961b49ec8e301eb99946245e70bf42c8817350cbefc/pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154", size = 1811273, upload-time = "2024-12-18T11:29:05.306Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ba/15/52cfe49c8c986e081b863b102d6b859d9defc63446b642ccbbb3742bf371/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9", size = 1823027, upload-time = "2024-12-18T11:29:07.294Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b1/1c/b6f402cfc18ec0024120602bdbcebc7bdd5b856528c013bd4d13865ca473/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9", size = 1868888, upload-time = "2024-12-18T11:29:09.249Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bd/7b/8cb75b66ac37bc2975a3b7de99f3c6f355fcc4d89820b61dffa8f1e81677/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1", size = 2037738, upload-time = "2024-12-18T11:29:11.23Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c8/f1/786d8fe78970a06f61df22cba58e365ce304bf9b9f46cc71c8c424e0c334/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a", size = 2685138, upload-time = "2024-12-18T11:29:16.396Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a6/74/d12b2cd841d8724dc8ffb13fc5cef86566a53ed358103150209ecd5d1999/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e", size = 1997025, upload-time = "2024-12-18T11:29:20.25Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a0/6e/940bcd631bc4d9a06c9539b51f070b66e8f370ed0933f392db6ff350d873/pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4", size = 2004633, upload-time = "2024-12-18T11:29:23.877Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/50/cc/a46b34f1708d82498c227d5d80ce615b2dd502ddcfd8376fc14a36655af1/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27", size = 1999404, upload-time = "2024-12-18T11:29:25.872Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ca/2d/c365cfa930ed23bc58c41463bae347d1005537dc8db79e998af8ba28d35e/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee", size = 2130130, upload-time = "2024-12-18T11:29:29.252Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f4/d7/eb64d015c350b7cdb371145b54d96c919d4db516817f31cd1c650cae3b21/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1", size = 2157946, upload-time = "2024-12-18T11:29:31.338Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a4/99/bddde3ddde76c03b65dfd5a66ab436c4e58ffc42927d4ff1198ffbf96f5f/pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130", size = 1834387, upload-time = "2024-12-18T11:29:33.481Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/71/47/82b5e846e01b26ac6f1893d3c5f9f3a2eb6ba79be26eef0b759b4fe72946/pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee", size = 1990453, upload-time = "2024-12-18T11:29:35.533Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/51/b2/b2b50d5ecf21acf870190ae5d093602d95f66c9c31f9d5de6062eb329ad1/pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b", size = 1885186, upload-time = "2024-12-18T11:29:37.649Z" }, +] + +[[package]] +name = "pydub" +version = "0.25.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fe/9a/e6bca0eed82db26562c73b5076539a4a08d3cffd19c3cc5913a3e61145fd/pydub-0.25.1.tar.gz", hash = "sha256:980a33ce9949cab2a569606b65674d748ecbca4f0796887fd6f46173a7b0d30f", size = 38326, upload-time = "2021-03-10T02:09:54.659Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a6/53/d78dc063216e62fc55f6b2eebb447f6a4b0a59f55c8406376f76bf959b08/pydub-0.25.1-py2.py3-none-any.whl", hash = "sha256:65617e33033874b59d87db603aa1ed450633288aefead953b30bded59cb599a6", size = 32327, upload-time = "2021-03-10T02:09:53.503Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pytablewriter" +version = "1.2.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "dataproperty" }, + { name = "mbstrdecoder" }, + { name = "pathvalidate" }, + { name = "setuptools" }, + { name = "tabledata" }, + { name = "tcolorpy" }, + { name = "typepy", extra = ["datetime"] }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f6/a1/617730f290f04d347103ab40bf67d317df6691b14746f6e1ea039fb57062/pytablewriter-1.2.1.tar.gz", hash = "sha256:7bd0f4f397e070e3b8a34edcf1b9257ccbb18305493d8350a5dbc9957fced959", size = 619241, upload-time = "2025-01-01T15:37:00.04Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/21/4c/c199512f01c845dfe5a7840ab3aae6c60463b5dc2a775be72502dfd9170a/pytablewriter-1.2.1-py3-none-any.whl", hash = "sha256:e906ff7ff5151d70a5f66e0f7b75642a7f2dce8d893c265b79cc9cf6bc04ddb4", size = 91083, upload-time = "2025-01-01T15:36:55.63Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-multipart" +version = "0.0.20" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" }, +] + +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, +] + +[[package]] +name = "pywin32" +version = "311" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload-time = "2024-08-06T20:32:25.131Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload-time = "2024-08-06T20:32:26.511Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload-time = "2024-08-06T20:32:28.363Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload-time = "2024-08-06T20:32:30.058Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload-time = "2024-08-06T20:32:31.881Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload-time = "2024-08-06T20:32:37.083Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload-time = "2024-08-06T20:32:38.898Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, +] + +[[package]] +name = "regex" +version = "2025.7.34" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0b/de/e13fa6dc61d78b30ba47481f99933a3b49a57779d625c392d8036770a60d/regex-2025.7.34.tar.gz", hash = "sha256:9ead9765217afd04a86822dfcd4ed2747dfe426e887da413b15ff0ac2457e21a", size = 400714, upload-time = "2025-07-31T00:21:16.262Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ff/f0/31d62596c75a33f979317658e8d261574785c6cd8672c06741ce2e2e2070/regex-2025.7.34-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7f7211a746aced993bef487de69307a38c5ddd79257d7be83f7b202cb59ddb50", size = 485492, upload-time = "2025-07-31T00:19:35.57Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d8/16/b818d223f1c9758c3434be89aa1a01aae798e0e0df36c1f143d1963dd1ee/regex-2025.7.34-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fb31080f2bd0681484b275461b202b5ad182f52c9ec606052020fe13eb13a72f", size = 290000, upload-time = "2025-07-31T00:19:37.175Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cd/70/69506d53397b4bd6954061bae75677ad34deb7f6ca3ba199660d6f728ff5/regex-2025.7.34-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0200a5150c4cf61e407038f4b4d5cdad13e86345dac29ff9dab3d75d905cf130", size = 286072, upload-time = "2025-07-31T00:19:38.612Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b0/73/536a216d5f66084fb577bb0543b5cb7de3272eb70a157f0c3a542f1c2551/regex-2025.7.34-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:739a74970e736df0773788377969c9fea3876c2fc13d0563f98e5503e5185f46", size = 797341, upload-time = "2025-07-31T00:19:40.119Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/26/af/733f8168449e56e8f404bb807ea7189f59507cbea1b67a7bbcd92f8bf844/regex-2025.7.34-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4fef81b2f7ea6a2029161ed6dea9ae13834c28eb5a95b8771828194a026621e4", size = 862556, upload-time = "2025-07-31T00:19:41.556Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/19/dd/59c464d58c06c4f7d87de4ab1f590e430821345a40c5d345d449a636d15f/regex-2025.7.34-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ea74cf81fe61a7e9d77989050d0089a927ab758c29dac4e8e1b6c06fccf3ebf0", size = 910762, upload-time = "2025-07-31T00:19:43Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/37/a8/b05ccf33ceca0815a1e253693b2c86544932ebcc0049c16b0fbdf18b688b/regex-2025.7.34-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e4636a7f3b65a5f340ed9ddf53585c42e3ff37101d383ed321bfe5660481744b", size = 801892, upload-time = "2025-07-31T00:19:44.645Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5f/9a/b993cb2e634cc22810afd1652dba0cae156c40d4864285ff486c73cd1996/regex-2025.7.34-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6cef962d7834437fe8d3da6f9bfc6f93f20f218266dcefec0560ed7765f5fe01", size = 786551, upload-time = "2025-07-31T00:19:46.127Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2d/79/7849d67910a0de4e26834b5bb816e028e35473f3d7ae563552ea04f58ca2/regex-2025.7.34-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:cbe1698e5b80298dbce8df4d8d1182279fbdaf1044e864cbc9d53c20e4a2be77", size = 856457, upload-time = "2025-07-31T00:19:47.562Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/91/c6/de516bc082524b27e45cb4f54e28bd800c01efb26d15646a65b87b13a91e/regex-2025.7.34-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:32b9f9bcf0f605eb094b08e8da72e44badabb63dde6b83bd530580b488d1c6da", size = 848902, upload-time = "2025-07-31T00:19:49.312Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7d/22/519ff8ba15f732db099b126f039586bd372da6cd4efb810d5d66a5daeda1/regex-2025.7.34-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:524c868ba527eab4e8744a9287809579f54ae8c62fbf07d62aacd89f6026b282", size = 788038, upload-time = "2025-07-31T00:19:50.794Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3f/7d/aabb467d8f57d8149895d133c88eb809a1a6a0fe262c1d508eb9dfabb6f9/regex-2025.7.34-cp312-cp312-win32.whl", hash = "sha256:d600e58ee6d036081c89696d2bdd55d507498a7180df2e19945c6642fac59588", size = 264417, upload-time = "2025-07-31T00:19:52.292Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3b/39/bd922b55a4fc5ad5c13753274e5b536f5b06ec8eb9747675668491c7ab7a/regex-2025.7.34-cp312-cp312-win_amd64.whl", hash = "sha256:9a9ab52a466a9b4b91564437b36417b76033e8778e5af8f36be835d8cb370d62", size = 275387, upload-time = "2025-07-31T00:19:53.593Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f7/3c/c61d2fdcecb754a40475a3d1ef9a000911d3e3fc75c096acf44b0dfb786a/regex-2025.7.34-cp312-cp312-win_arm64.whl", hash = "sha256:c83aec91af9c6fbf7c743274fd952272403ad9a9db05fe9bfc9df8d12b45f176", size = 268482, upload-time = "2025-07-31T00:19:55.183Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/15/16/b709b2119975035169a25aa8e4940ca177b1a2e25e14f8d996d09130368e/regex-2025.7.34-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c3c9740a77aeef3f5e3aaab92403946a8d34437db930a0280e7e81ddcada61f5", size = 485334, upload-time = "2025-07-31T00:19:56.58Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/94/a6/c09136046be0595f0331bc58a0e5f89c2d324cf734e0b0ec53cf4b12a636/regex-2025.7.34-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:69ed3bc611540f2ea70a4080f853741ec698be556b1df404599f8724690edbcd", size = 289942, upload-time = "2025-07-31T00:19:57.943Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/36/91/08fc0fd0f40bdfb0e0df4134ee37cfb16e66a1044ac56d36911fd01c69d2/regex-2025.7.34-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d03c6f9dcd562c56527c42b8530aad93193e0b3254a588be1f2ed378cdfdea1b", size = 285991, upload-time = "2025-07-31T00:19:59.837Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/be/2f/99dc8f6f756606f0c214d14c7b6c17270b6bbe26d5c1f05cde9dbb1c551f/regex-2025.7.34-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6164b1d99dee1dfad33f301f174d8139d4368a9fb50bf0a3603b2eaf579963ad", size = 797415, upload-time = "2025-07-31T00:20:01.668Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/62/cf/2fcdca1110495458ba4e95c52ce73b361cf1cafd8a53b5c31542cde9a15b/regex-2025.7.34-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1e4f4f62599b8142362f164ce776f19d79bdd21273e86920a7b604a4275b4f59", size = 862487, upload-time = "2025-07-31T00:20:03.142Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/90/38/899105dd27fed394e3fae45607c1983e138273ec167e47882fc401f112b9/regex-2025.7.34-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:72a26dcc6a59c057b292f39d41465d8233a10fd69121fa24f8f43ec6294e5415", size = 910717, upload-time = "2025-07-31T00:20:04.727Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ee/f6/4716198dbd0bcc9c45625ac4c81a435d1c4d8ad662e8576dac06bab35b17/regex-2025.7.34-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5273fddf7a3e602695c92716c420c377599ed3c853ea669c1fe26218867002f", size = 801943, upload-time = "2025-07-31T00:20:07.1Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/40/5d/cff8896d27e4e3dd11dd72ac78797c7987eb50fe4debc2c0f2f1682eb06d/regex-2025.7.34-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c1844be23cd40135b3a5a4dd298e1e0c0cb36757364dd6cdc6025770363e06c1", size = 786664, upload-time = "2025-07-31T00:20:08.818Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/10/29/758bf83cf7b4c34f07ac3423ea03cee3eb3176941641e4ccc05620f6c0b8/regex-2025.7.34-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dde35e2afbbe2272f8abee3b9fe6772d9b5a07d82607b5788e8508974059925c", size = 856457, upload-time = "2025-07-31T00:20:10.328Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d7/30/c19d212b619963c5b460bfed0ea69a092c6a43cba52a973d46c27b3e2975/regex-2025.7.34-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f3f6e8e7af516a7549412ce57613e859c3be27d55341a894aacaa11703a4c31a", size = 849008, upload-time = "2025-07-31T00:20:11.823Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9e/b8/3c35da3b12c87e3cc00010ef6c3a4ae787cff0bc381aa3d251def219969a/regex-2025.7.34-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:469142fb94a869beb25b5f18ea87646d21def10fbacb0bcb749224f3509476f0", size = 788101, upload-time = "2025-07-31T00:20:13.729Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/47/80/2f46677c0b3c2b723b2c358d19f9346e714113865da0f5f736ca1a883bde/regex-2025.7.34-cp313-cp313-win32.whl", hash = "sha256:da7507d083ee33ccea1310447410c27ca11fb9ef18c95899ca57ff60a7e4d8f1", size = 264401, upload-time = "2025-07-31T00:20:15.233Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/be/fa/917d64dd074682606a003cba33585c28138c77d848ef72fc77cbb1183849/regex-2025.7.34-cp313-cp313-win_amd64.whl", hash = "sha256:9d644de5520441e5f7e2db63aec2748948cc39ed4d7a87fd5db578ea4043d997", size = 275368, upload-time = "2025-07-31T00:20:16.711Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/65/cd/f94383666704170a2154a5df7b16be28f0c27a266bffcd843e58bc84120f/regex-2025.7.34-cp313-cp313-win_arm64.whl", hash = "sha256:7bf1c5503a9f2cbd2f52d7e260acb3131b07b6273c470abb78568174fe6bde3f", size = 268482, upload-time = "2025-07-31T00:20:18.189Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ac/23/6376f3a23cf2f3c00514b1cdd8c990afb4dfbac3cb4a68b633c6b7e2e307/regex-2025.7.34-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:8283afe7042d8270cecf27cca558873168e771183d4d593e3c5fe5f12402212a", size = 485385, upload-time = "2025-07-31T00:20:19.692Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/73/5b/6d4d3a0b4d312adbfd6d5694c8dddcf1396708976dd87e4d00af439d962b/regex-2025.7.34-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6c053f9647e3421dd2f5dff8172eb7b4eec129df9d1d2f7133a4386319b47435", size = 289788, upload-time = "2025-07-31T00:20:21.941Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/92/71/5862ac9913746e5054d01cb9fb8125b3d0802c0706ef547cae1e7f4428fa/regex-2025.7.34-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a16dd56bbcb7d10e62861c3cd000290ddff28ea142ffb5eb3470f183628011ac", size = 286136, upload-time = "2025-07-31T00:20:26.146Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/27/df/5b505dc447eb71278eba10d5ec940769ca89c1af70f0468bfbcb98035dc2/regex-2025.7.34-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69c593ff5a24c0d5c1112b0df9b09eae42b33c014bdca7022d6523b210b69f72", size = 797753, upload-time = "2025-07-31T00:20:27.919Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/86/38/3e3dc953d13998fa047e9a2414b556201dbd7147034fbac129392363253b/regex-2025.7.34-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98d0ce170fcde1a03b5df19c5650db22ab58af375aaa6ff07978a85c9f250f0e", size = 863263, upload-time = "2025-07-31T00:20:29.803Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/68/e5/3ff66b29dde12f5b874dda2d9dec7245c2051f2528d8c2a797901497f140/regex-2025.7.34-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d72765a4bff8c43711d5b0f5b452991a9947853dfa471972169b3cc0ba1d0751", size = 910103, upload-time = "2025-07-31T00:20:31.313Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9e/fe/14176f2182125977fba3711adea73f472a11f3f9288c1317c59cd16ad5e6/regex-2025.7.34-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4494f8fd95a77eb434039ad8460e64d57baa0434f1395b7da44015bef650d0e4", size = 801709, upload-time = "2025-07-31T00:20:33.323Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5a/0d/80d4e66ed24f1ba876a9e8e31b709f9fd22d5c266bf5f3ab3c1afe683d7d/regex-2025.7.34-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4f42b522259c66e918a0121a12429b2abcf696c6f967fa37bdc7b72e61469f98", size = 786726, upload-time = "2025-07-31T00:20:35.252Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/12/75/c3ebb30e04a56c046f5c85179dc173818551037daae2c0c940c7b19152cb/regex-2025.7.34-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:aaef1f056d96a0a5d53ad47d019d5b4c66fe4be2da87016e0d43b7242599ffc7", size = 857306, upload-time = "2025-07-31T00:20:37.12Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b1/b2/a4dc5d8b14f90924f27f0ac4c4c4f5e195b723be98adecc884f6716614b6/regex-2025.7.34-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:656433e5b7dccc9bc0da6312da8eb897b81f5e560321ec413500e5367fcd5d47", size = 848494, upload-time = "2025-07-31T00:20:38.818Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0d/21/9ac6e07a4c5e8646a90b56b61f7e9dac11ae0747c857f91d3d2bc7c241d9/regex-2025.7.34-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e91eb2c62c39705e17b4d42d4b86c4e86c884c0d15d9c5a47d0835f8387add8e", size = 787850, upload-time = "2025-07-31T00:20:40.478Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/be/6c/d51204e28e7bc54f9a03bb799b04730d7e54ff2718862b8d4e09e7110a6a/regex-2025.7.34-cp314-cp314-win32.whl", hash = "sha256:f978ddfb6216028c8f1d6b0f7ef779949498b64117fc35a939022f67f810bdcb", size = 269730, upload-time = "2025-07-31T00:20:42.253Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/74/52/a7e92d02fa1fdef59d113098cb9f02c5d03289a0e9f9e5d4d6acccd10677/regex-2025.7.34-cp314-cp314-win_amd64.whl", hash = "sha256:4b7dc33b9b48fb37ead12ffc7bdb846ac72f99a80373c4da48f64b373a7abeae", size = 278640, upload-time = "2025-07-31T00:20:44.42Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d1/78/a815529b559b1771080faa90c3ab401730661f99d495ab0071649f139ebd/regex-2025.7.34-cp314-cp314-win_arm64.whl", hash = "sha256:4b8c4d39f451e64809912c82392933d80fe2e4a87eeef8859fcc5380d0173c64", size = 271757, upload-time = "2025-07-31T00:20:46.355Z" }, +] + +[[package]] +name = "requests" +version = "2.32.4" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258, upload-time = "2025-06-09T16:43:07.34Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" }, +] + +[[package]] +name = "rich" +version = "14.1.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fe/75/af448d8e52bf1d8fa6a9d089ca6c07ff4453d86c65c145d0a300bb073b9b/rich-14.1.0.tar.gz", hash = "sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8", size = 224441, upload-time = "2025-07-25T07:32:58.125Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f", size = 243368, upload-time = "2025-07-25T07:32:56.73Z" }, +] + +[[package]] +name = "rouge-score" +version = "0.1.2" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "absl-py" }, + { name = "nltk" }, + { name = "numpy" }, + { name = "six" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e2/c5/9136736c37022a6ad27fea38f3111eb8f02fe75d067f9a985cc358653102/rouge_score-0.1.2.tar.gz", hash = "sha256:c7d4da2683e68c9abf0135ef915d63a46643666f848e558a1b9f7ead17ff0f04", size = 17400, upload-time = "2022-07-22T22:46:22.909Z" } + +[[package]] +name = "ruff" +version = "0.12.8" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4b/da/5bd7565be729e86e1442dad2c9a364ceeff82227c2dece7c29697a9795eb/ruff-0.12.8.tar.gz", hash = "sha256:4cb3a45525176e1009b2b64126acf5f9444ea59066262791febf55e40493a033", size = 5242373, upload-time = "2025-08-07T19:05:47.268Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c9/1e/c843bfa8ad1114fab3eb2b78235dda76acd66384c663a4e0415ecc13aa1e/ruff-0.12.8-py3-none-linux_armv6l.whl", hash = "sha256:63cb5a5e933fc913e5823a0dfdc3c99add73f52d139d6cd5cc8639d0e0465513", size = 11675315, upload-time = "2025-08-07T19:05:06.15Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/24/ee/af6e5c2a8ca3a81676d5480a1025494fd104b8896266502bb4de2a0e8388/ruff-0.12.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9a9bbe28f9f551accf84a24c366c1aa8774d6748438b47174f8e8565ab9dedbc", size = 12456653, upload-time = "2025-08-07T19:05:09.759Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/99/9d/e91f84dfe3866fa648c10512904991ecc326fd0b66578b324ee6ecb8f725/ruff-0.12.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2fae54e752a3150f7ee0e09bce2e133caf10ce9d971510a9b925392dc98d2fec", size = 11659690, upload-time = "2025-08-07T19:05:12.551Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fe/ac/a363d25ec53040408ebdd4efcee929d48547665858ede0505d1d8041b2e5/ruff-0.12.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0acbcf01206df963d9331b5838fb31f3b44fa979ee7fa368b9b9057d89f4a53", size = 11896923, upload-time = "2025-08-07T19:05:14.821Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/58/9f/ea356cd87c395f6ade9bb81365bd909ff60860975ca1bc39f0e59de3da37/ruff-0.12.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ae3e7504666ad4c62f9ac8eedb52a93f9ebdeb34742b8b71cd3cccd24912719f", size = 11477612, upload-time = "2025-08-07T19:05:16.712Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1a/46/92e8fa3c9dcfd49175225c09053916cb97bb7204f9f899c2f2baca69e450/ruff-0.12.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb82efb5d35d07497813a1c5647867390a7d83304562607f3579602fa3d7d46f", size = 13182745, upload-time = "2025-08-07T19:05:18.709Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5e/c4/f2176a310f26e6160deaf661ef60db6c3bb62b7a35e57ae28f27a09a7d63/ruff-0.12.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:dbea798fc0065ad0b84a2947b0aff4233f0cb30f226f00a2c5850ca4393de609", size = 14206885, upload-time = "2025-08-07T19:05:21.025Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/87/9d/98e162f3eeeb6689acbedbae5050b4b3220754554526c50c292b611d3a63/ruff-0.12.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:49ebcaccc2bdad86fd51b7864e3d808aad404aab8df33d469b6e65584656263a", size = 13639381, upload-time = "2025-08-07T19:05:23.423Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/81/4e/1b7478b072fcde5161b48f64774d6edd59d6d198e4ba8918d9f4702b8043/ruff-0.12.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ac9c570634b98c71c88cb17badd90f13fc076a472ba6ef1d113d8ed3df109fb", size = 12613271, upload-time = "2025-08-07T19:05:25.507Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e8/67/0c3c9179a3ad19791ef1b8f7138aa27d4578c78700551c60d9260b2c660d/ruff-0.12.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:560e0cd641e45591a3e42cb50ef61ce07162b9c233786663fdce2d8557d99818", size = 12847783, upload-time = "2025-08-07T19:05:28.14Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4e/2a/0b6ac3dd045acf8aa229b12c9c17bb35508191b71a14904baf99573a21bd/ruff-0.12.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:71c83121512e7743fba5a8848c261dcc454cafb3ef2934a43f1b7a4eb5a447ea", size = 11702672, upload-time = "2025-08-07T19:05:30.413Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9d/ee/f9fdc9f341b0430110de8b39a6ee5fa68c5706dc7c0aa940817947d6937e/ruff-0.12.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:de4429ef2ba091ecddedd300f4c3f24bca875d3d8b23340728c3cb0da81072c3", size = 11440626, upload-time = "2025-08-07T19:05:32.492Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/89/fb/b3aa2d482d05f44e4d197d1de5e3863feb13067b22c571b9561085c999dc/ruff-0.12.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a2cab5f60d5b65b50fba39a8950c8746df1627d54ba1197f970763917184b161", size = 12462162, upload-time = "2025-08-07T19:05:34.449Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/18/9f/5c5d93e1d00d854d5013c96e1a92c33b703a0332707a7cdbd0a4880a84fb/ruff-0.12.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:45c32487e14f60b88aad6be9fd5da5093dbefb0e3e1224131cb1d441d7cb7d46", size = 12913212, upload-time = "2025-08-07T19:05:36.541Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/71/13/ab9120add1c0e4604c71bfc2e4ef7d63bebece0cfe617013da289539cef8/ruff-0.12.8-py3-none-win32.whl", hash = "sha256:daf3475060a617fd5bc80638aeaf2f5937f10af3ec44464e280a9d2218e720d3", size = 11694382, upload-time = "2025-08-07T19:05:38.468Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f6/dc/a2873b7c5001c62f46266685863bee2888caf469d1edac84bf3242074be2/ruff-0.12.8-py3-none-win_amd64.whl", hash = "sha256:7209531f1a1fcfbe8e46bcd7ab30e2f43604d8ba1c49029bb420b103d0b5f76e", size = 12740482, upload-time = "2025-08-07T19:05:40.391Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cb/5c/799a1efb8b5abab56e8a9f2a0b72d12bd64bb55815e9476c7d0a2887d2f7/ruff-0.12.8-py3-none-win_arm64.whl", hash = "sha256:c90e1a334683ce41b0e7a04f41790c429bf5073b62c1ae701c9dc5b3d14f0749", size = 11884718, upload-time = "2025-08-07T19:05:42.866Z" }, +] + +[[package]] +name = "sacrebleu" +version = "2.5.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "colorama" }, + { name = "lxml" }, + { name = "numpy" }, + { name = "portalocker" }, + { name = "regex" }, + { name = "tabulate" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/01/14/8526cf8a5b912b618e7d6ed319a5b1876788bebba1f9a660e1291832c1cc/sacrebleu-2.5.1.tar.gz", hash = "sha256:1a088cc1c74ffaff0759c3191a85db09eecfa7a52e09be244e319d8d64e2fb11", size = 1896900, upload-time = "2025-01-03T20:15:16.772Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cd/45/7b55a7bd7e5c5b573b40ad58ba43fa09962dc5c8d71b1f573d4aeaa54a7e/sacrebleu-2.5.1-py3-none-any.whl", hash = "sha256:7c9f7ee75bec3a5bf19dd87112dfd654952130e403ad30c48298fb7da3212d5d", size = 104107, upload-time = "2025-01-03T20:15:14.626Z" }, +] + +[[package]] +name = "safehttpx" +version = "0.1.6" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "httpx" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/67/4c/19db75e6405692b2a96af8f06d1258f8aa7290bdc35ac966f03e207f6d7f/safehttpx-0.1.6.tar.gz", hash = "sha256:b356bfc82cee3a24c395b94a2dbeabbed60aff1aa5fa3b5fe97c4f2456ebce42", size = 9987, upload-time = "2024-12-02T18:44:10.226Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4d/c0/1108ad9f01567f66b3154063605b350b69c3c9366732e09e45f9fd0d1deb/safehttpx-0.1.6-py3-none-any.whl", hash = "sha256:407cff0b410b071623087c63dd2080c3b44dc076888d8c5823c00d1e58cb381c", size = 8692, upload-time = "2024-12-02T18:44:08.555Z" }, +] + +[[package]] +name = "safetensors" +version = "0.6.2" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ac/cc/738f3011628920e027a11754d9cae9abec1aed00f7ae860abbf843755233/safetensors-0.6.2.tar.gz", hash = "sha256:43ff2aa0e6fa2dc3ea5524ac7ad93a9839256b8703761e76e2d0b2a3fa4f15d9", size = 197968, upload-time = "2025-08-08T13:13:58.654Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4d/b1/3f5fd73c039fc87dba3ff8b5d528bfc5a32b597fea8e7a6a4800343a17c7/safetensors-0.6.2-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:9c85ede8ec58f120bad982ec47746981e210492a6db876882aa021446af8ffba", size = 454797, upload-time = "2025-08-08T13:13:52.066Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8c/c9/bb114c158540ee17907ec470d01980957fdaf87b4aa07914c24eba87b9c6/safetensors-0.6.2-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:d6675cf4b39c98dbd7d940598028f3742e0375a6b4d4277e76beb0c35f4b843b", size = 432206, upload-time = "2025-08-08T13:13:50.931Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d3/8e/f70c34e47df3110e8e0bb268d90db8d4be8958a54ab0336c9be4fe86dac8/safetensors-0.6.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d2d2b3ce1e2509c68932ca03ab8f20570920cd9754b05063d4368ee52833ecd", size = 473261, upload-time = "2025-08-08T13:13:41.259Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2a/f5/be9c6a7c7ef773e1996dc214e73485286df1836dbd063e8085ee1976f9cb/safetensors-0.6.2-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:93de35a18f46b0f5a6a1f9e26d91b442094f2df02e9fd7acf224cfec4238821a", size = 485117, upload-time = "2025-08-08T13:13:43.506Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c9/55/23f2d0a2c96ed8665bf17a30ab4ce5270413f4d74b6d87dd663258b9af31/safetensors-0.6.2-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89a89b505f335640f9120fac65ddeb83e40f1fd081cb8ed88b505bdccec8d0a1", size = 616154, upload-time = "2025-08-08T13:13:45.096Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/98/c6/affb0bd9ce02aa46e7acddbe087912a04d953d7a4d74b708c91b5806ef3f/safetensors-0.6.2-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fc4d0d0b937e04bdf2ae6f70cd3ad51328635fe0e6214aa1fc811f3b576b3bda", size = 520713, upload-time = "2025-08-08T13:13:46.25Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fe/5d/5a514d7b88e310c8b146e2404e0dc161282e78634d9358975fd56dfd14be/safetensors-0.6.2-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8045db2c872db8f4cbe3faa0495932d89c38c899c603f21e9b6486951a5ecb8f", size = 485835, upload-time = "2025-08-08T13:13:49.373Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7a/7b/4fc3b2ba62c352b2071bea9cfbad330fadda70579f617506ae1a2f129cab/safetensors-0.6.2-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:81e67e8bab9878bb568cffbc5f5e655adb38d2418351dc0859ccac158f753e19", size = 521503, upload-time = "2025-08-08T13:13:47.651Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5a/50/0057e11fe1f3cead9254315a6c106a16dd4b1a19cd247f7cc6414f6b7866/safetensors-0.6.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b0e4d029ab0a0e0e4fdf142b194514695b1d7d3735503ba700cf36d0fc7136ce", size = 652256, upload-time = "2025-08-08T13:13:53.167Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e9/29/473f789e4ac242593ac1656fbece6e1ecd860bb289e635e963667807afe3/safetensors-0.6.2-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:fa48268185c52bfe8771e46325a1e21d317207bcabcb72e65c6e28e9ffeb29c7", size = 747281, upload-time = "2025-08-08T13:13:54.656Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/68/52/f7324aad7f2df99e05525c84d352dc217e0fa637a4f603e9f2eedfbe2c67/safetensors-0.6.2-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:d83c20c12c2d2f465997c51b7ecb00e407e5f94d7dec3ea0cc11d86f60d3fde5", size = 692286, upload-time = "2025-08-08T13:13:55.884Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ad/fe/cad1d9762868c7c5dc70c8620074df28ebb1a8e4c17d4c0cb031889c457e/safetensors-0.6.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d944cea65fad0ead848b6ec2c37cc0b197194bec228f8020054742190e9312ac", size = 655957, upload-time = "2025-08-08T13:13:57.029Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/59/a7/e2158e17bbe57d104f0abbd95dff60dda916cf277c9f9663b4bf9bad8b6e/safetensors-0.6.2-cp38-abi3-win32.whl", hash = "sha256:cab75ca7c064d3911411461151cb69380c9225798a20e712b102edda2542ddb1", size = 308926, upload-time = "2025-08-08T13:14:01.095Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2c/c3/c0be1135726618dc1e28d181b8c442403d8dbb9e273fd791de2d4384bcdd/safetensors-0.6.2-cp38-abi3-win_amd64.whl", hash = "sha256:c7b214870df923cbc1593c3faee16bec59ea462758699bd3fee399d00aac072c", size = 320192, upload-time = "2025-08-08T13:13:59.467Z" }, +] + +[[package]] +name = "scikit-learn" +version = "1.7.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "joblib" }, + { name = "numpy" }, + { name = "scipy" }, + { name = "threadpoolctl" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/41/84/5f4af978fff619706b8961accac84780a6d298d82a8873446f72edb4ead0/scikit_learn-1.7.1.tar.gz", hash = "sha256:24b3f1e976a4665aa74ee0fcaac2b8fccc6ae77c8e07ab25da3ba6d3292b9802", size = 7190445, upload-time = "2025-07-18T08:01:54.5Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cb/16/57f176585b35ed865f51b04117947fe20f130f78940c6477b6d66279c9c2/scikit_learn-1.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3cee419b49b5bbae8796ecd690f97aa412ef1674410c23fc3257c6b8b85b8087", size = 9260431, upload-time = "2025-07-18T08:01:22.77Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/67/4e/899317092f5efcab0e9bc929e3391341cec8fb0e816c4789686770024580/scikit_learn-1.7.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2fd8b8d35817b0d9ebf0b576f7d5ffbbabdb55536b0655a8aaae629d7ffd2e1f", size = 8637191, upload-time = "2025-07-18T08:01:24.731Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f3/1b/998312db6d361ded1dd56b457ada371a8d8d77ca2195a7d18fd8a1736f21/scikit_learn-1.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:588410fa19a96a69763202f1d6b7b91d5d7a5d73be36e189bc6396bfb355bd87", size = 9486346, upload-time = "2025-07-18T08:01:26.713Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ad/09/a2aa0b4e644e5c4ede7006748f24e72863ba2ae71897fecfd832afea01b4/scikit_learn-1.7.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e3142f0abe1ad1d1c31a2ae987621e41f6b578144a911ff4ac94781a583adad7", size = 9290988, upload-time = "2025-07-18T08:01:28.938Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/15/fa/c61a787e35f05f17fc10523f567677ec4eeee5f95aa4798dbbbcd9625617/scikit_learn-1.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:3ddd9092c1bd469acab337d87930067c87eac6bd544f8d5027430983f1e1ae88", size = 8735568, upload-time = "2025-07-18T08:01:30.936Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/52/f8/e0533303f318a0f37b88300d21f79b6ac067188d4824f1047a37214ab718/scikit_learn-1.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b7839687fa46d02e01035ad775982f2470be2668e13ddd151f0f55a5bf123bae", size = 9213143, upload-time = "2025-07-18T08:01:32.942Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/71/f3/f1df377d1bdfc3e3e2adc9c119c238b182293e6740df4cbeac6de2cc3e23/scikit_learn-1.7.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:a10f276639195a96c86aa572ee0698ad64ee939a7b042060b98bd1930c261d10", size = 8591977, upload-time = "2025-07-18T08:01:34.967Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/99/72/c86a4cd867816350fe8dee13f30222340b9cd6b96173955819a5561810c5/scikit_learn-1.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:13679981fdaebc10cc4c13c43344416a86fcbc61449cb3e6517e1df9d12c8309", size = 9436142, upload-time = "2025-07-18T08:01:37.397Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e8/66/277967b29bd297538dc7a6ecfb1a7dce751beabd0d7f7a2233be7a4f7832/scikit_learn-1.7.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f1262883c6a63f067a980a8cdd2d2e7f2513dddcef6a9eaada6416a7a7cbe43", size = 9282996, upload-time = "2025-07-18T08:01:39.721Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e2/47/9291cfa1db1dae9880420d1e07dbc7e8dd4a7cdbc42eaba22512e6bde958/scikit_learn-1.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:ca6d31fb10e04d50bfd2b50d66744729dbb512d4efd0223b864e2fdbfc4cee11", size = 8707418, upload-time = "2025-07-18T08:01:42.124Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/61/95/45726819beccdaa34d3362ea9b2ff9f2b5d3b8bf721bd632675870308ceb/scikit_learn-1.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:781674d096303cfe3d351ae6963ff7c958db61cde3421cd490e3a5a58f2a94ae", size = 9561466, upload-time = "2025-07-18T08:01:44.195Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ee/1c/6f4b3344805de783d20a51eb24d4c9ad4b11a7f75c1801e6ec6d777361fd/scikit_learn-1.7.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:10679f7f125fe7ecd5fad37dd1aa2daae7e3ad8df7f3eefa08901b8254b3e12c", size = 9040467, upload-time = "2025-07-18T08:01:46.671Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6f/80/abe18fe471af9f1d181904203d62697998b27d9b62124cd281d740ded2f9/scikit_learn-1.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1f812729e38c8cb37f760dce71a9b83ccfb04f59b3dca7c6079dcdc60544fa9e", size = 9532052, upload-time = "2025-07-18T08:01:48.676Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/14/82/b21aa1e0c4cee7e74864d3a5a721ab8fcae5ca55033cb6263dca297ed35b/scikit_learn-1.7.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:88e1a20131cf741b84b89567e1717f27a2ced228e0f29103426102bc2e3b8ef7", size = 9361575, upload-time = "2025-07-18T08:01:50.639Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f2/20/f4777fcd5627dc6695fa6b92179d0edb7a3ac1b91bcd9a1c7f64fa7ade23/scikit_learn-1.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b1bd1d919210b6a10b7554b717c9000b5485aa95a1d0f177ae0d7ee8ec750da5", size = 9277310, upload-time = "2025-07-18T08:01:52.547Z" }, +] + +[[package]] +name = "scipy" +version = "1.16.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f5/4a/b927028464795439faec8eaf0b03b011005c487bb2d07409f28bf30879c4/scipy-1.16.1.tar.gz", hash = "sha256:44c76f9e8b6e8e488a586190ab38016e4ed2f8a038af7cd3defa903c0a2238b3", size = 30580861, upload-time = "2025-07-27T16:33:30.834Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f8/d9/ec4864f5896232133f51382b54a08de91a9d1af7a76dfa372894026dfee2/scipy-1.16.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:81b433bbeaf35728dad619afc002db9b189e45eebe2cd676effe1fb93fef2b9c", size = 36575194, upload-time = "2025-07-27T16:27:41.321Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5c/6d/40e81ecfb688e9d25d34a847dca361982a6addf8e31f0957b1a54fbfa994/scipy-1.16.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:886cc81fdb4c6903a3bb0464047c25a6d1016fef77bb97949817d0c0d79f9e04", size = 28594590, upload-time = "2025-07-27T16:27:49.204Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0e/37/9f65178edfcc629377ce9a64fc09baebea18c80a9e57ae09a52edf84880b/scipy-1.16.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:15240c3aac087a522b4eaedb09f0ad061753c5eebf1ea430859e5bf8640d5919", size = 20866458, upload-time = "2025-07-27T16:27:54.98Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2c/7b/749a66766871ea4cb1d1ea10f27004db63023074c22abed51f22f09770e0/scipy-1.16.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:65f81a25805f3659b48126b5053d9e823d3215e4a63730b5e1671852a1705921", size = 23539318, upload-time = "2025-07-27T16:28:01.604Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c4/db/8d4afec60eb833a666434d4541a3151eedbf2494ea6d4d468cbe877f00cd/scipy-1.16.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6c62eea7f607f122069b9bad3f99489ddca1a5173bef8a0c75555d7488b6f725", size = 33292899, upload-time = "2025-07-27T16:28:09.147Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/51/1e/79023ca3bbb13a015d7d2757ecca3b81293c663694c35d6541b4dca53e98/scipy-1.16.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f965bbf3235b01c776115ab18f092a95aa74c271a52577bcb0563e85738fd618", size = 35162637, upload-time = "2025-07-27T16:28:17.535Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b6/49/0648665f9c29fdaca4c679182eb972935b3b4f5ace41d323c32352f29816/scipy-1.16.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f006e323874ffd0b0b816d8c6a8e7f9a73d55ab3b8c3f72b752b226d0e3ac83d", size = 35490507, upload-time = "2025-07-27T16:28:25.705Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/62/8f/66cbb9d6bbb18d8c658f774904f42a92078707a7c71e5347e8bf2f52bb89/scipy-1.16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8fd15fc5085ab4cca74cb91fe0a4263b1f32e4420761ddae531ad60934c2119", size = 37923998, upload-time = "2025-07-27T16:28:34.339Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/14/c3/61f273ae550fbf1667675701112e380881905e28448c080b23b5a181df7c/scipy-1.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:f7b8013c6c066609577d910d1a2a077021727af07b6fab0ee22c2f901f22352a", size = 38508060, upload-time = "2025-07-27T16:28:43.242Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/93/0b/b5c99382b839854a71ca9482c684e3472badc62620287cbbdab499b75ce6/scipy-1.16.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5451606823a5e73dfa621a89948096c6528e2896e40b39248295d3a0138d594f", size = 36533717, upload-time = "2025-07-27T16:28:51.706Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/eb/e5/69ab2771062c91e23e07c12e7d5033a6b9b80b0903ee709c3c36b3eb520c/scipy-1.16.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:89728678c5ca5abd610aee148c199ac1afb16e19844401ca97d43dc548a354eb", size = 28570009, upload-time = "2025-07-27T16:28:57.017Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f4/69/bd75dbfdd3cf524f4d753484d723594aed62cfaac510123e91a6686d520b/scipy-1.16.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e756d688cb03fd07de0fffad475649b03cb89bee696c98ce508b17c11a03f95c", size = 20841942, upload-time = "2025-07-27T16:29:01.152Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ea/74/add181c87663f178ba7d6144b370243a87af8476664d5435e57d599e6874/scipy-1.16.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:5aa2687b9935da3ed89c5dbed5234576589dd28d0bf7cd237501ccfbdf1ad608", size = 23498507, upload-time = "2025-07-27T16:29:05.202Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1d/74/ece2e582a0d9550cee33e2e416cc96737dce423a994d12bbe59716f47ff1/scipy-1.16.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0851f6a1e537fe9399f35986897e395a1aa61c574b178c0d456be5b1a0f5ca1f", size = 33286040, upload-time = "2025-07-27T16:29:10.201Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e4/82/08e4076df538fb56caa1d489588d880ec7c52d8273a606bb54d660528f7c/scipy-1.16.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fedc2cbd1baed37474b1924c331b97bdff611d762c196fac1a9b71e67b813b1b", size = 35176096, upload-time = "2025-07-27T16:29:17.091Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fa/79/cd710aab8c921375711a8321c6be696e705a120e3011a643efbbcdeeabcc/scipy-1.16.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2ef500e72f9623a6735769e4b93e9dcb158d40752cdbb077f305487e3e2d1f45", size = 35490328, upload-time = "2025-07-27T16:29:22.928Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/71/73/e9cc3d35ee4526d784520d4494a3e1ca969b071fb5ae5910c036a375ceec/scipy-1.16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:978d8311674b05a8f7ff2ea6c6bce5d8b45a0cb09d4c5793e0318f448613ea65", size = 37939921, upload-time = "2025-07-27T16:29:29.108Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/21/12/c0efd2941f01940119b5305c375ae5c0fcb7ec193f806bd8f158b73a1782/scipy-1.16.1-cp313-cp313-win_amd64.whl", hash = "sha256:81929ed0fa7a5713fcdd8b2e6f73697d3b4c4816d090dd34ff937c20fa90e8ab", size = 38479462, upload-time = "2025-07-27T16:30:24.078Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7a/19/c3d08b675260046a991040e1ea5d65f91f40c7df1045fffff412dcfc6765/scipy-1.16.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:bcc12db731858abda693cecdb3bdc9e6d4bd200213f49d224fe22df82687bdd6", size = 36938832, upload-time = "2025-07-27T16:29:35.057Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/81/f2/ce53db652c033a414a5b34598dba6b95f3d38153a2417c5a3883da429029/scipy-1.16.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:744d977daa4becb9fc59135e75c069f8d301a87d64f88f1e602a9ecf51e77b27", size = 29093084, upload-time = "2025-07-27T16:29:40.201Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a9/ae/7a10ff04a7dc15f9057d05b33737ade244e4bd195caa3f7cc04d77b9e214/scipy-1.16.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:dc54f76ac18073bcecffb98d93f03ed6b81a92ef91b5d3b135dcc81d55a724c7", size = 21365098, upload-time = "2025-07-27T16:29:44.295Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/36/ac/029ff710959932ad3c2a98721b20b405f05f752f07344622fd61a47c5197/scipy-1.16.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:367d567ee9fc1e9e2047d31f39d9d6a7a04e0710c86e701e053f237d14a9b4f6", size = 23896858, upload-time = "2025-07-27T16:29:48.784Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/71/13/d1ef77b6bd7898720e1f0b6b3743cb945f6c3cafa7718eaac8841035ab60/scipy-1.16.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4cf5785e44e19dcd32a0e4807555e1e9a9b8d475c6afff3d21c3c543a6aa84f4", size = 33438311, upload-time = "2025-07-27T16:29:54.164Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2d/e0/e64a6821ffbb00b4c5b05169f1c1fddb4800e9307efe3db3788995a82a2c/scipy-1.16.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3d0b80fb26d3e13a794c71d4b837e2a589d839fd574a6bbb4ee1288c213ad4a3", size = 35279542, upload-time = "2025-07-27T16:30:00.249Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/57/59/0dc3c8b43e118f1e4ee2b798dcc96ac21bb20014e5f1f7a8e85cc0653bdb/scipy-1.16.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8503517c44c18d1030d666cb70aaac1cc8913608816e06742498833b128488b7", size = 35667665, upload-time = "2025-07-27T16:30:05.916Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/45/5f/844ee26e34e2f3f9f8febb9343748e72daeaec64fe0c70e9bf1ff84ec955/scipy-1.16.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:30cc4bb81c41831ecfd6dc450baf48ffd80ef5aed0f5cf3ea775740e80f16ecc", size = 38045210, upload-time = "2025-07-27T16:30:11.655Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8d/d7/210f2b45290f444f1de64bc7353aa598ece9f0e90c384b4a156f9b1a5063/scipy-1.16.1-cp313-cp313t-win_amd64.whl", hash = "sha256:c24fa02f7ed23ae514460a22c57eca8f530dbfa50b1cfdbf4f37c05b5309cc39", size = 38593661, upload-time = "2025-07-27T16:30:17.825Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/81/ea/84d481a5237ed223bd3d32d6e82d7a6a96e34756492666c260cef16011d1/scipy-1.16.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:796a5a9ad36fa3a782375db8f4241ab02a091308eb079746bc0f874c9b998318", size = 36525921, upload-time = "2025-07-27T16:30:30.081Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4e/9f/d9edbdeff9f3a664807ae3aea383e10afaa247e8e6255e6d2aa4515e8863/scipy-1.16.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:3ea0733a2ff73fd6fdc5fecca54ee9b459f4d74f00b99aced7d9a3adb43fb1cc", size = 28564152, upload-time = "2025-07-27T16:30:35.336Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3b/95/8125bcb1fe04bc267d103e76516243e8d5e11229e6b306bda1024a5423d1/scipy-1.16.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:85764fb15a2ad994e708258bb4ed8290d1305c62a4e1ef07c414356a24fcfbf8", size = 20836028, upload-time = "2025-07-27T16:30:39.421Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/77/9c/bf92e215701fc70bbcd3d14d86337cf56a9b912a804b9c776a269524a9e9/scipy-1.16.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:ca66d980469cb623b1759bdd6e9fd97d4e33a9fad5b33771ced24d0cb24df67e", size = 23489666, upload-time = "2025-07-27T16:30:43.663Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5e/00/5e941d397d9adac41b02839011594620d54d99488d1be5be755c00cde9ee/scipy-1.16.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e7cc1ffcc230f568549fc56670bcf3df1884c30bd652c5da8138199c8c76dae0", size = 33358318, upload-time = "2025-07-27T16:30:48.982Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0e/87/8db3aa10dde6e3e8e7eb0133f24baa011377d543f5b19c71469cf2648026/scipy-1.16.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ddfb1e8d0b540cb4ee9c53fc3dea3186f97711248fb94b4142a1b27178d8b4b", size = 35185724, upload-time = "2025-07-27T16:30:54.26Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/89/b4/6ab9ae443216807622bcff02690262d8184078ea467efee2f8c93288a3b1/scipy-1.16.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4dc0e7be79e95d8ba3435d193e0d8ce372f47f774cffd882f88ea4e1e1ddc731", size = 35554335, upload-time = "2025-07-27T16:30:59.765Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9c/9a/d0e9dc03c5269a1afb60661118296a32ed5d2c24298af61b676c11e05e56/scipy-1.16.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f23634f9e5adb51b2a77766dac217063e764337fbc816aa8ad9aaebcd4397fd3", size = 37960310, upload-time = "2025-07-27T16:31:06.151Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5e/00/c8f3130a50521a7977874817ca89e0599b1b4ee8e938bad8ae798a0e1f0d/scipy-1.16.1-cp314-cp314-win_amd64.whl", hash = "sha256:57d75524cb1c5a374958a2eae3d84e1929bb971204cc9d52213fb8589183fc19", size = 39319239, upload-time = "2025-07-27T16:31:59.942Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f2/f2/1ca3eda54c3a7e4c92f6acef7db7b3a057deb135540d23aa6343ef8ad333/scipy-1.16.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:d8da7c3dd67bcd93f15618938f43ed0995982eb38973023d46d4646c4283ad65", size = 36939460, upload-time = "2025-07-27T16:31:11.865Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/80/30/98c2840b293a132400c0940bb9e140171dcb8189588619048f42b2ce7b4f/scipy-1.16.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:cc1d2f2fd48ba1e0620554fe5bc44d3e8f5d4185c8c109c7fbdf5af2792cfad2", size = 29093322, upload-time = "2025-07-27T16:31:17.045Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c1/e6/1e6e006e850622cf2a039b62d1a6ddc4497d4851e58b68008526f04a9a00/scipy-1.16.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:21a611ced9275cb861bacadbada0b8c0623bc00b05b09eb97f23b370fc2ae56d", size = 21365329, upload-time = "2025-07-27T16:31:21.188Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8e/02/72a5aa5b820589dda9a25e329ca752842bfbbaf635e36bc7065a9b42216e/scipy-1.16.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:8dfbb25dffc4c3dd9371d8ab456ca81beeaf6f9e1c2119f179392f0dc1ab7695", size = 23897544, upload-time = "2025-07-27T16:31:25.408Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2b/dc/7122d806a6f9eb8a33532982234bed91f90272e990f414f2830cfe656e0b/scipy-1.16.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f0ebb7204f063fad87fc0a0e4ff4a2ff40b2a226e4ba1b7e34bf4b79bf97cd86", size = 33442112, upload-time = "2025-07-27T16:31:30.62Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/24/39/e383af23564daa1021a5b3afbe0d8d6a68ec639b943661841f44ac92de85/scipy-1.16.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f1b9e5962656f2734c2b285a8745358ecb4e4efbadd00208c80a389227ec61ff", size = 35286594, upload-time = "2025-07-27T16:31:36.112Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/95/47/1a0b0aff40c3056d955f38b0df5d178350c3d74734ec54f9c68d23910be5/scipy-1.16.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e1a106f8c023d57a2a903e771228bf5c5b27b5d692088f457acacd3b54511e4", size = 35665080, upload-time = "2025-07-27T16:31:42.025Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/64/df/ce88803e9ed6e27fe9b9abefa157cf2c80e4fa527cf17ee14be41f790ad4/scipy-1.16.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:709559a1db68a9abc3b2c8672c4badf1614f3b440b3ab326d86a5c0491eafae3", size = 38050306, upload-time = "2025-07-27T16:31:48.109Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6e/6c/a76329897a7cae4937d403e623aa6aaea616a0bb5b36588f0b9d1c9a3739/scipy-1.16.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c0c804d60492a0aad7f5b2bb1862f4548b990049e27e828391ff2bf6f7199998", size = 39427705, upload-time = "2025-07-27T16:31:53.96Z" }, +] + +[[package]] +name = "semantic-version" +version = "2.10.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7d/31/f2289ce78b9b473d582568c234e104d2a342fd658cc288a7553d83bb8595/semantic_version-2.10.0.tar.gz", hash = "sha256:bdabb6d336998cbb378d4b9db3a4b56a1e3235701dc05ea2690d9a997ed5041c", size = 52289, upload-time = "2022-05-26T13:35:23.454Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6a/23/8146aad7d88f4fcb3a6218f41a60f6c2d4e3a72de72da1825dc7c8f7877c/semantic_version-2.10.0-py2.py3-none-any.whl", hash = "sha256:de78a3b8e0feda74cabc54aab2da702113e33ac9d9eb9d2389bcf1f58b7d9177", size = 15552, upload-time = "2022-05-26T13:35:21.206Z" }, +] + +[[package]] +name = "setuptools" +version = "80.9.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "sqlitedict" +version = "2.1.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/12/9a/7620d1e9dcb02839ed6d4b14064e609cdd7a8ae1e47289aa0456796dd9ca/sqlitedict-2.1.0.tar.gz", hash = "sha256:03d9cfb96d602996f1d4c2db2856f1224b96a9c431bdd16e78032a72940f9e8c", size = 21846, upload-time = "2022-12-03T13:39:13.102Z" } + +[[package]] +name = "starlette" +version = "0.47.2" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "anyio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/04/57/d062573f391d062710d4088fa1369428c38d51460ab6fedff920efef932e/starlette-0.47.2.tar.gz", hash = "sha256:6ae9aa5db235e4846decc1e7b79c4f346adf41e9777aebeb49dfd09bbd7023d8", size = 2583948, upload-time = "2025-07-20T17:31:58.522Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f7/1f/b876b1f83aef204198a42dc101613fefccb32258e5428b5f9259677864b4/starlette-0.47.2-py3-none-any.whl", hash = "sha256:c5847e96134e5c5371ee9fac6fdf1a67336d5815e09eb2a01fdb57a351ef915b", size = 72984, upload-time = "2025-07-20T17:31:56.738Z" }, +] + +[[package]] +name = "sympy" +version = "1.14.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "mpmath" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" }, +] + +[[package]] +name = "tabledata" +version = "1.3.4" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "dataproperty" }, + { name = "typepy" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b2/35/171c8977162f1163368406deddde4c59673b62bd0cb2f34948a02effb075/tabledata-1.3.4.tar.gz", hash = "sha256:e9649cab129d718f3bff4150083b77f8a78c30f6634a30caf692b10fdc60cb97", size = 25074, upload-time = "2024-12-31T14:12:31.198Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/08/64/fa4160151976ee4b2cf0c1217a99443ffaeb991956feddfeac9eee9952f8/tabledata-1.3.4-py3-none-any.whl", hash = "sha256:1f56e433bfdeb89f4487abfa48c4603a3b07c5d3a3c7e05ff73dd018c24bd0d4", size = 11820, upload-time = "2024-12-31T14:12:28.584Z" }, +] + +[[package]] +name = "tabulate" +version = "0.9.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", size = 81090, upload-time = "2022-10-06T17:21:48.54Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252, upload-time = "2022-10-06T17:21:44.262Z" }, +] + +[[package]] +name = "tcolorpy" +version = "0.1.7" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/80/cc/44f2d81d8f9093aad81c3467a5bf5718d2b5f786e887b6e4adcfc17ec6b9/tcolorpy-0.1.7.tar.gz", hash = "sha256:0fbf6bf238890bbc2e32662aa25736769a29bf6d880328f310c910a327632614", size = 299437, upload-time = "2024-12-29T15:24:23.847Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/05/a2/ed023f2edd1e011b4d99b6727bce8253842d66c3fbf9ed0a26fc09a92571/tcolorpy-0.1.7-py3-none-any.whl", hash = "sha256:26a59d52027e175a37e0aba72efc99dda43f074db71f55b316d3de37d3251378", size = 8096, upload-time = "2024-12-29T15:24:21.33Z" }, +] + +[[package]] +name = "threadpoolctl" +version = "3.6.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e", size = 21274, upload-time = "2025-03-13T13:49:23.031Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638, upload-time = "2025-03-13T13:49:21.846Z" }, +] + +[[package]] +name = "tokenizers" +version = "0.21.4" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "huggingface-hub" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c2/2f/402986d0823f8d7ca139d969af2917fefaa9b947d1fb32f6168c509f2492/tokenizers-0.21.4.tar.gz", hash = "sha256:fa23f85fbc9a02ec5c6978da172cdcbac23498c3ca9f3645c5c68740ac007880", size = 351253, upload-time = "2025-07-28T15:48:54.325Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/98/c6/fdb6f72bf6454f52eb4a2510be7fb0f614e541a2554d6210e370d85efff4/tokenizers-0.21.4-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:2ccc10a7c3bcefe0f242867dc914fc1226ee44321eb618cfe3019b5df3400133", size = 2863987, upload-time = "2025-07-28T15:48:44.877Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8d/a6/28975479e35ddc751dc1ddc97b9b69bf7fcf074db31548aab37f8116674c/tokenizers-0.21.4-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:5e2f601a8e0cd5be5cc7506b20a79112370b9b3e9cb5f13f68ab11acd6ca7d60", size = 2732457, upload-time = "2025-07-28T15:48:43.265Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/aa/8f/24f39d7b5c726b7b0be95dca04f344df278a3fe3a4deb15a975d194cbb32/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39b376f5a1aee67b4d29032ee85511bbd1b99007ec735f7f35c8a2eb104eade5", size = 3012624, upload-time = "2025-07-28T13:22:43.895Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/58/47/26358925717687a58cb74d7a508de96649544fad5778f0cd9827398dc499/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2107ad649e2cda4488d41dfd031469e9da3fcbfd6183e74e4958fa729ffbf9c6", size = 2939681, upload-time = "2025-07-28T13:22:47.499Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/99/6f/cc300fea5db2ab5ddc2c8aea5757a27b89c84469899710c3aeddc1d39801/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c73012da95afafdf235ba80047699df4384fdc481527448a078ffd00e45a7d9", size = 3247445, upload-time = "2025-07-28T15:48:39.711Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/be/bf/98cb4b9c3c4afd8be89cfa6423704337dc20b73eb4180397a6e0d456c334/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f23186c40395fc390d27f519679a58023f368a0aad234af145e0f39ad1212732", size = 3428014, upload-time = "2025-07-28T13:22:49.569Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/75/c7/96c1cc780e6ca7f01a57c13235dd05b7bc1c0f3588512ebe9d1331b5f5ae/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc88bb34e23a54cc42713d6d98af5f1bf79c07653d24fe984d2d695ba2c922a2", size = 3193197, upload-time = "2025-07-28T13:22:51.471Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f2/90/273b6c7ec78af547694eddeea9e05de771278bd20476525ab930cecaf7d8/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51b7eabb104f46c1c50b486520555715457ae833d5aee9ff6ae853d1130506ff", size = 3115426, upload-time = "2025-07-28T15:48:41.439Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/91/43/c640d5a07e95f1cf9d2c92501f20a25f179ac53a4f71e1489a3dcfcc67ee/tokenizers-0.21.4-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:714b05b2e1af1288bd1bc56ce496c4cebb64a20d158ee802887757791191e6e2", size = 9089127, upload-time = "2025-07-28T15:48:46.472Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/44/a1/dd23edd6271d4dca788e5200a807b49ec3e6987815cd9d0a07ad9c96c7c2/tokenizers-0.21.4-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:1340ff877ceedfa937544b7d79f5b7becf33a4cfb58f89b3b49927004ef66f78", size = 9055243, upload-time = "2025-07-28T15:48:48.539Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/21/2b/b410d6e9021c4b7ddb57248304dc817c4d4970b73b6ee343674914701197/tokenizers-0.21.4-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:3c1f4317576e465ac9ef0d165b247825a2a4078bcd01cba6b54b867bdf9fdd8b", size = 9298237, upload-time = "2025-07-28T15:48:50.443Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b7/0a/42348c995c67e2e6e5c89ffb9cfd68507cbaeb84ff39c49ee6e0a6dd0fd2/tokenizers-0.21.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:c212aa4e45ec0bb5274b16b6f31dd3f1c41944025c2358faaa5782c754e84c24", size = 9461980, upload-time = "2025-07-28T15:48:52.325Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3d/d3/dacccd834404cd71b5c334882f3ba40331ad2120e69ded32cf5fda9a7436/tokenizers-0.21.4-cp39-abi3-win32.whl", hash = "sha256:6c42a930bc5f4c47f4ea775c91de47d27910881902b0f20e4990ebe045a415d0", size = 2329871, upload-time = "2025-07-28T15:48:56.841Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/41/f2/fd673d979185f5dcbac4be7d09461cbb99751554ffb6718d0013af8604cb/tokenizers-0.21.4-cp39-abi3-win_amd64.whl", hash = "sha256:475d807a5c3eb72c59ad9b5fcdb254f6e17f53dfcbb9903233b0dfa9c943b597", size = 2507568, upload-time = "2025-07-28T15:48:55.456Z" }, +] + +[[package]] +name = "tomlkit" +version = "0.13.3" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cc/18/0bbf3884e9eaa38819ebe46a7bd25dcd56b67434402b66a58c4b8e552575/tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1", size = 185207, upload-time = "2025-06-05T07:13:44.947Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bd/75/8539d011f6be8e29f339c42e633aae3cb73bffa95dd0f9adec09b9c58e85/tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0", size = 38901, upload-time = "2025-06-05T07:13:43.546Z" }, +] + +[[package]] +name = "torch" +version = "2.8.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { 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-nvtx-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "setuptools" }, + { name = "sympy" }, + { name = "triton", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "typing-extensions" }, +] +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/49/0c/2fd4df0d83a495bb5e54dca4474c4ec5f9c62db185421563deeb5dabf609/torch-2.8.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:e2fab4153768d433f8ed9279c8133a114a034a61e77a3a104dcdf54388838705", size = 101906089, upload-time = "2025-08-06T14:53:52.631Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/99/a8/6acf48d48838fb8fe480597d98a0668c2beb02ee4755cc136de92a0a956f/torch-2.8.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b2aca0939fb7e4d842561febbd4ffda67a8e958ff725c1c27e244e85e982173c", size = 887913624, upload-time = "2025-08-06T14:56:44.33Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/af/8a/5c87f08e3abd825c7dfecef5a0f1d9aa5df5dd0e3fd1fa2f490a8e512402/torch-2.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:2f4ac52f0130275d7517b03a33d2493bab3693c83dcfadf4f81688ea82147d2e", size = 241326087, upload-time = "2025-08-06T14:53:46.503Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/be/66/5c9a321b325aaecb92d4d1855421e3a055abd77903b7dab6575ca07796db/torch-2.8.0-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:619c2869db3ada2c0105487ba21b5008defcc472d23f8b80ed91ac4a380283b0", size = 73630478, upload-time = "2025-08-06T14:53:57.144Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/10/4e/469ced5a0603245d6a19a556e9053300033f9c5baccf43a3d25ba73e189e/torch-2.8.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:2b2f96814e0345f5a5aed9bf9734efa913678ed19caf6dc2cddb7930672d6128", size = 101936856, upload-time = "2025-08-06T14:54:01.526Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/16/82/3948e54c01b2109238357c6f86242e6ecbf0c63a1af46906772902f82057/torch-2.8.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:65616ca8ec6f43245e1f5f296603e33923f4c30f93d65e103d9e50c25b35150b", size = 887922844, upload-time = "2025-08-06T14:55:50.78Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e3/54/941ea0a860f2717d86a811adf0c2cd01b3983bdd460d0803053c4e0b8649/torch-2.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:659df54119ae03e83a800addc125856effda88b016dfc54d9f65215c3975be16", size = 241330968, upload-time = "2025-08-06T14:54:45.293Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/de/69/8b7b13bba430f5e21d77708b616f767683629fc4f8037564a177d20f90ed/torch-2.8.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:1a62a1ec4b0498930e2543535cf70b1bef8c777713de7ceb84cd79115f553767", size = 73915128, upload-time = "2025-08-06T14:54:34.769Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/15/0e/8a800e093b7f7430dbaefa80075aee9158ec22e4c4fc3c1a66e4fb96cb4f/torch-2.8.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:83c13411a26fac3d101fe8035a6b0476ae606deb8688e904e796a3534c197def", size = 102020139, upload-time = "2025-08-06T14:54:39.047Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4a/15/5e488ca0bc6162c86a33b58642bc577c84ded17c7b72d97e49b5833e2d73/torch-2.8.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:8f0a9d617a66509ded240add3754e462430a6c1fc5589f86c17b433dd808f97a", size = 887990692, upload-time = "2025-08-06T14:56:18.286Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b4/a8/6a04e4b54472fc5dba7ca2341ab219e529f3c07b6941059fbf18dccac31f/torch-2.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a7242b86f42be98ac674b88a4988643b9bc6145437ec8f048fea23f72feb5eca", size = 241603453, upload-time = "2025-08-06T14:55:22.945Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/04/6e/650bb7f28f771af0cb791b02348db8b7f5f64f40f6829ee82aa6ce99aabe/torch-2.8.0-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:7b677e17f5a3e69fdef7eb3b9da72622f8d322692930297e4ccb52fefc6c8211", size = 73632395, upload-time = "2025-08-06T14:55:28.645Z" }, +] + +[[package]] +name = "tqdm" +version = "4.67.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, +] + +[[package]] +name = "tqdm-multiprocess" +version = "0.0.11" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "colorama" }, + { name = "tqdm" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b4/1e/de81bd0f6cb2b61d6ee7ccbf304d99a42a0f53879481536dfb3288ee9a87/tqdm-multiprocess-0.0.11.tar.gz", hash = "sha256:a74002a1222ea9cbe8cdc9bd460108c6009be359621fbee9b92d0515d4d180f7", size = 8082, upload-time = "2020-10-27T06:57:54.313Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/25/7e/0d889fc6c84e3df6b69aaafe893fc77f69b3d968ac9ce574d1c62c688050/tqdm_multiprocess-0.0.11-py3-none-any.whl", hash = "sha256:3ebdf03e7a675150fa0bbceaa9c3c64b8cb556e9ffafa4fe6c078e51820524aa", size = 9817, upload-time = "2020-10-27T06:57:53.167Z" }, +] + +[[package]] +name = "transformers" +version = "4.49.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "filelock" }, + { name = "huggingface-hub" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "regex" }, + { name = "requests" }, + { name = "safetensors" }, + { name = "tokenizers" }, + { name = "tqdm" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/79/50/46573150944f46df8ec968eda854023165a84470b42f69f67c7d475dabc5/transformers-4.49.0.tar.gz", hash = "sha256:7e40e640b5b8dc3f48743f5f5adbdce3660c82baafbd3afdfc04143cdbd2089e", size = 8610952, upload-time = "2025-02-17T15:19:03.614Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/20/37/1f29af63e9c30156a3ed6ebc2754077016577c094f31de7b2631e5d379eb/transformers-4.49.0-py3-none-any.whl", hash = "sha256:6b4fded1c5fee04d384b1014495b4235a2b53c87503d7d592423c06128cbbe03", size = 9970275, upload-time = "2025-02-17T15:18:58.814Z" }, +] + +[[package]] +name = "triton" +version = "3.4.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "setuptools" }, +] +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d0/66/b1eb52839f563623d185f0927eb3530ee4d5ffe9d377cdaf5346b306689e/triton-3.4.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:31c1d84a5c0ec2c0f8e8a072d7fd150cab84a9c239eaddc6706c081bfae4eb04", size = 155560068, upload-time = "2025-07-30T19:58:37.081Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/30/7b/0a685684ed5322d2af0bddefed7906674f67974aa88b0fae6e82e3b766f6/triton-3.4.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00be2964616f4c619193cb0d1b29a99bd4b001d7dc333816073f92cf2a8ccdeb", size = 155569223, upload-time = "2025-07-30T19:58:44.017Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/20/63/8cb444ad5cdb25d999b7d647abac25af0ee37d292afc009940c05b82dda0/triton-3.4.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7936b18a3499ed62059414d7df563e6c163c5e16c3773678a3ee3d417865035d", size = 155659780, upload-time = "2025-07-30T19:58:51.171Z" }, +] + +[[package]] +name = "typepy" +version = "1.3.4" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "mbstrdecoder" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/79/59/4c39942077d7de285f762a91024dbda731be693591732977358f77d120fb/typepy-1.3.4.tar.gz", hash = "sha256:89c1f66de6c6133209c43a94d23431d320ba03ef5db18f241091ea594035d9de", size = 39558, upload-time = "2024-12-29T09:18:15.774Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ee/31/e393c3830bdedd01735bd195c85ac3034b6bcaf6c18142bab60a4047ca36/typepy-1.3.4-py3-none-any.whl", hash = "sha256:d5ed3e0c7f49521bff0603dd08cf8d453371cf68d65a29d3d0038552ccc46e2e", size = 31449, upload-time = "2024-12-29T09:18:13.135Z" }, +] + +[package.optional-dependencies] +datetime = [ + { name = "packaging" }, + { name = "python-dateutil" }, + { name = "pytz" }, +] + +[[package]] +name = "typer" +version = "0.16.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "click" }, + { name = "rich" }, + { name = "shellingham" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c5/8c/7d682431efca5fd290017663ea4588bf6f2c6aad085c7f108c5dbc316e70/typer-0.16.0.tar.gz", hash = "sha256:af377ffaee1dbe37ae9440cb4e8f11686ea5ce4e9bae01b84ae7c63b87f1dd3b", size = 102625, upload-time = "2025-05-26T14:30:31.824Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/76/42/3efaf858001d2c2913de7f354563e3a3a2f0decae3efe98427125a8f441e/typer-0.16.0-py3-none-any.whl", hash = "sha256:1f79bed11d4d02d4310e3c1b7ba594183bcedb0ac73b27a9e5f28f6fb5b98855", size = 46317, upload-time = "2025-05-26T14:30:30.523Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.14.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" }, +] + +[[package]] +name = "tzdata" +version = "2025.2" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.35.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5e/42/e0e305207bb88c6b8d3061399c6a961ffe5fbb7e2aa63c9234df7259e9cd/uvicorn-0.35.0.tar.gz", hash = "sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01", size = 78473, upload-time = "2025-06-28T16:15:46.058Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d2/e2/dc81b1bd1dcfe91735810265e9d26bc8ec5da45b4c0f6237e286819194c3/uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a", size = 66406, upload-time = "2025-06-28T16:15:44.816Z" }, +] + +[[package]] +name = "websockets" +version = "15.0.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437, upload-time = "2025-03-05T20:02:16.706Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096, upload-time = "2025-03-05T20:02:18.832Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332, upload-time = "2025-03-05T20:02:20.187Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152, upload-time = "2025-03-05T20:02:22.286Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096, upload-time = "2025-03-05T20:02:24.368Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523, upload-time = "2025-03-05T20:02:25.669Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790, upload-time = "2025-03-05T20:02:26.99Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165, upload-time = "2025-03-05T20:02:30.291Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160, upload-time = "2025-03-05T20:02:31.634Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395, upload-time = "2025-03-05T20:02:33.017Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841, upload-time = "2025-03-05T20:02:34.498Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, +] + +[[package]] +name = "word2number" +version = "1.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4a/29/a31940c848521f0725f0df6b25dca8917f13a2025b0e8fcbe5d0457e45e6/word2number-1.1.zip", hash = "sha256:70e27a5d387f67b04c71fbb7621c05930b19bfd26efd6851e6e0f9969dcde7d0", size = 9723, upload-time = "2017-06-02T15:45:14.488Z" } + +[[package]] +name = "xxhash" +version = "3.5.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/00/5e/d6e5258d69df8b4ed8c83b6664f2b47d30d2dec551a29ad72a6c69eafd31/xxhash-3.5.0.tar.gz", hash = "sha256:84f2caddf951c9cbf8dc2e22a89d4ccf5d86391ac6418fe81e3c67d0cf60b45f", size = 84241, upload-time = "2024-08-17T09:20:38.972Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/07/0e/1bfce2502c57d7e2e787600b31c83535af83746885aa1a5f153d8c8059d6/xxhash-3.5.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:14470ace8bd3b5d51318782cd94e6f94431974f16cb3b8dc15d52f3b69df8e00", size = 31969, upload-time = "2024-08-17T09:18:24.025Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3f/d6/8ca450d6fe5b71ce521b4e5db69622383d039e2b253e9b2f24f93265b52c/xxhash-3.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:59aa1203de1cb96dbeab595ded0ad0c0056bb2245ae11fac11c0ceea861382b9", size = 30787, upload-time = "2024-08-17T09:18:25.318Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5b/84/de7c89bc6ef63d750159086a6ada6416cc4349eab23f76ab870407178b93/xxhash-3.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08424f6648526076e28fae6ea2806c0a7d504b9ef05ae61d196d571e5c879c84", size = 220959, upload-time = "2024-08-17T09:18:26.518Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fe/86/51258d3e8a8545ff26468c977101964c14d56a8a37f5835bc0082426c672/xxhash-3.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:61a1ff00674879725b194695e17f23d3248998b843eb5e933007ca743310f793", size = 200006, upload-time = "2024-08-17T09:18:27.905Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/02/0a/96973bd325412feccf23cf3680fd2246aebf4b789122f938d5557c54a6b2/xxhash-3.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2f2c61bee5844d41c3eb015ac652a0229e901074951ae48581d58bfb2ba01be", size = 428326, upload-time = "2024-08-17T09:18:29.335Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/11/a7/81dba5010f7e733de88af9555725146fc133be97ce36533867f4c7e75066/xxhash-3.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d32a592cac88d18cc09a89172e1c32d7f2a6e516c3dfde1b9adb90ab5df54a6", size = 194380, upload-time = "2024-08-17T09:18:30.706Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fb/7d/f29006ab398a173f4501c0e4977ba288f1c621d878ec217b4ff516810c04/xxhash-3.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70dabf941dede727cca579e8c205e61121afc9b28516752fd65724be1355cc90", size = 207934, upload-time = "2024-08-17T09:18:32.133Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8a/6e/6e88b8f24612510e73d4d70d9b0c7dff62a2e78451b9f0d042a5462c8d03/xxhash-3.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e5d0ddaca65ecca9c10dcf01730165fd858533d0be84c75c327487c37a906a27", size = 216301, upload-time = "2024-08-17T09:18:33.474Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/af/51/7862f4fa4b75a25c3b4163c8a873f070532fe5f2d3f9b3fc869c8337a398/xxhash-3.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e5b5e16c5a480fe5f59f56c30abdeba09ffd75da8d13f6b9b6fd224d0b4d0a2", size = 203351, upload-time = "2024-08-17T09:18:34.889Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/22/61/8d6a40f288f791cf79ed5bb113159abf0c81d6efb86e734334f698eb4c59/xxhash-3.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149b7914451eb154b3dfaa721315117ea1dac2cc55a01bfbd4df7c68c5dd683d", size = 210294, upload-time = "2024-08-17T09:18:36.355Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/17/02/215c4698955762d45a8158117190261b2dbefe9ae7e5b906768c09d8bc74/xxhash-3.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:eade977f5c96c677035ff39c56ac74d851b1cca7d607ab3d8f23c6b859379cab", size = 414674, upload-time = "2024-08-17T09:18:38.536Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/31/5c/b7a8db8a3237cff3d535261325d95de509f6a8ae439a5a7a4ffcff478189/xxhash-3.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fa9f547bd98f5553d03160967866a71056a60960be00356a15ecc44efb40ba8e", size = 192022, upload-time = "2024-08-17T09:18:40.138Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/78/e3/dd76659b2811b3fd06892a8beb850e1996b63e9235af5a86ea348f053e9e/xxhash-3.5.0-cp312-cp312-win32.whl", hash = "sha256:f7b58d1fd3551b8c80a971199543379be1cee3d0d409e1f6d8b01c1a2eebf1f8", size = 30170, upload-time = "2024-08-17T09:18:42.163Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d9/6b/1c443fe6cfeb4ad1dcf231cdec96eb94fb43d6498b4469ed8b51f8b59a37/xxhash-3.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:fa0cafd3a2af231b4e113fba24a65d7922af91aeb23774a8b78228e6cd785e3e", size = 30040, upload-time = "2024-08-17T09:18:43.699Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0f/eb/04405305f290173acc0350eba6d2f1a794b57925df0398861a20fbafa415/xxhash-3.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:586886c7e89cb9828bcd8a5686b12e161368e0064d040e225e72607b43858ba2", size = 26796, upload-time = "2024-08-17T09:18:45.29Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c9/b8/e4b3ad92d249be5c83fa72916c9091b0965cb0faeff05d9a0a3870ae6bff/xxhash-3.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:37889a0d13b0b7d739cfc128b1c902f04e32de17b33d74b637ad42f1c55101f6", size = 31795, upload-time = "2024-08-17T09:18:46.813Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fc/d8/b3627a0aebfbfa4c12a41e22af3742cf08c8ea84f5cc3367b5de2d039cce/xxhash-3.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:97a662338797c660178e682f3bc180277b9569a59abfb5925e8620fba00b9fc5", size = 30792, upload-time = "2024-08-17T09:18:47.862Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c3/cc/762312960691da989c7cd0545cb120ba2a4148741c6ba458aa723c00a3f8/xxhash-3.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f85e0108d51092bdda90672476c7d909c04ada6923c14ff9d913c4f7dc8a3bc", size = 220950, upload-time = "2024-08-17T09:18:49.06Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fe/e9/cc266f1042c3c13750e86a535496b58beb12bf8c50a915c336136f6168dc/xxhash-3.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2fd827b0ba763ac919440042302315c564fdb797294d86e8cdd4578e3bc7f3", size = 199980, upload-time = "2024-08-17T09:18:50.445Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bf/85/a836cd0dc5cc20376de26b346858d0ac9656f8f730998ca4324921a010b9/xxhash-3.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:82085c2abec437abebf457c1d12fccb30cc8b3774a0814872511f0f0562c768c", size = 428324, upload-time = "2024-08-17T09:18:51.988Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b4/0e/15c243775342ce840b9ba34aceace06a1148fa1630cd8ca269e3223987f5/xxhash-3.5.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07fda5de378626e502b42b311b049848c2ef38784d0d67b6f30bb5008642f8eb", size = 194370, upload-time = "2024-08-17T09:18:54.164Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/87/a1/b028bb02636dfdc190da01951d0703b3d904301ed0ef6094d948983bef0e/xxhash-3.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c279f0d2b34ef15f922b77966640ade58b4ccdfef1c4d94b20f2a364617a493f", size = 207911, upload-time = "2024-08-17T09:18:55.509Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/80/d5/73c73b03fc0ac73dacf069fdf6036c9abad82de0a47549e9912c955ab449/xxhash-3.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:89e66ceed67b213dec5a773e2f7a9e8c58f64daeb38c7859d8815d2c89f39ad7", size = 216352, upload-time = "2024-08-17T09:18:57.073Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b6/2a/5043dba5ddbe35b4fe6ea0a111280ad9c3d4ba477dd0f2d1fe1129bda9d0/xxhash-3.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bcd51708a633410737111e998ceb3b45d3dbc98c0931f743d9bb0a209033a326", size = 203410, upload-time = "2024-08-17T09:18:58.54Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a2/b2/9a8ded888b7b190aed75b484eb5c853ddd48aa2896e7b59bbfbce442f0a1/xxhash-3.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3ff2c0a34eae7df88c868be53a8dd56fbdf592109e21d4bfa092a27b0bf4a7bf", size = 210322, upload-time = "2024-08-17T09:18:59.943Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/98/62/440083fafbc917bf3e4b67c2ade621920dd905517e85631c10aac955c1d2/xxhash-3.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:4e28503dccc7d32e0b9817aa0cbfc1f45f563b2c995b7a66c4c8a0d232e840c7", size = 414725, upload-time = "2024-08-17T09:19:01.332Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/75/db/009206f7076ad60a517e016bb0058381d96a007ce3f79fa91d3010f49cc2/xxhash-3.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a6c50017518329ed65a9e4829154626f008916d36295b6a3ba336e2458824c8c", size = 192070, upload-time = "2024-08-17T09:19:03.007Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1f/6d/c61e0668943a034abc3a569cdc5aeae37d686d9da7e39cf2ed621d533e36/xxhash-3.5.0-cp313-cp313-win32.whl", hash = "sha256:53a068fe70301ec30d868ece566ac90d873e3bb059cf83c32e76012c889b8637", size = 30172, upload-time = "2024-08-17T09:19:04.355Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/96/14/8416dce965f35e3d24722cdf79361ae154fa23e2ab730e5323aa98d7919e/xxhash-3.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:80babcc30e7a1a484eab952d76a4f4673ff601f54d5142c26826502740e70b43", size = 30041, upload-time = "2024-08-17T09:19:05.435Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/27/ee/518b72faa2073f5aa8e3262408d284892cb79cf2754ba0c3a5870645ef73/xxhash-3.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:4811336f1ce11cac89dcbd18f3a25c527c16311709a89313c3acaf771def2d4b", size = 26801, upload-time = "2024-08-17T09:19:06.547Z" }, +] + +[[package]] +name = "yarl" +version = "1.20.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3c/fb/efaa23fa4e45537b827620f04cf8f3cd658b76642205162e072703a5b963/yarl-1.20.1.tar.gz", hash = "sha256:d017a4997ee50c91fd5466cef416231bb82177b93b029906cefc542ce14c35ac", size = 186428, upload-time = "2025-06-10T00:46:09.923Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5f/9a/cb7fad7d73c69f296eda6815e4a2c7ed53fc70c2f136479a91c8e5fbdb6d/yarl-1.20.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdcc4cd244e58593a4379fe60fdee5ac0331f8eb70320a24d591a3be197b94a9", size = 133667, upload-time = "2025-06-10T00:43:44.369Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/67/38/688577a1cb1e656e3971fb66a3492501c5a5df56d99722e57c98249e5b8a/yarl-1.20.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b29a2c385a5f5b9c7d9347e5812b6f7ab267193c62d282a540b4fc528c8a9d2a", size = 91025, upload-time = "2025-06-10T00:43:46.295Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/50/ec/72991ae51febeb11a42813fc259f0d4c8e0507f2b74b5514618d8b640365/yarl-1.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1112ae8154186dfe2de4732197f59c05a83dc814849a5ced892b708033f40dc2", size = 89709, upload-time = "2025-06-10T00:43:48.22Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/99/da/4d798025490e89426e9f976702e5f9482005c548c579bdae792a4c37769e/yarl-1.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90bbd29c4fe234233f7fa2b9b121fb63c321830e5d05b45153a2ca68f7d310ee", size = 352287, upload-time = "2025-06-10T00:43:49.924Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1a/26/54a15c6a567aac1c61b18aa0f4b8aa2e285a52d547d1be8bf48abe2b3991/yarl-1.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:680e19c7ce3710ac4cd964e90dad99bf9b5029372ba0c7cbfcd55e54d90ea819", size = 345429, upload-time = "2025-06-10T00:43:51.7Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d6/95/9dcf2386cb875b234353b93ec43e40219e14900e046bf6ac118f94b1e353/yarl-1.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a979218c1fdb4246a05efc2cc23859d47c89af463a90b99b7c56094daf25a16", size = 365429, upload-time = "2025-06-10T00:43:53.494Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/91/b2/33a8750f6a4bc224242a635f5f2cff6d6ad5ba651f6edcccf721992c21a0/yarl-1.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255b468adf57b4a7b65d8aad5b5138dce6a0752c139965711bdcb81bc370e1b6", size = 363862, upload-time = "2025-06-10T00:43:55.766Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/98/28/3ab7acc5b51f4434b181b0cee8f1f4b77a65919700a355fb3617f9488874/yarl-1.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a97d67108e79cfe22e2b430d80d7571ae57d19f17cda8bb967057ca8a7bf5bfd", size = 355616, upload-time = "2025-06-10T00:43:58.056Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/36/a3/f666894aa947a371724ec7cd2e5daa78ee8a777b21509b4252dd7bd15e29/yarl-1.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8570d998db4ddbfb9a590b185a0a33dbf8aafb831d07a5257b4ec9948df9cb0a", size = 339954, upload-time = "2025-06-10T00:43:59.773Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f1/81/5f466427e09773c04219d3450d7a1256138a010b6c9f0af2d48565e9ad13/yarl-1.20.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:97c75596019baae7c71ccf1d8cc4738bc08134060d0adfcbe5642f778d1dca38", size = 365575, upload-time = "2025-06-10T00:44:02.051Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2e/e3/e4b0ad8403e97e6c9972dd587388940a032f030ebec196ab81a3b8e94d31/yarl-1.20.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1c48912653e63aef91ff988c5432832692ac5a1d8f0fb8a33091520b5bbe19ef", size = 365061, upload-time = "2025-06-10T00:44:04.196Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ac/99/b8a142e79eb86c926f9f06452eb13ecb1bb5713bd01dc0038faf5452e544/yarl-1.20.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4c3ae28f3ae1563c50f3d37f064ddb1511ecc1d5584e88c6b7c63cf7702a6d5f", size = 364142, upload-time = "2025-06-10T00:44:06.527Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/34/f2/08ed34a4a506d82a1a3e5bab99ccd930a040f9b6449e9fd050320e45845c/yarl-1.20.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c5e9642f27036283550f5f57dc6156c51084b458570b9d0d96100c8bebb186a8", size = 381894, upload-time = "2025-06-10T00:44:08.379Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/92/f8/9a3fbf0968eac704f681726eff595dce9b49c8a25cd92bf83df209668285/yarl-1.20.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2c26b0c49220d5799f7b22c6838409ee9bc58ee5c95361a4d7831f03cc225b5a", size = 383378, upload-time = "2025-06-10T00:44:10.51Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/af/85/9363f77bdfa1e4d690957cd39d192c4cacd1c58965df0470a4905253b54f/yarl-1.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:564ab3d517e3d01c408c67f2e5247aad4019dcf1969982aba3974b4093279004", size = 374069, upload-time = "2025-06-10T00:44:12.834Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/35/99/9918c8739ba271dcd935400cff8b32e3cd319eaf02fcd023d5dcd487a7c8/yarl-1.20.1-cp312-cp312-win32.whl", hash = "sha256:daea0d313868da1cf2fac6b2d3a25c6e3a9e879483244be38c8e6a41f1d876a5", size = 81249, upload-time = "2025-06-10T00:44:14.731Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/eb/83/5d9092950565481b413b31a23e75dd3418ff0a277d6e0abf3729d4d1ce25/yarl-1.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:48ea7d7f9be0487339828a4de0360d7ce0efc06524a48e1810f945c45b813698", size = 86710, upload-time = "2025-06-10T00:44:16.716Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8a/e1/2411b6d7f769a07687acee88a062af5833cf1966b7266f3d8dfb3d3dc7d3/yarl-1.20.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0b5ff0fbb7c9f1b1b5ab53330acbfc5247893069e7716840c8e7d5bb7355038a", size = 131811, upload-time = "2025-06-10T00:44:18.933Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b2/27/584394e1cb76fb771371770eccad35de400e7b434ce3142c2dd27392c968/yarl-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:14f326acd845c2b2e2eb38fb1346c94f7f3b01a4f5c788f8144f9b630bfff9a3", size = 90078, upload-time = "2025-06-10T00:44:20.635Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bf/9a/3246ae92d4049099f52d9b0fe3486e3b500e29b7ea872d0f152966fc209d/yarl-1.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f60e4ad5db23f0b96e49c018596707c3ae89f5d0bd97f0ad3684bcbad899f1e7", size = 88748, upload-time = "2025-06-10T00:44:22.34Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a3/25/35afe384e31115a1a801fbcf84012d7a066d89035befae7c5d4284df1e03/yarl-1.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49bdd1b8e00ce57e68ba51916e4bb04461746e794e7c4d4bbc42ba2f18297691", size = 349595, upload-time = "2025-06-10T00:44:24.314Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/28/2d/8aca6cb2cabc8f12efcb82749b9cefecbccfc7b0384e56cd71058ccee433/yarl-1.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:66252d780b45189975abfed839616e8fd2dbacbdc262105ad7742c6ae58f3e31", size = 342616, upload-time = "2025-06-10T00:44:26.167Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0b/e9/1312633d16b31acf0098d30440ca855e3492d66623dafb8e25b03d00c3da/yarl-1.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59174e7332f5d153d8f7452a102b103e2e74035ad085f404df2e40e663a22b28", size = 361324, upload-time = "2025-06-10T00:44:27.915Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bc/a0/688cc99463f12f7669eec7c8acc71ef56a1521b99eab7cd3abb75af887b0/yarl-1.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3968ec7d92a0c0f9ac34d5ecfd03869ec0cab0697c91a45db3fbbd95fe1b653", size = 359676, upload-time = "2025-06-10T00:44:30.041Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/af/44/46407d7f7a56e9a85a4c207724c9f2c545c060380718eea9088f222ba697/yarl-1.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1a4fbb50e14396ba3d375f68bfe02215d8e7bc3ec49da8341fe3157f59d2ff5", size = 352614, upload-time = "2025-06-10T00:44:32.171Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b1/91/31163295e82b8d5485d31d9cf7754d973d41915cadce070491778d9c9825/yarl-1.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11a62c839c3a8eac2410e951301309426f368388ff2f33799052787035793b02", size = 336766, upload-time = "2025-06-10T00:44:34.494Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b4/8e/c41a5bc482121f51c083c4c2bcd16b9e01e1cf8729e380273a952513a21f/yarl-1.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:041eaa14f73ff5a8986b4388ac6bb43a77f2ea09bf1913df7a35d4646db69e53", size = 364615, upload-time = "2025-06-10T00:44:36.856Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e3/5b/61a3b054238d33d70ea06ebba7e58597891b71c699e247df35cc984ab393/yarl-1.20.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:377fae2fef158e8fd9d60b4c8751387b8d1fb121d3d0b8e9b0be07d1b41e83dc", size = 360982, upload-time = "2025-06-10T00:44:39.141Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/df/a3/6a72fb83f8d478cb201d14927bc8040af901811a88e0ff2da7842dd0ed19/yarl-1.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1c92f4390e407513f619d49319023664643d3339bd5e5a56a3bebe01bc67ec04", size = 369792, upload-time = "2025-06-10T00:44:40.934Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7c/af/4cc3c36dfc7c077f8dedb561eb21f69e1e9f2456b91b593882b0b18c19dc/yarl-1.20.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d25ddcf954df1754ab0f86bb696af765c5bfaba39b74095f27eececa049ef9a4", size = 382049, upload-time = "2025-06-10T00:44:42.854Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/19/3a/e54e2c4752160115183a66dc9ee75a153f81f3ab2ba4bf79c3c53b33de34/yarl-1.20.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:909313577e9619dcff8c31a0ea2aa0a2a828341d92673015456b3ae492e7317b", size = 384774, upload-time = "2025-06-10T00:44:45.275Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9c/20/200ae86dabfca89060ec6447649f219b4cbd94531e425e50d57e5f5ac330/yarl-1.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:793fd0580cb9664548c6b83c63b43c477212c0260891ddf86809e1c06c8b08f1", size = 374252, upload-time = "2025-06-10T00:44:47.31Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/83/75/11ee332f2f516b3d094e89448da73d557687f7d137d5a0f48c40ff211487/yarl-1.20.1-cp313-cp313-win32.whl", hash = "sha256:468f6e40285de5a5b3c44981ca3a319a4b208ccc07d526b20b12aeedcfa654b7", size = 81198, upload-time = "2025-06-10T00:44:49.164Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ba/ba/39b1ecbf51620b40ab402b0fc817f0ff750f6d92712b44689c2c215be89d/yarl-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:495b4ef2fea40596bfc0affe3837411d6aa3371abcf31aac0ccc4bdd64d4ef5c", size = 86346, upload-time = "2025-06-10T00:44:51.182Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/43/c7/669c52519dca4c95153c8ad96dd123c79f354a376346b198f438e56ffeb4/yarl-1.20.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f60233b98423aab21d249a30eb27c389c14929f47be8430efa7dbd91493a729d", size = 138826, upload-time = "2025-06-10T00:44:52.883Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6a/42/fc0053719b44f6ad04a75d7f05e0e9674d45ef62f2d9ad2c1163e5c05827/yarl-1.20.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6f3eff4cc3f03d650d8755c6eefc844edde99d641d0dcf4da3ab27141a5f8ddf", size = 93217, upload-time = "2025-06-10T00:44:54.658Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4f/7f/fa59c4c27e2a076bba0d959386e26eba77eb52ea4a0aac48e3515c186b4c/yarl-1.20.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:69ff8439d8ba832d6bed88af2c2b3445977eba9a4588b787b32945871c2444e3", size = 92700, upload-time = "2025-06-10T00:44:56.784Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2f/d4/062b2f48e7c93481e88eff97a6312dca15ea200e959f23e96d8ab898c5b8/yarl-1.20.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cf34efa60eb81dd2645a2e13e00bb98b76c35ab5061a3989c7a70f78c85006d", size = 347644, upload-time = "2025-06-10T00:44:59.071Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/89/47/78b7f40d13c8f62b499cc702fdf69e090455518ae544c00a3bf4afc9fc77/yarl-1.20.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8e0fe9364ad0fddab2688ce72cb7a8e61ea42eff3c7caeeb83874a5d479c896c", size = 323452, upload-time = "2025-06-10T00:45:01.605Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/eb/2b/490d3b2dc66f52987d4ee0d3090a147ea67732ce6b4d61e362c1846d0d32/yarl-1.20.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f64fbf81878ba914562c672024089e3401974a39767747691c65080a67b18c1", size = 346378, upload-time = "2025-06-10T00:45:03.946Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/66/ad/775da9c8a94ce925d1537f939a4f17d782efef1f973039d821cbe4bcc211/yarl-1.20.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6342d643bf9a1de97e512e45e4b9560a043347e779a173250824f8b254bd5ce", size = 353261, upload-time = "2025-06-10T00:45:05.992Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4b/23/0ed0922b47a4f5c6eb9065d5ff1e459747226ddce5c6a4c111e728c9f701/yarl-1.20.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56dac5f452ed25eef0f6e3c6a066c6ab68971d96a9fb441791cad0efba6140d3", size = 335987, upload-time = "2025-06-10T00:45:08.227Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3e/49/bc728a7fe7d0e9336e2b78f0958a2d6b288ba89f25a1762407a222bf53c3/yarl-1.20.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7d7f497126d65e2cad8dc5f97d34c27b19199b6414a40cb36b52f41b79014be", size = 329361, upload-time = "2025-06-10T00:45:10.11Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/93/8f/b811b9d1f617c83c907e7082a76e2b92b655400e61730cd61a1f67178393/yarl-1.20.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:67e708dfb8e78d8a19169818eeb5c7a80717562de9051bf2413aca8e3696bf16", size = 346460, upload-time = "2025-06-10T00:45:12.055Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/70/fd/af94f04f275f95da2c3b8b5e1d49e3e79f1ed8b6ceb0f1664cbd902773ff/yarl-1.20.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:595c07bc79af2494365cc96ddeb772f76272364ef7c80fb892ef9d0649586513", size = 334486, upload-time = "2025-06-10T00:45:13.995Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/84/65/04c62e82704e7dd0a9b3f61dbaa8447f8507655fd16c51da0637b39b2910/yarl-1.20.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7bdd2f80f4a7df852ab9ab49484a4dee8030023aa536df41f2d922fd57bf023f", size = 342219, upload-time = "2025-06-10T00:45:16.479Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/91/95/459ca62eb958381b342d94ab9a4b6aec1ddec1f7057c487e926f03c06d30/yarl-1.20.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c03bfebc4ae8d862f853a9757199677ab74ec25424d0ebd68a0027e9c639a390", size = 350693, upload-time = "2025-06-10T00:45:18.399Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a6/00/d393e82dd955ad20617abc546a8f1aee40534d599ff555ea053d0ec9bf03/yarl-1.20.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:344d1103e9c1523f32a5ed704d576172d2cabed3122ea90b1d4e11fe17c66458", size = 355803, upload-time = "2025-06-10T00:45:20.677Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9e/ed/c5fb04869b99b717985e244fd93029c7a8e8febdfcffa06093e32d7d44e7/yarl-1.20.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:88cab98aa4e13e1ade8c141daeedd300a4603b7132819c484841bb7af3edce9e", size = 341709, upload-time = "2025-06-10T00:45:23.221Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/24/fd/725b8e73ac2a50e78a4534ac43c6addf5c1c2d65380dd48a9169cc6739a9/yarl-1.20.1-cp313-cp313t-win32.whl", hash = "sha256:b121ff6a7cbd4abc28985b6028235491941b9fe8fe226e6fdc539c977ea1739d", size = 86591, upload-time = "2025-06-10T00:45:25.793Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/94/c3/b2e9f38bc3e11191981d57ea08cab2166e74ea770024a646617c9cddd9f6/yarl-1.20.1-cp313-cp313t-win_amd64.whl", hash = "sha256:541d050a355bbbc27e55d906bc91cb6fe42f96c01413dd0f4ed5a5240513874f", size = 93003, upload-time = "2025-06-10T00:45:27.752Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b4/2d/2345fce04cfd4bee161bf1e7d9cdc702e3e16109021035dbb24db654a622/yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77", size = 46542, upload-time = "2025-06-10T00:46:07.521Z" }, +] + +[[package]] +name = "zstandard" +version = "0.23.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation == 'PyPy'" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ed/f6/2ac0287b442160a89d726b17a9184a4c615bb5237db763791a7fd16d9df1/zstandard-0.23.0.tar.gz", hash = "sha256:b2d8c62d08e7255f68f7a740bae85b3c9b8e5466baa9cbf7f57f1cde0ac6bc09", size = 681701, upload-time = "2024-07-15T00:18:06.141Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7b/83/f23338c963bd9de687d47bf32efe9fd30164e722ba27fb59df33e6b1719b/zstandard-0.23.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b4567955a6bc1b20e9c31612e615af6b53733491aeaa19a6b3b37f3b65477094", size = 788713, upload-time = "2024-07-15T00:15:35.815Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5b/b3/1a028f6750fd9227ee0b937a278a434ab7f7fdc3066c3173f64366fe2466/zstandard-0.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e172f57cd78c20f13a3415cc8dfe24bf388614324d25539146594c16d78fcc8", size = 633459, upload-time = "2024-07-15T00:15:37.995Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/26/af/36d89aae0c1f95a0a98e50711bc5d92c144939efc1f81a2fcd3e78d7f4c1/zstandard-0.23.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0e166f698c5a3e914947388c162be2583e0c638a4703fc6a543e23a88dea3c1", size = 4945707, upload-time = "2024-07-15T00:15:39.872Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cd/2e/2051f5c772f4dfc0aae3741d5fc72c3dcfe3aaeb461cc231668a4db1ce14/zstandard-0.23.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12a289832e520c6bd4dcaad68e944b86da3bad0d339ef7989fb7e88f92e96072", size = 5306545, upload-time = "2024-07-15T00:15:41.75Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0a/9e/a11c97b087f89cab030fa71206963090d2fecd8eb83e67bb8f3ffb84c024/zstandard-0.23.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d50d31bfedd53a928fed6707b15a8dbeef011bb6366297cc435accc888b27c20", size = 5337533, upload-time = "2024-07-15T00:15:44.114Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fc/79/edeb217c57fe1bf16d890aa91a1c2c96b28c07b46afed54a5dcf310c3f6f/zstandard-0.23.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72c68dda124a1a138340fb62fa21b9bf4848437d9ca60bd35db36f2d3345f373", size = 5436510, upload-time = "2024-07-15T00:15:46.509Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/81/4f/c21383d97cb7a422ddf1ae824b53ce4b51063d0eeb2afa757eb40804a8ef/zstandard-0.23.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53dd9d5e3d29f95acd5de6802e909ada8d8d8cfa37a3ac64836f3bc4bc5512db", size = 4859973, upload-time = "2024-07-15T00:15:49.939Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ab/15/08d22e87753304405ccac8be2493a495f529edd81d39a0870621462276ef/zstandard-0.23.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6a41c120c3dbc0d81a8e8adc73312d668cd34acd7725f036992b1b72d22c1772", size = 4936968, upload-time = "2024-07-15T00:15:52.025Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/eb/fa/f3670a597949fe7dcf38119a39f7da49a8a84a6f0b1a2e46b2f71a0ab83f/zstandard-0.23.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:40b33d93c6eddf02d2c19f5773196068d875c41ca25730e8288e9b672897c105", size = 5467179, upload-time = "2024-07-15T00:15:54.971Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4e/a9/dad2ab22020211e380adc477a1dbf9f109b1f8d94c614944843e20dc2a99/zstandard-0.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9206649ec587e6b02bd124fb7799b86cddec350f6f6c14bc82a2b70183e708ba", size = 4848577, upload-time = "2024-07-15T00:15:57.634Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/08/03/dd28b4484b0770f1e23478413e01bee476ae8227bbc81561f9c329e12564/zstandard-0.23.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76e79bc28a65f467e0409098fa2c4376931fd3207fbeb6b956c7c476d53746dd", size = 4693899, upload-time = "2024-07-15T00:16:00.811Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2b/64/3da7497eb635d025841e958bcd66a86117ae320c3b14b0ae86e9e8627518/zstandard-0.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:66b689c107857eceabf2cf3d3fc699c3c0fe8ccd18df2219d978c0283e4c508a", size = 5199964, upload-time = "2024-07-15T00:16:03.669Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/43/a4/d82decbab158a0e8a6ebb7fc98bc4d903266bce85b6e9aaedea1d288338c/zstandard-0.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9c236e635582742fee16603042553d276cca506e824fa2e6489db04039521e90", size = 5655398, upload-time = "2024-07-15T00:16:06.694Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f2/61/ac78a1263bc83a5cf29e7458b77a568eda5a8f81980691bbc6eb6a0d45cc/zstandard-0.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a8fffdbd9d1408006baaf02f1068d7dd1f016c6bcb7538682622c556e7b68e35", size = 5191313, upload-time = "2024-07-15T00:16:09.758Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e7/54/967c478314e16af5baf849b6ee9d6ea724ae5b100eb506011f045d3d4e16/zstandard-0.23.0-cp312-cp312-win32.whl", hash = "sha256:dc1d33abb8a0d754ea4763bad944fd965d3d95b5baef6b121c0c9013eaf1907d", size = 430877, upload-time = "2024-07-15T00:16:11.758Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/75/37/872d74bd7739639c4553bf94c84af7d54d8211b626b352bc57f0fd8d1e3f/zstandard-0.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:64585e1dba664dc67c7cdabd56c1e5685233fbb1fc1966cfba2a340ec0dfff7b", size = 495595, upload-time = "2024-07-15T00:16:13.731Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/80/f1/8386f3f7c10261fe85fbc2c012fdb3d4db793b921c9abcc995d8da1b7a80/zstandard-0.23.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:576856e8594e6649aee06ddbfc738fec6a834f7c85bf7cadd1c53d4a58186ef9", size = 788975, upload-time = "2024-07-15T00:16:16.005Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/16/e8/cbf01077550b3e5dc86089035ff8f6fbbb312bc0983757c2d1117ebba242/zstandard-0.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:38302b78a850ff82656beaddeb0bb989a0322a8bbb1bf1ab10c17506681d772a", size = 633448, upload-time = "2024-07-15T00:16:17.897Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/06/27/4a1b4c267c29a464a161aeb2589aff212b4db653a1d96bffe3598f3f0d22/zstandard-0.23.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2240ddc86b74966c34554c49d00eaafa8200a18d3a5b6ffbf7da63b11d74ee2", size = 4945269, upload-time = "2024-07-15T00:16:20.136Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7c/64/d99261cc57afd9ae65b707e38045ed8269fbdae73544fd2e4a4d50d0ed83/zstandard-0.23.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ef230a8fd217a2015bc91b74f6b3b7d6522ba48be29ad4ea0ca3a3775bf7dd5", size = 5306228, upload-time = "2024-07-15T00:16:23.398Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7a/cf/27b74c6f22541f0263016a0fd6369b1b7818941de639215c84e4e94b2a1c/zstandard-0.23.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:774d45b1fac1461f48698a9d4b5fa19a69d47ece02fa469825b442263f04021f", size = 5336891, upload-time = "2024-07-15T00:16:26.391Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fa/18/89ac62eac46b69948bf35fcd90d37103f38722968e2981f752d69081ec4d/zstandard-0.23.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f77fa49079891a4aab203d0b1744acc85577ed16d767b52fc089d83faf8d8ed", size = 5436310, upload-time = "2024-07-15T00:16:29.018Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a8/a8/5ca5328ee568a873f5118d5b5f70d1f36c6387716efe2e369010289a5738/zstandard-0.23.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac184f87ff521f4840e6ea0b10c0ec90c6b1dcd0bad2f1e4a9a1b4fa177982ea", size = 4859912, upload-time = "2024-07-15T00:16:31.871Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ea/ca/3781059c95fd0868658b1cf0440edd832b942f84ae60685d0cfdb808bca1/zstandard-0.23.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c363b53e257246a954ebc7c488304b5592b9c53fbe74d03bc1c64dda153fb847", size = 4936946, upload-time = "2024-07-15T00:16:34.593Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ce/11/41a58986f809532742c2b832c53b74ba0e0a5dae7e8ab4642bf5876f35de/zstandard-0.23.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e7792606d606c8df5277c32ccb58f29b9b8603bf83b48639b7aedf6df4fe8171", size = 5466994, upload-time = "2024-07-15T00:16:36.887Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/83/e3/97d84fe95edd38d7053af05159465d298c8b20cebe9ccb3d26783faa9094/zstandard-0.23.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a0817825b900fcd43ac5d05b8b3079937073d2b1ff9cf89427590718b70dd840", size = 4848681, upload-time = "2024-07-15T00:16:39.709Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6e/99/cb1e63e931de15c88af26085e3f2d9af9ce53ccafac73b6e48418fd5a6e6/zstandard-0.23.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9da6bc32faac9a293ddfdcb9108d4b20416219461e4ec64dfea8383cac186690", size = 4694239, upload-time = "2024-07-15T00:16:41.83Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ab/50/b1e703016eebbc6501fc92f34db7b1c68e54e567ef39e6e59cf5fb6f2ec0/zstandard-0.23.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fd7699e8fd9969f455ef2926221e0233f81a2542921471382e77a9e2f2b57f4b", size = 5200149, upload-time = "2024-07-15T00:16:44.287Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/aa/e0/932388630aaba70197c78bdb10cce2c91fae01a7e553b76ce85471aec690/zstandard-0.23.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d477ed829077cd945b01fc3115edd132c47e6540ddcd96ca169facff28173057", size = 5655392, upload-time = "2024-07-15T00:16:46.423Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/02/90/2633473864f67a15526324b007a9f96c96f56d5f32ef2a56cc12f9548723/zstandard-0.23.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ce8b52c5987b3e34d5674b0ab529a4602b632ebab0a93b07bfb4dfc8f8a33", size = 5191299, upload-time = "2024-07-15T00:16:49.053Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b0/4c/315ca5c32da7e2dc3455f3b2caee5c8c2246074a61aac6ec3378a97b7136/zstandard-0.23.0-cp313-cp313-win32.whl", hash = "sha256:a9b07268d0c3ca5c170a385a0ab9fb7fdd9f5fd866be004c4ea39e44edce47dd", size = 430862, upload-time = "2024-07-15T00:16:51.003Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a2/bf/c6aaba098e2d04781e8f4f7c0ba3c7aa73d00e4c436bcc0cf059a66691d1/zstandard-0.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:f3513916e8c645d0610815c257cbfd3242adfd5c4cfa78be514e5a3ebb42a41b", size = 495578, upload-time = "2024-07-15T00:16:53.135Z" }, +] diff --git a/FlexMDM/.gitignore b/FlexMDM/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..aa5d7c6e566e4297d5560f1e5fa3fa0290660b5e --- /dev/null +++ b/FlexMDM/.gitignore @@ -0,0 +1,31 @@ +# Python-generated files +__pycache__/ +*.py[oc] +build/ +dist/ +wheels/ +*.egg-info + +# Virtual environments +.venv +.python-version + + +# Training checkpoints +checkpoints/ + +# Lightning logs +lightning_logs/ + +# slurm_logs +slurm_logs +outputs/ + +# wandb +wandb/ + +# tmp directory for file dumps +tmp/ + + +d1/SFT/results/ \ No newline at end of file diff --git a/FlexMDM/.pre-commit-config.yaml b/FlexMDM/.pre-commit-config.yaml new file mode 100644 index 0000000000000000000000000000000000000000..d704282085180439e99562801d849b06470914f5 --- /dev/null +++ b/FlexMDM/.pre-commit-config.yaml @@ -0,0 +1,7 @@ +repos: +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.11.10 + hooks: + - id: ruff + args: [--fix] + - id: ruff-format \ No newline at end of file diff --git a/FlexMDM/.vscode/settings.json b/FlexMDM/.vscode/settings.json new file mode 100644 index 0000000000000000000000000000000000000000..c6b0102242d50f549ce572b8de601c9bcf4486b5 --- /dev/null +++ b/FlexMDM/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "jupyter.notebookFileRoot": "${workspaceFolder}", + "python.analysis.autoImportCompletions": true +} \ No newline at end of file diff --git a/FlexMDM/README.md b/FlexMDM/README.md new file mode 100644 index 0000000000000000000000000000000000000000..3eafbc946ef6f63118b4d14e4e5491b458755f26 --- /dev/null +++ b/FlexMDM/README.md @@ -0,0 +1,46 @@ +### Dependency management +This repository uses the uv package manager +To install uv pleaes refer to the official uv website and make sure to add the uv binary to $PATH. + +Installing dependency is as simple as, +``` +uv sync +``` + +The Flash Attention build script is not compatible with uv and require you CUDA 11.4+ + +To properly install Flash Attentoin: + +1. Load CUDA +``` +module load cuda/12.4.1-fasrc01 +``` + +2. Install FlashAttention build script dependencies and build with --no-build-isolation +``` +uv pip install torch setuptools +uv add flash-attn --no-build-isolation +uv sync +``` + + +### Instructions for using VSCode Jupyter Notebook for testing +Notebooks are provided to play with the sampling algorithms with trained models. +To use VSCode Jupyter with GPUs, the easiest way is to login to a compute node and establish a connection tunnel. +This is very easy to do with the VSCode server module on the FASRC cluster, + +``` +salloc [GPU Things] +module load vscode +code tunnel +``` + +### Pre-commit Hook +The codebase employ a ruff pre-commit hook for style fomartting. + +After you've installed the necessary dependencies, install the pre-commit hooks by, + +``` +pre-commit install +``` + diff --git a/FlexMDM/bregman.py b/FlexMDM/bregman.py new file mode 100644 index 0000000000000000000000000000000000000000..825f26eacb1b1eaab7b52f3e0406923138681ab7 --- /dev/null +++ b/FlexMDM/bregman.py @@ -0,0 +1,19 @@ +# A file of bregman divergences +import torch + + +def mse(x, y): + sq_diff = (x - y) ** 2 + if x.shape != y.shape: + assert False, "x and y must have the same shape" + return sq_diff.reshape(sq_diff.size(0), -1).sum(dim=-1) + + +# TODO: check if this formulation is correct +def jump_kernel_elbo(x, y, eps=1e-6): + # x_safe: true length + # y_safe: predicted length + x_safe = torch.clamp(x, min=eps) + y_safe = torch.clamp(y, min=eps) + + return y_safe - x_safe + x_safe * (torch.log(x_safe) - torch.log(y_safe)) diff --git a/FlexMDM/config/bracket/any_order.yaml b/FlexMDM/config/bracket/any_order.yaml new file mode 100644 index 0000000000000000000000000000000000000000..a1833bc6cb428831f107bd5147df4d25c6deeb1b --- /dev/null +++ b/FlexMDM/config/bracket/any_order.yaml @@ -0,0 +1,42 @@ +trainer: "any-order-flow" +dataset: "bracket" +model: + hidden_size: 256 + n_heads: 4 + cond_dim: 64 + dropout: 0.0 + n_blocks: 4 +interpolant: + type: "any-order" + max_length: 64 + tokens: 4 + mask_token: 0 + pad_token: 3 + insert_schedule: + type: "sin" + unmask_schedule: + type: "linear" +training: + batch_size: 2048 + per_gpu_batch_size: 2048 # Gradient accumulation happens automatically + cpus: 1 + learning_rate: 0.0001 + devices: 1 + nodes: 1 + weight_decay: 0.01 + max_steps: null + num_epochs: 10000 + checkpoint_dir: "/n/netscratch/albergo_lab/Lab/brianlck/interpretable-flow/checkpoints/bracket/any_order" + save_top_k: 1 + save_every_n_epochs: 1 + loss_fn: + unmask: "elbo" + insert: "expectation" + warmup_steps: 1000 + ema_decay: 0.99 + filter_max_length: false + # resume_path: "/n/netscratch/albergo_lab/Lab/brianlck/interpretable-flow/checkpoints/bracket/any_order/20250711-145941/last.ckpt" +wandb: + entity: "jaeyeon_kim-harvard-university" + project: "interpretable-flow" + name: "bracket-any-order" diff --git a/FlexMDM/config/bracket/mdm.yaml b/FlexMDM/config/bracket/mdm.yaml new file mode 100644 index 0000000000000000000000000000000000000000..b6eb54d8a4f51c2a74630afd517163bde074165a --- /dev/null +++ b/FlexMDM/config/bracket/mdm.yaml @@ -0,0 +1,36 @@ +trainer: "mdm" +dataset: "bracket" +model: + hidden_size: 256 + n_heads: 4 + cond_dim: 64 + dropout: 0.1 + n_blocks: 4 +interpolant: + type: "mdm" + max_length: 64 + tokens: 4 + mask_token: 0 + pad_token: 3 + unmask_schedule: + type: "linear" +training: + batch_size: 2 + per_gpu_batch_size: 2 + learning_rate: 0.0001 + devices: 1 + nodes: 1 + cpus: 1 + weight_decay: 0.01 + max_steps: null + num_epochs: 100 + checkpoint_dir: "/n/netscratch/albergo_lab/Lab/brianlck/interpretable-flow/checkpoints/bracket/mdm" + save_top_k: 1 + save_every_n_epochs: 1 + warmup_steps: 1000 + ema_decay: 0.9999 + filter_max_length: false +wandb: + entity: "jaeyeon_kim-harvard-university" + project: "interpretable-flow" + name: "mdm-any-order" diff --git a/FlexMDM/config/bracket/semi_auto.yaml b/FlexMDM/config/bracket/semi_auto.yaml new file mode 100644 index 0000000000000000000000000000000000000000..277d4dffbfb77b9bbab8b198ac590412f8ed694d --- /dev/null +++ b/FlexMDM/config/bracket/semi_auto.yaml @@ -0,0 +1,27 @@ +# This file is currently unusable in the current version +trainer: "semiautoregressive" +dataset: "bracket" +model: + hidden_size: 256 + n_heads: 4 + cond_dim: 64 + dropout: 0.1 + n_blocks: 4 +interpolant: + type: "semi-auto" + max_length: 64 + tokens: 4 + mask_token: 0 + pad_token: 3 + mask_schedule: + type: "geometric" + min: 5 + max: 0.01 +training: + batch_size: 32 + learning_rate: 0.0001 + devices: 1 + num_epochs: 100 + checkpoint_dir: "checkpoints/bracket/semi-auto" + save_top_k: 3 + save_every_n_epochs: 5 \ No newline at end of file diff --git a/FlexMDM/config/config.yaml b/FlexMDM/config/config.yaml new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/FlexMDM/config/openwebtext/any_order_small.yaml b/FlexMDM/config/openwebtext/any_order_small.yaml new file mode 100644 index 0000000000000000000000000000000000000000..eb0cabddc7fbdfca6c1bb60a29415035e8142e35 --- /dev/null +++ b/FlexMDM/config/openwebtext/any_order_small.yaml @@ -0,0 +1,42 @@ +trainer: "any-order-flow" +dataset: "openwebtext" +model: + hidden_size: 768 + n_heads: 12 + cond_dim: 128 + dropout: 0.05 + n_blocks: 12 +interpolant: + type: "any-order" + tokens: null # filled in automatically + pad_token: null # filled in automatically + mask_token: null # filled in automatically + max_length: 1024 + insert_schedule: + type: "linear" + unmask_schedule: + type: "linear" +training: + only_embed_insert: true + batch_size: 1024 + per_gpu_batch_size: 64 # Gradient accumulation happens automatically + cpus: 4 + learning_rate: 3e-4 + nodes: 4 + devices: 4 + max_steps: 1000000 + weight_decay: 0.03 + checkpoint_dir: "checkpoints/openwebtext/any_order" + save_top_k: 1 + save_every_n_epochs: 1 + loss_fn: + unmask: "elbo" + insert: "expectation" + reset_lr: false + warmup_steps: 2000 + ema_decay: 0.9999 + filter_max_length: false +wandb: + entity: "jaeyeon_kim-harvard-university" + project: "interpretable-flow" + name: "openwebtext-any-order" diff --git a/FlexMDM/config/openwebtext/mdm.yaml b/FlexMDM/config/openwebtext/mdm.yaml new file mode 100644 index 0000000000000000000000000000000000000000..a636d3cc2ae6fa7af4a7fa66f5ffd6843dc716ab --- /dev/null +++ b/FlexMDM/config/openwebtext/mdm.yaml @@ -0,0 +1,35 @@ +trainer: "mdm" +dataset: "openwebtext" +model: + hidden_size: 768 + n_heads: 12 + cond_dim: 128 + dropout: 0.05 + n_blocks: 12 +interpolant: + type: "mdm" + tokens: null # filled in automatically + pad_token: null # filled in automatically + mask_token: null # filled in automatically + max_length: 1024 + unmask_schedule: + type: "linear" +training: + batch_size: 1024 + per_gpu_batch_size: 64 + cpus: 4 + learning_rate: 3e-4 + nodes: 4 + devices: 4 + max_steps: 500000 + weight_decay: 0.03 + checkpoint_dir: "checkpoints/openwebtext/mdm" + save_top_k: 1 + save_every_n_epochs: 1 + warmup_steps: 2000 + ema_decay: 0.9999 + filter_max_length: false +wandb: + entity: "jaeyeon_kim-harvard-university" + project: "interpretable-flow" + name: "openwebtext-mdm" diff --git a/FlexMDM/config/wikitext2/any_order.yaml b/FlexMDM/config/wikitext2/any_order.yaml new file mode 100644 index 0000000000000000000000000000000000000000..8818ccec2f77b41b792bec7f91468bd9b05e675c --- /dev/null +++ b/FlexMDM/config/wikitext2/any_order.yaml @@ -0,0 +1,38 @@ +trainer: "any-order-flow" +dataset: "wikitext2" +model: + hidden_size: 256 + n_heads: 4 + cond_dim: 64 + dropout: 0.1 + n_blocks: 4 +interpolant: + type: "any-order" + tokens: null # filled in automatically + pad_token: null # filled in automatically + mask_token: null # filled in automatically + max_length: 512 + insert_schedule: + type: "linear" + unmask_schedule: + type: "linear" +training: + batch_size: 512 + learning_rate: 3e-4 + nodes: 2 + devices: 4 + max_steps: 10000 + weight_decay: 0.03 + checkpoint_dir: "checkpoints/wikitext2/any_order" + save_top_k: 1 + save_every_n_epochs: 1 + loss_fn: + unmask: "elbo" + insert: "expectation" + warmup_steps: 2000 + ema_decay: 0.9999 + filter_max_length: false +wandb: + entity: "jaeyeon_kim-harvard-university" + project: "interpretable-flow" + name: "wikitext2-any-order" diff --git a/FlexMDM/config/wikitext2/autoregressive.yaml b/FlexMDM/config/wikitext2/autoregressive.yaml new file mode 100644 index 0000000000000000000000000000000000000000..3168b8ae858d8a78699165b4ca0f594b44e6fe4c --- /dev/null +++ b/FlexMDM/config/wikitext2/autoregressive.yaml @@ -0,0 +1,26 @@ +trainer: "autoregressive" +dataset: "wikitext2" +model: + hidden_size: 256 + n_heads: 4 + cond_dim: 64 + dropout: 0.1 + n_blocks: 4 +interpolant: + max_length: 512 + tokens: null # filled in automatically + pad_token: null # filled in automatically + mask_token: null # filled in automatically +training: + batch_size: 32 + learning_rate: 0.0001 + devices: 1 + num_epochs: 5 + checkpoint_dir: "checkpoints/wikitext2" + save_top_k: 1 + save_every_n_epochs: 1 +wandb: + entity: "jaeyeon_kim-harvard-university" + project: "interpretable-flow" + name: "wikitext2-any-order" + \ No newline at end of file diff --git a/FlexMDM/config/wikitext2/mdm.yaml b/FlexMDM/config/wikitext2/mdm.yaml new file mode 100644 index 0000000000000000000000000000000000000000..6b06b23833e6c79de7668a1f0d51f13a9735ce2c --- /dev/null +++ b/FlexMDM/config/wikitext2/mdm.yaml @@ -0,0 +1,27 @@ +trainer: "mdm" +dataset: "wikitext2" +model: + hidden_size: 256 + n_heads: 4 + cond_dim: 64 + dropout: 0.1 + n_blocks: 4 +interpolant: + max_length: 512 + tokens: null # filled in automatically + pad_token: null # filled in automatically + mask_token: null # filled in automatically + unmask_schedule: + type: "linear" +training: + batch_size: 32 + learning_rate: 0.0001 + devices: 1 + num_epochs: 5 + checkpoint_dir: "checkpoints/wikitext2" + save_top_k: 1 + save_every_n_epochs: 1 +wandb: + entity: "jaeyeon_kim-harvard-university" + project: "interpretable-flow" + name: "wikitext2-any-order" diff --git a/FlexMDM/data/__init__.py b/FlexMDM/data/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..0ce2a88717b6d3209f7d815863fd0b77345bec33 --- /dev/null +++ b/FlexMDM/data/__init__.py @@ -0,0 +1,65 @@ +from omegaconf import DictConfig +from .parenthesis import BracketDataset +from .text import get_text_dataset, setup_tokeniser_from_dataset, TEXT_DATASETS +from typing import Optional +from transformers import AutoTokenizer +from dataclasses import dataclass +from torch.utils.data import DataLoader + + +@dataclass +class DatasetBundle: + train_loader: DataLoader + val_loader: Optional[DataLoader] = None + tokeniser: Optional[AutoTokenizer] = None + + +def setup_data_and_update_config(config: DictConfig) -> DatasetBundle: + """ + Get the dataset and update the config with token information for text datasets. + """ + tokeniser = None + if config.dataset in TEXT_DATASETS: + tokeniser = setup_tokeniser_from_dataset(config.dataset) + train_set = get_text_dataset( + config.dataset, + split="train", + max_length=config.interpolant.max_length, + filter_max_length=config.training.filter_max_length, + ) + val_set = get_text_dataset( + config.dataset, + split="validation", + max_length=config.interpolant.max_length, + filter_max_length=config.training.filter_max_length, + ) + config.interpolant.tokens = len(tokeniser) + config.interpolant.pad_token = tokeniser.pad_token_id + config.interpolant.mask_token = tokeniser.mask_token_id + + if config.dataset == "bracket": + train_set = BracketDataset(2048, {4: 0.1, 16: 0.4, 32: 0.4, 64: 0.1}) + val_set = BracketDataset(300, {4: 0.1, 16: 0.4, 32: 0.4, 64: 0.1}) + + train_loader = DataLoader( + train_set, + batch_size=config.training.per_gpu_batch_size, + shuffle=True, + drop_last=True, + num_workers=config.training.cpus, + pin_memory=True, + persistent_workers=True, + ) + val_loader = DataLoader( + val_set, + batch_size=config.training.per_gpu_batch_size, + shuffle=False, + drop_last=True, + num_workers=config.training.cpus, + pin_memory=True, + persistent_workers=True, + ) + + return DatasetBundle( + train_loader=train_loader, val_loader=val_loader, tokeniser=tokeniser + ) diff --git a/FlexMDM/data/gsm8k.py b/FlexMDM/data/gsm8k.py new file mode 100644 index 0000000000000000000000000000000000000000..5dbd606409d36743f2cea8c7b694573c908a0fa2 --- /dev/null +++ b/FlexMDM/data/gsm8k.py @@ -0,0 +1,98 @@ +import torch +import numpy as np +import torch.nn.functional as F +from transformers import AutoTokenizer, AutoModel +from tqdm import tqdm +import time +import random +import re +from datasets import load_dataset +from parsers import Parser, is_equiv +import torch.distributed as dist + +GSM_SYSTEM_PROMPT = """You are a math expert. You will be given a question to solve. Solve it step by step. Wrap the final answer in a \\boxed{}. +Respond in the following format: + +Your reasoning here + + +\\boxed{...} +""" + + +class GSM8KDataset(torch.utils.data.Dataset): + def __init__( + self, + tokenizer, + num_examples=0, + add_reasoning=True, + system_prompt=GSM_SYSTEM_PROMPT, + subsample=-1, + ): + self.tokenizer = tokenizer + self.num_examples = num_examples + self.add_reasoning = add_reasoning + self.system_prompt = system_prompt + self.load_test_dataset() + self.create_few_shot_prompt() + + self.subsample = ( + np.random.choice(len(self.dataset), subsample, replace=False) + if subsample != -1 + else np.arange(len(self.dataset)) + ) + print(f"evaluating {len(self.subsample)} examples") + assert subsample <= len(self.dataset), "Subsample size is greater than dataset size" + + def __len__(self): + return len(self.subsample) + + def load_test_dataset(self): + self.dataset = load_dataset("gsm8k", "main", split="test") + + def create_prompt(self, input_text): + # Format similar to your chat function + if self.num_examples > 0: + prompt = f"{self.few_shot_prompt}\n\nQuestion: {input_text}\nAnswer:\n" + else: + prompt = input_text + messages = [{"role": "user", "content": self.system_prompt + "\n\n" + prompt}] + user_input = self.tokenizer.apply_chat_template(messages, add_generation_prompt=True, tokenize=False) + if self.add_reasoning: + return user_input + "" + else: + return user_input + + def load_few_shot_examples(self): + if isinstance(self.dataset, GSM8KDataset): + train_data = load_dataset("gsm8k", "main", split="train") + examples = random.sample(range(len(train_data)), self.num_examples) + return [train_data[example] for example in examples] + else: + return [] + + def create_few_shot_prompt(self): + """Create few-shot prompt from dataset examples""" + few_shot_examples = self.load_few_shot_examples() + + formatted_examples = [] + for example in few_shot_examples: + input_text = example["question"] + answer = example["answer"] + formatted_examples.append(f"Question: {input_text}\nAnswer:\n{answer}") + self.few_shot_prompt = "\n\n".join(formatted_examples) + + def __getitem__(self, idx): + question = self.dataset[self.subsample[idx].item()]["question"] + answer = Parser.extract_answer_gsm8k(self.dataset[self.subsample[idx].item()]["answer"]) + prompt = self.create_prompt(question) + return prompt, question, answer + + def collate_fn(self, batch): + prompts = [item[0] for item in batch] + questions = [item[1] for item in batch] + answers = [item[2] for item in batch] + input_ids = self.tokenizer( + prompts, padding_side="left", return_tensors="pt", padding="longest" + ).input_ids + return {"input_ids": input_ids, "questions": questions, "answers": answers, "prompts": prompts} \ No newline at end of file diff --git a/FlexMDM/data/parenthesis.py b/FlexMDM/data/parenthesis.py new file mode 100644 index 0000000000000000000000000000000000000000..24216c4f09706d2679df5bd56da7a2c0fd04de99 --- /dev/null +++ b/FlexMDM/data/parenthesis.py @@ -0,0 +1,72 @@ +from torch.utils.data import Dataset +import numpy as np +import torch + + +def generate_bracket(length: int, seq: str = ""): + import random + + if length == 0: + return seq + p = random.randint(0, 1) + if p == 0 or seq == "": + return generate_bracket(length - 2, "(" + seq + "(") + else: + return seq + generate_bracket(length, "") + + +class BracketDataset(Dataset): + def __init__(self, n, length_probs): + lengths = list(length_probs.keys()) + probs = [length_probs[k] for k in lengths] + self.data = [] + + # Track actual length distribution + length_counts = {length: 0 for length in lengths} + + for _ in range(n): + L = int(np.random.choice(lengths, p=probs)) + seq = generate_bracket(L) + mapped = [1 if c == "(" else 2 for c in seq] + mapped += [3] * (64 - len(mapped)) + self.data.append(torch.tensor(mapped, dtype=torch.long)) + + # Count actual sequence length + actual_length = len(seq) + if actual_length in length_counts: + length_counts[actual_length] += 1 + else: + length_counts[actual_length] = 1 + + # Print length distribution + print("Length distribution in dataset:") + for length, count in sorted(length_counts.items()): + print(f" Length {length}: {count} sequences ({count/n:.2%})") + + @staticmethod + def parse_tensor(tensor: torch.Tensor): + if tensor.dim() == 1: + result = "" + mapping = {0: "m", 1: "(", 2: ")", 3: ""} + for i in range(tensor.size(0)): + result += mapping[int(tensor[i].item())] + return result + elif tensor.dim() == 2: + return [ + BracketDataset.parse_tensor(tensor[i]) for i in range(tensor.size(0)) + ] + else: + raise ValueError("input cannot have dimension more than 2") + + def __len__(self): + return len(self.data) + + def __getitem__(self, idx): + return self.data[idx] + + +if __name__ == "__main__": + ds = BracketDataset(1000, {4: 0.1, 8: 0.2, 32: 0.3, 64: 0.4}) + + for i in range(len(ds)): + print(ds[i]) diff --git a/FlexMDM/data/text.py b/FlexMDM/data/text.py new file mode 100644 index 0000000000000000000000000000000000000000..b3b26b8fbf6be14e54298bff3abf5ce9caa90133 --- /dev/null +++ b/FlexMDM/data/text.py @@ -0,0 +1,239 @@ +import re +from transformers import GPT2TokenizerFast, GPTNeoXTokenizerFast +from datasets import Dataset, load_dataset +from typing import Literal, List + +TEXT_DATASETS = ["wikitext2", "openwebtext"] +MIN_LEN = 50 + + +def wt_detokeniser(string): + # contractions + string = string.replace("s '", "s'") + string = re.sub(r"/' [0-9]/", r"/'[0-9]/", string) + # number separators + string = string.replace(" @-@ ", "-") + string = string.replace(" @,@ ", ",") + string = string.replace(" @.@ ", ".") + # punctuation + string = string.replace(" : ", ": ") + string = string.replace(" ; ", "; ") + string = string.replace(" . ", ". ") + string = string.replace(" ! ", "! ") + string = string.replace(" ? ", "? ") + string = string.replace(" , ", ", ") + # double brackets + string = re.sub(r"\(\s*([^\)]*?)\s*\)", r"(\1)", string) + string = re.sub(r"\[\s*([^\]]*?)\s*\]", r"[\1]", string) + string = re.sub(r"{\s*([^}]*?)\s*}", r"{\1}", string) + string = re.sub(r"\"\s*([^\"]*?)\s*\"", r'"\1"', string) + string = re.sub(r"'\s*([^']*?)\s*'", r"'\1'", string) + # miscellaneous + string = string.replace("= = = =", "====") + string = string.replace("= = =", "===") + string = string.replace("= =", "==") + string = string.replace(" " + chr(176) + " ", chr(176)) + string = string.replace(" \n", "\n") + string = string.replace("\n ", "\n") + string = string.replace(" N ", " 1 ") + string = string.replace(" 's", "'s") + return string + + +def setup_tokeniser(tokeniser_name: str) -> GPT2TokenizerFast: + match tokeniser_name: + case "gpt2": + tokeniser = GPT2TokenizerFast.from_pretrained("gpt2") + case "gpt-neo": + tokeniser = GPTNeoXTokenizerFast.from_pretrained("EleutherAI/gpt-neox-20b") + case _: + raise ValueError(f"Tokeniser {tokeniser_name} not supported") + tokeniser.add_special_tokens( + { + "pad_token": "[PAD]", + "mask_token": "[MASK]", + } + ) + return tokeniser + + +def find_delimiter_positions(tokens, delimiter_tokens): + """Return the start indices where the delimiter occurs in the token sequence.""" + positions = [] + n = len(delimiter_tokens) + for i in range(len(tokens) - n + 1): + if tokens[i : i + n] == delimiter_tokens: + positions.append(i) + return positions + + +def recursive_split(tokens, max_length, delimiter_tokens): + if len(tokens) <= max_length: + return [tokens] + + # Find all positions where the delimiter sequence occurs + split_candidates = find_delimiter_positions(tokens, delimiter_tokens) + if not split_candidates: + # Safe fallback: naive split + return [ + tokens[i : min(i + max_length, len(tokens))] + for i in range(0, len(tokens), max_length) + ] + + # Find delimiter closest to the midpoint + midpoint = len(tokens) // 2 + split_point = min(split_candidates, key=lambda x: abs(x - midpoint)) + + # Recurse on both sides, skipping the delimiter + dlen = len(delimiter_tokens) + left = recursive_split(tokens[:split_point], max_length, delimiter_tokens) + right = recursive_split(tokens[split_point + dlen :], max_length, delimiter_tokens) + + return left + right + + +def preprocess_batch(batch, pad_token, max_length, delimiter, detokeniser, tokeniser): + all_input_ids = [] + all_lengths = [] + + for text in batch["text"]: + if detokeniser is not None: + text = detokeniser(text) + + tokens = tokeniser.encode(text, add_special_tokens=False) + chunks = recursive_split(tokens, max_length, delimiter) + + all_input_ids.extend( + [ + c + [pad_token] * (max_length - len(c)) if len(c) < max_length else c + for c in chunks + ] + ) + all_lengths.extend([len(chunk) for chunk in chunks]) + + return { + "input_ids": all_input_ids, + "length": all_lengths, + } + + +def setup_tokeniser_from_dataset(dataset_name: str): + tokeniser = None + match dataset_name: + case "wikitext2" | "openwebtext": + tokeniser = setup_tokeniser("gpt2") + case "dclm": + tokeniser = setup_tokeniser("gpt-neo") + case _: + raise ValueError(f"Tokeniser for dataset {dataset_name} not supported") + + return tokeniser + + +def decode_sequence_with_mask( + seqs: List[List[int]], tokeniser: GPT2TokenizerFast, pad_token: int, mask_token: int +) -> List[str]: + """ + Decode a sequence with visible mask tokens. + """ + decoded = [] + for seq in seqs: + tokens = tokeniser.convert_ids_to_tokens(seq) + filtered = [] + for tok, tok_id in zip(tokens, seq): + if tok_id == pad_token: + continue + if tok_id == mask_token: + filtered.append("[MASK]") + else: + filtered.append(tok) + text = tokeniser.convert_tokens_to_string(filtered) + decoded.append(text) + return decoded + + +def get_text_dataset( + name: str, + split: Literal["train", "validation", "test"], + cache_dir=None, + max_length=1024, + num_proc=64, + filter_max_length=True, +) -> Dataset: + match name: + case "wikitext2": + dataset = load_dataset( + "wikitext", "wikitext-2-raw-v1", cache_dir=cache_dir, split=split + ) + case "openwebtext": + ds_all = load_dataset(name, cache_dir=cache_dir) + train_ds = ds_all["train"] + if split in ["train", "validation"]: + split_data = train_ds.train_test_split(test_size=0.02, seed=42) + dataset = ( + split_data["train"] if split == "train" else split_data["test"] + ) + else: + raise ValueError(f"Dataset {name} does not support split {split}") + case _: + raise ValueError(f"Dataset {name} not supported") + + match name: + case "wikitext2": + detokeniser = wt_detokeniser + case "openwebtext": + detokeniser = None + case "dclm": + detokeniser = None + case _: + raise ValueError(f"Dataset {name} not supported") + + tokeniser = setup_tokeniser_from_dataset(name) + pad_token = tokeniser.pad_token_id + + if filter_max_length: + + def preprocess(sample): + text = sample["text"] + if detokeniser is not None: + text = detokeniser(text) + text = tokeniser(text, return_attention_mask=False) + if len(text["input_ids"]) < MIN_LEN: + return {"input_ids": []} + text["input_ids"] += max(0, max_length - len(text["input_ids"])) * [ + pad_token + ] + return text + + tokenised_dataset = dataset.map( + preprocess, + num_proc=num_proc, + load_from_cache_file=True, + remove_columns=["text"], + ) + tokenised_dataset = tokenised_dataset.filter( + lambda x: 0 < len(x["input_ids"]) <= max_length, + num_proc=num_proc, + load_from_cache_file=True, + ) + tokenised_dataset = tokenised_dataset.with_format("torch") + + return tokenised_dataset + else: + tokenised_dataset = dataset.map( + lambda batch: preprocess_batch( + batch, + pad_token=pad_token, + max_length=max_length, + detokeniser=detokeniser, + tokeniser=tokeniser, + delimiter=[198, 198], + ), + batched=True, + num_proc=num_proc, + remove_columns=["text"], + ) + + tokenised_dataset = tokenised_dataset.with_format("torch") + + return tokenised_dataset diff --git a/FlexMDM/evaluate_samples.py b/FlexMDM/evaluate_samples.py new file mode 100644 index 0000000000000000000000000000000000000000..7395ac0ea8542fef733819fde02d352f00fc8eba --- /dev/null +++ b/FlexMDM/evaluate_samples.py @@ -0,0 +1,450 @@ +from transformers import LlamaForCausalLM, GPT2LMHeadModel, AutoTokenizer +import torch +import math +from collections import Counter +import argparse +from torch.cuda.amp import autocast +import torch.nn.functional as F + +import json +import matplotlib.pyplot as plt +import os +from data import get_text_dataset +from data.text import setup_tokeniser_from_dataset + +llama_model_path = "meta-llama/Llama-2-7b-hf" +gpt2_model_path = "gpt2-large" + + +def get_reference_text_dataset(): + dataset = get_text_dataset( + "openwebtext", + split="train", + max_length=1024, + filter_max_length=False + )[:5000]["input_ids"] + tokeniser = setup_tokeniser_from_dataset("openwebtext") + return tokeniser.batch_decode(dataset, skip_special_tokens=True) + + +def batch_reduce(batch, func, reduce_fn, init, step=16): + """ + Function signature: Tensor[B, L] -> func:(Tensor[B', L] -> A) -> reduce_fn:(B -> A -> B) -> init:B' -> steps:int -> B + """ + result = init + for i in range(0, len(batch), step): + sub_batch = batch[i : min(i + step, len(batch))] + sub_result = func(sub_batch) + result = reduce_fn(result, sub_result) + return result + + +@torch.no_grad() +def compute_generative_perplexity( + text_samples, max_length: int = 1024, retokenize: bool = True, + input_is_tokenized: bool = False, tokenizer=None, model_type="llama" +) -> None: + # load the specified model based on model_type + if model_type == "llama": + eval_model = LlamaForCausalLM.from_pretrained( + llama_model_path, + torch_dtype=torch.float16, + ).eval() + model_path = llama_model_path + elif model_type == "gpt2-xl": + eval_model = GPT2LMHeadModel.from_pretrained( + gpt2_model_path, + torch_dtype=torch.float16, + ).eval() + model_path = gpt2_model_path + else: + raise ValueError(f"Unsupported model type: {model_type}") + + eval_model = eval_model.to("cuda") + + if tokenizer is None: + eval_model_tokenizer = AutoTokenizer.from_pretrained(model_path) + eval_model_tokenizer.pad_token = eval_model_tokenizer.eos_token + else: + eval_model_tokenizer = tokenizer + + # tokenize the batch or use pre-tokenized input + if input_is_tokenized: + # If input is already token IDs, create the tensor and pad if necessary + max_len = max(len(seq) for seq in text_samples) + padded_max_len = min(max_len, max_length) + input_ids = torch.ones((len(text_samples), padded_max_len), + dtype=torch.long) * eval_model_tokenizer.pad_token_id + + for i, seq in enumerate(text_samples): + seq_len = min(len(seq), padded_max_len) + input_ids[i, :seq_len] = torch.tensor(seq[:seq_len]) + + input_ids = input_ids.to(eval_model.device) + + print(input_ids) + else: + # tokenize the text samples + tokenized = eval_model_tokenizer( + text_samples, + return_tensors="pt", + padding="max_length", + truncation=True, + max_length=max_length, + ).to(eval_model.device) + input_ids = tokenized["input_ids"] + + eos_token_id = eval_model_tokenizer.eos_token_id + eos_mask = input_ids == eos_token_id + first_eos = eos_mask.cumsum(dim=-1) == 1 + + # generative perplexity + with autocast(), torch.no_grad(): + outputs = eval_model(input_ids) + logits = outputs.logits + + logits = logits.transpose( + -1, -2 + ) # size b X D X N, D = the number of possible tokens + nlls = F.cross_entropy(logits[..., :-1], input_ids[..., 1:], reduction="none") + effective_mask = (first_eos[..., 1:] + (input_ids[..., 1:] != eos_token_id)).bool() + nlls = nlls * effective_mask + + # compute per-sample perplexity + likelihood_list = [] + for b in range(input_ids.size(0)): + nll = nlls[b] + mask = effective_mask[b] + likelihood = nll.sum() / mask.sum() + likelihood_list.append(likelihood.exp().item()) + + return likelihood_list + + +def compute_entropy(samples: list, model_name: str = llama_model_path, + input_is_tokenized: bool = False, tokenizer=None): + """ + Compute the entropy of each text sample using subword tokens. + Can accept either text samples or pre-tokenized token IDs. + """ + # initialize tokenizer if not provided + if tokenizer is None: + tokenizer = AutoTokenizer.from_pretrained(model_name, use_fast=False) + + # use provided token IDs or encode text samples + if input_is_tokenized: + token_id_seqs = samples + else: + # encode each sample into subword IDs (no special tokens) + token_id_seqs = [ + tokenizer.encode(sample, add_special_tokens=False) for sample in samples + ] + + # compute per-sample entropy + entropies = [] + for seq in token_id_seqs: + counts = Counter(seq) + total = sum(counts.values()) + entropy = ( + -sum((cnt / total) * math.log(cnt / total, 2) for cnt in counts.values()) + if total > 0 + else 0.0 + ) + entropies.append(entropy) + return entropies + +def compute_mauve_score(candidate_samples, reference_samples): + import mauve + score = mauve.compute_mauve(p_text=candidate_samples, q_text=reference_samples, device_id=0, max_text_length=1024, verbose=False) + return score.mauve + +def main(): + parser = argparse.ArgumentParser( + description="Compute average entropy, generative perplexity, and mauve score for a list of text samples." + ) + parser.add_argument( + "--input-json", + type=str, + help="Path to a JSON file containing a list of strings", + ) + parser.add_argument( + "--batch-size", + type=int, + default=16, + help="Batch size for computing generative perplexity", + ) + parser.add_argument( + "--length-plot-output", + type=str, + default="length_distribution.png", + help="Output path for the sentence length distribution plot", + ) + parser.add_argument( + "--perplexity-plot-output", + type=str, + default=None, # Will be derived from length-plot-output + help="Output path for the perplexity vs length scatter plot", + ) + parser.add_argument( + "--results-output", + type=str, + default=None, + help="Path to JSON file to save computed metrics", + ) + parser.add_argument( + "--eval-mode", + type=str, + choices=["sentence", "chunk"], + default="sentence", + help="sentence: eval each input as one; chunk: tokenize & split into 1024‐length segments", + ) + parser.add_argument( + "--model-type", + type=str, + choices=["llama", "gpt2-large"], + default="llama", + help="Model to use for generative perplexity evaluation", + ) + # New flags to control metric evaluation (default false) + parser.add_argument("--entropy", action="store_true", default=False, help="Evaluate entropy") + parser.add_argument("--perplexity", action="store_true", default=False, help="Evaluate generative perplexity") + parser.add_argument("--mauve", action="store_true", default=False, help="Evaluate mauve score") + parser.add_argument("--reference-perplexity", action="store_true", default=False, help="Evaluate reference text perplexity") + args = parser.parse_args() + + # Derive perplexity plot path from length plot path if not specified + if args.perplexity_plot_output is None: + base, ext = os.path.splitext(args.length_plot_output) + args.perplexity_plot_output = f"{base}_perplexity{ext}" + + with open(args.input_json, "r", encoding="utf-8") as f: + samples = json.load(f) + + # choose sentence‐level or chunk‐level inputs + if args.eval_mode == "chunk": + # pre‐load tokenizer based on model type + model_path = llama_model_path if args.model_type == "llama" else gpt2_model_path + tokenizer = AutoTokenizer.from_pretrained(model_path, use_fast=False) + tokenizer.pad_token = tokenizer.eos_token + chunk_size = 1024 + + # Tokenize all samples + token_id_seqs = [tokenizer.encode(s, add_special_tokens=False) for s in samples] + + # Concatenate all sentences with EOS tokens between them + concatenated_tokens = [] + for seq in token_id_seqs: + concatenated_tokens.extend(seq) + concatenated_tokens.append(tokenizer.eos_token_id) # Add EOS between sentences + + # Truncate concatenated_tokens to be a multiple of chunk_size + truncated_length = (len(concatenated_tokens) // chunk_size) * chunk_size + concatenated_tokens = concatenated_tokens[:truncated_length] + + # Split the concatenated tokens into chunks of size chunk_size + chunks = [] + for i in range(0, len(concatenated_tokens), chunk_size): + chunks.append(concatenated_tokens[i:i + chunk_size]) + + # Keep chunks as token IDs for direct use + target_samples = chunks + use_tokenized_input = True + else: + target_samples = samples + use_tokenized_input = False + + # Conditionally compute entropy + if args.entropy: + entropy_list = compute_entropy( + target_samples, + input_is_tokenized=use_tokenized_input, + tokenizer=tokenizer if use_tokenized_input else None + ) + avg_entropy = sum(entropy_list) / len(entropy_list) + print(f"Average entropy: {avg_entropy:.4f}") + else: + avg_entropy = None + print("Entropy evaluation skipped") + + # Conditionally compute generative perplexity + if args.perplexity: + all_perps = batch_reduce( + target_samples, + lambda batch: compute_generative_perplexity( + batch, + input_is_tokenized=use_tokenized_input, + tokenizer=tokenizer if use_tokenized_input else None, + model_type=args.model_type + ), + lambda acc, res: acc + res, + init=[], + step=args.batch_size, + ) + avg_perp = sum(all_perps) / len(all_perps) + print(f"Average generative perplexity: {avg_perp:.4f}") + else: + avg_perp = None + all_perps = None + print("Generative perplexity evaluation skipped") + + # Conditionally compute reference text perplexity + if args.reference_perplexity: + print("Computing reference text perplexity...") + reference_samples = get_reference_text_dataset() + reference_perps = batch_reduce( + reference_samples, + lambda batch: compute_generative_perplexity( + batch, + input_is_tokenized=False, + tokenizer=None, + model_type=args.model_type + ), + lambda acc, res: acc + res, + init=[], + step=args.batch_size, + ) + avg_reference_perp = sum(reference_perps) / len(reference_perps) + print(f"Average reference perplexity: {avg_reference_perp:.4f}") + else: + avg_reference_perp = None + reference_perps = None + reference_samples = None + print("Reference perplexity evaluation skipped") + + # Conditionally compute mauve score + if args.mauve: + if reference_samples is None: + reference_samples = get_reference_text_dataset() + mauve_score = compute_mauve_score(samples, reference_samples) + print(f"Mauve score: {mauve_score:.4f}") + else: + mauve_score = None + print("Mauve evaluation skipped") + + # Calculate lengths early for use in filtered perplexities + gpt2_tokenizer = AutoTokenizer.from_pretrained(gpt2_model_path) + lengths = [len(gpt2_tokenizer.encode(s, add_special_tokens=False)) for s in samples] + + # Conditionally create perplexity vs. tokenized length plot when perplexity is evaluated + filtered_perplexities = None + reference_filtered_perplexities = None + + if args.perplexity and args.eval_mode == "sentence": + idx = [] + val = [] + for i in range(0, 1024): + _val = [] + for l, perp in zip(lengths, all_perps): + if l >= i: + _val.append(perp) + idx.append(i) + val.append(sum(_val) / len(_val) if _val else 0) + + # Store filtered perplexities for JSON output + filtered_perplexities = { + "token_thresholds": idx, + "avg_perplexities": val + } + + plt.figure(figsize=(12, 6)) + + # Plot candidate samples + plt.scatter(idx, val, alpha=0.6, color="blue", label="Candidate samples") + + # Plot reference samples if available + if args.reference_perplexity and reference_samples is not None: + reference_lengths = [len(gpt2_tokenizer.encode(s, add_special_tokens=False)) for s in reference_samples] + ref_idx = [] + ref_val = [] + for i in range(0, 1024): + _ref_val = [] + for l, perp in zip(reference_lengths, reference_perps): + if l >= i: + _ref_val.append(perp) + ref_idx.append(i) + ref_val.append(sum(_ref_val) / len(_ref_val) if _ref_val else 0) + + # Store reference filtered perplexities for JSON output + reference_filtered_perplexities = { + "token_thresholds": ref_idx, + "avg_perplexities": ref_val + } + + plt.scatter(ref_idx, ref_val, alpha=0.6, color="red", label="Reference samples") + + # Add horizontal lines for specific token lengths + for tlen in [10, 20, 30, 40, 50, 75, 100]: + if tlen < len(val): + plt.axhline(y=val[tlen], linestyle='--', color='blue', alpha=0.3) + + plt.title("Perplexity vs. Tokenized Length") + plt.xlabel("Number of tokens") + plt.ylabel("Log Perplexity") + plt.legend() + ax = plt.gca() + ticks = list(ax.get_yticks()) + for tlen in [10, 20, 30, 40, 50, 75, 100]: + if tlen < len(val): + tick_value = val[tlen] + if tick_value not in ticks: + ticks.append(tick_value) + ax.set_yticks(sorted(ticks)) + import matplotlib.ticker as ticker + ax.yaxis.set_major_formatter(ticker.FormatStrFormatter('%.2f')) + plt.yscale("log") + plt.grid(True, linestyle='--', alpha=0.7) + plt.tight_layout() + plt.savefig(args.perplexity_plot_output) + print(f"Saved perplexity vs. length scatter plot to {args.perplexity_plot_output}") + elif args.eval_mode == "sentence": + print("Perplexity plot skipped because the --perplexity flag was not provided") + + if args.results_output: + results = { + "avg_entropy": avg_entropy, + "avg_perplexity": avg_perp, + "avg_reference_perplexity": avg_reference_perp, + "mauve_score": mauve_score, + "filtered_perplexities": filtered_perplexities, + "reference_filtered_perplexities": reference_filtered_perplexities + } + with open(args.results_output, "w", encoding="utf-8") as outf: + json.dump(results, outf, indent=2) + print(f"Saved metrics to {args.results_output}") + + # plot cumulative distribution of GPT2‐tokenized sentence lengths + # Create cumulative distribution + sorted_lengths = sorted(lengths) + cumulative_percentages = [i / len(sorted_lengths) * 100 for i in range(1, len(sorted_lengths) + 1)] + + # Save length data to JSON file + length_data = { + "lengths": lengths, + "sorted_lengths": sorted_lengths, + "cumulative_percentages": cumulative_percentages, + "num_samples": len(samples) + } + base, ext = os.path.splitext(args.length_plot_output) + length_data_output = f"{base}.json" + with open(length_data_output, "w", encoding="utf-8") as f: + json.dump(length_data, f, indent=2) + print(f"Saved length distribution data to {length_data_output}") + + plt.figure() + plt.plot(sorted_lengths, cumulative_percentages, color="skyblue", linewidth=2) + plt.title("Tokenized Sentence Length Cumulative Distribution") + plt.xlabel("Number of tokens") + plt.ylabel("Cumulative percentage (%)") + plt.grid(True, alpha=0.3) + plt.tight_layout() + plt.savefig(args.length_plot_output) + + if args.eval_mode == "chunk": + print(f"Evaluated in chunk mode over {len(target_samples)} segments (using pre-tokenized input)") + else: + print(f"Evaluated in sentence mode over {len(target_samples)} samples") + if args.perplexity: + print(f"Saved perplexity vs. length scatter plot to {args.perplexity_plot_output}") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/FlexMDM/generate_samples.py b/FlexMDM/generate_samples.py new file mode 100644 index 0000000000000000000000000000000000000000..4a9a0da18941d03498e769aa002c10c87b49e2bc --- /dev/null +++ b/FlexMDM/generate_samples.py @@ -0,0 +1,147 @@ +from lightning_modules import AnyOrderInsertionFlowModule, MaskedDiffusionModule +from sampling import ( + any_order_mask_insertion_euler_sampling, + mdm_euler_sampling, + any_order_mask_insertion_tau_leaping_sampling, + mdm_tau_leaping_sampling, +) +from schedule import GeometricSchedule, LinearSchedule +from data.text import setup_tokeniser +import torch +import torch.nn as nn +import argparse +import json + + +torch.set_float32_matmul_precision("high") +torch.set_printoptions(threshold=10_000) + +# Add argparse and remove hard-coded checkpoint_path +parser = argparse.ArgumentParser(description="Generate samples") +parser.add_argument( + "--checkpoint_path", + type=str, + required=True, + help="Path to the model checkpoint file", +) +# Add argparse arguments +parser.add_argument( + "--total_samples", + "-n", + type=int, + default=1024, + help="Total number of samples to generate", +) +parser.add_argument( + "--model_type", + choices=["flow", "mdm"], + default="flow", + help="Model type to use: 'flow' or 'mdm'", +) +parser.add_argument( + "--output_file", + "-o", + type=str, + default="generated_samples.json", + help="Path to save generated samples JSON", +) +parser.add_argument( + "--batch_size", "-b", type=int, help="Batch size; defaults to #GPUs or 1" +) +parser.add_argument( + "--step_size", + type=int, + default=2048, + help="Number of sampling steps", +) +parser.add_argument( + "--sampler_type", + type=str, + default="euler", + choices=["euler", "tau-leaping"], +) +args = parser.parse_args() +checkpoint_path = args.checkpoint_path +output_file = args.output_file + +# Load chosen model +if args.model_type == "flow": + model = AnyOrderInsertionFlowModule.load_from_checkpoint(checkpoint_path) +elif args.model_type == "mdm": + model = MaskedDiffusionModule.load_from_checkpoint(checkpoint_path) +else: + raise ValueError(f"Unknown model_type: {args.model_type}") +model.swap_to_ema() +model.eval() + +# distribute model across GPUs +device = torch.device("cuda" if torch.cuda.is_available() else "cpu") +model.to(device) +if torch.cuda.device_count() > 1: + print(f"Using {torch.cuda.device_count()} GPUs") + model = nn.DataParallel(model) + +tokeniser = setup_tokeniser("gpt2") + +# determine processing batch size +per_gpu_batch_size = 32 +if args.batch_size: + processing_batch_size = args.batch_size +elif torch.cuda.is_available(): + processing_batch_size = per_gpu_batch_size * torch.cuda.device_count() +else: + processing_batch_size = per_gpu_batch_size + +step_size = args.step_size +total_samples = args.total_samples + +all_samples = [] +all_raw_samples = [] + +sample_fn = None + +match (args.model_type, args.sampler_type): + case ("flow", "euler"): + sample_fn = any_order_mask_insertion_euler_sampling + case ("mdm", "euler"): + sample_fn = mdm_euler_sampling + case ("flow", "tau-leaping"): + sample_fn = any_order_mask_insertion_tau_leaping_sampling + case ("mdm", "tau-leaping"): + sample_fn = mdm_tau_leaping_sampling + + +for _ in range(0, total_samples, processing_batch_size): + samples, _ = sample_fn( + model, + steps=step_size, + mask=model.interpolant.mask_token, + pad=model.interpolant.pad_token, + batch_size=processing_batch_size, + max_length=model.interpolant.max_length, + return_trace=False, + ) + if args.model_type == "flow": + all_raw_samples.extend(samples.cpu().tolist()) + else: + all_raw_samples.extend(samples.cpu().tolist()) + # post-process mdm samples: replace tokens after first pad with pad + pad_id = model.interpolant.pad_token + B, L = samples.shape + pos = torch.arange(L, device=samples.device).unsqueeze(0).expand(B, L) + pad_mask = samples == pad_id + idxs = torch.where(pad_mask, pos, torch.full_like(pos, L)) + first_idx = idxs.min(dim=1).values + mask_after = pos > first_idx.unsqueeze(1) + samples[mask_after] = pad_id + + # store raw token IDs + # Decode and strip samples + decoded_samples = tokeniser.batch_decode(samples, skip_special_tokens=True) + all_samples.extend(decoded_samples) + +with open(output_file, "w") as f: + json.dump(all_samples, f, indent=2) +# also write raw token outputs +with open(output_file.replace(".json", "_raw.json"), "w") as f: + json.dump(all_raw_samples, f, indent=2) diff --git a/FlexMDM/interpolant.py b/FlexMDM/interpolant.py new file mode 100644 index 0000000000000000000000000000000000000000..c83b27dcfa267cc6eb05f6322de16145d67d224e --- /dev/null +++ b/FlexMDM/interpolant.py @@ -0,0 +1,312 @@ +import abc +from typing import Optional +import torch +from torch import Tensor +from dataclasses import dataclass +from schedule import Schedule +import torch.nn.functional as F + + +@dataclass +class ModelPrediction: + token_logits: Tensor + length_posterior: Optional[Tensor] + expected_gaps: Tensor + + def __init__( + self, + token_logits: Tensor, + length_posterior: Optional[Tensor] = None, + expected_gaps: Optional[Tensor] = None, + ): + assert length_posterior is not None or expected_gaps is not None + self.token_logits = token_logits + self.length_posterior = length_posterior + self.expected_gaps = expected_gaps + if self.expected_gaps is None: + _, _, L = self.length_posterior.shape + index = torch.arange(0, L, device=token_logits.device).view(1, 1, -1) + self.expected_gaps = (F.softmax(self.length_posterior, dim=-1) * index).sum(dim=-1) + + +@dataclass +class Rate: + unmask_rate: Tensor # Shape [Batch, Length, Vocab] + length_rate: Tensor # Shape [Batch] + + +@dataclass +class HittingTime: + insertion_time: Tensor # Shape [Batch, Length] + unmasking_time: Tensor # Shape [Batch, Length] + + def __iter__(self): + yield from [self.insertion_time, self.unmasking_time] + + +@dataclass +class JointInterpolantResult: + # Joint Interpolant + xt: Tensor # Shape [Batch, Length] + st: Tensor # Shape [Batch, Length] + _x1: Tensor + _pad_token: int + _mask_token: int + + @property + def mask_indices(self) -> Tensor: + return self.xt == self._mask_token + + @property + def unmasked(self) -> Tensor: + return torch.gather(self._x1, 1, self.st) + + @property + def xt_length(self) -> Tensor: + # Calculate length of xt + return (self.xt != self._pad_token).sum(dim=1) + + @property + def x1_length(self) -> Tensor: + # Calculate length of x1 + return (self._x1 != self._pad_token).sum(dim=1) + + @property + def gaps_and_mask(self) -> tuple[Tensor, Tensor]: + x1_len = self.x1_length + gaps = self.st.clone() + + pad_front = gaps.new_zeros((gaps.shape[0], 1)) - 1 # -1 for the front padding + pad_back = gaps.new_zeros((gaps.shape[0], 1)) + gaps = torch.cat([pad_front, gaps, pad_back], dim=1) # Add a leading zero + + gaps.scatter_( + 1, self.xt_length.unsqueeze(1) + 1, x1_len.unsqueeze(1) + ) # Fill the last position with x1_len + + gaps = gaps[:, 1:] - gaps[:, :-1] - 1 + gaps = torch.clamp(gaps, min=0) + + idx = torch.arange(gaps.size(1), device=self.xt.device).unsqueeze( + 0 + ) # shape [1, max_gap] + mask = idx <= self.xt_length.unsqueeze(1) + gaps[~mask] = 0 + + return gaps, mask + + +class JointInterpolant(abc.ABC): + def __init__( + self, + vocab_size: int, + mask_token: int, + pad_token: int, + max_length: int, + ): + """ + TODO: Add knobs + """ + self.mask_token = mask_token + self.pad_token = pad_token + self.max_length = max_length + self.vocab_size = vocab_size + + @abc.abstractmethod + def elbo_weight(self, t: Tensor, x1: Tensor): + """ + Return the ELBO weight for the training, can be changed depends on the empirical results + Shape: + t: [B] + Returns: + weight_unmask: [B, L] + weight_delete: [B, L+1] + """ + raise NotImplementedError + + @abc.abstractmethod + def to_actual_rate(self, prediction: ModelPrediction, t: Tensor) -> Rate: + raise NotImplementedError + + @abc.abstractmethod + def sample_interpolant(self, t: Tensor, x1: Tensor) -> JointInterpolantResult: + """ + Sample the interpolant xt from x1 at time t + Shapes: + x1: [B, L] + t: [B] + Returns: + xt: [B, L] + st: [B, L] boolean mask of positions that corresponds to xt + xt_mask_indices: [B, L] boolean mask of positions that are masked at xt + x1_remained: [B, L] tokens that are not deleted, used for the training target + gap_counts: [B, L+1] the number of deleted tokens between xt slots + """ + raise NotImplementedError + + +class AnyOrderMaskInsertionInterpolant(JointInterpolant): + def __init__( + self, + insertion_schedule: Schedule, + unmask_schedule: Schedule, + vocab_size: int, + mask_token: int, + pad_token: int, + max_length: int, + ): + super().__init__(vocab_size, mask_token, pad_token, max_length) + self.insertion_schedule = insertion_schedule + self.unmask_schedule = unmask_schedule + + def hitting_time(self, t: Tensor, x1: Tensor) -> tuple[Tensor, Tensor]: + """ + t1 is sampled from a uniform distribution over [0, 1]. when t1 < self.mask_schedule.at(t) + t2 is sampled from a uniform distribution over [t1, 1] + """ + B, L = x1.shape + eps = 1e-6 + + insert_time = self.insertion_schedule.sample((B, L), device=x1.device) + insert_time = eps + (1 - eps) * insert_time # ensure t1 is not 0 + unmask_time = self.unmask_schedule.sample_truncated( + insert_time, (B, L), device=x1.device + ) + + return insert_time, unmask_time + + def elbo_weight(self, t: Tensor, x1: Tensor): + """ + Return the ELBO weight for the training, can be changed depends on the empirical results + """ + insert_weight = self.insertion_schedule.rate_scale_factor(t) + insert_weight = insert_weight[:, None].expand(-1, x1.shape[1] + 1) + + unmask_weight = self.unmask_schedule.rate_scale_factor(t) + unmask_weight = unmask_weight.unsqueeze(1).expand(-1, x1.shape[1]) + + return unmask_weight, insert_weight + + def to_actual_rate( + self, xt: Tensor, prediction: ModelPrediction, t: Tensor + ) -> Rate: + """ + Return the actual rate for the sampling + Args: + xt: [B, L] the sampled tokens + prediction: ModelPrediction object containing token_posterior and expected_gaps + t: [B] the time parameter + """ + token_posterior = F.softmax(prediction.token_logits, dim=-1) # (B, L, V) + unmask_rate = token_posterior * self.unmask_schedule.rate_scale_factor(t).view( + -1, 1, 1 + ) + length_rate = ( + prediction.expected_gaps + * self.insertion_schedule.rate_scale_factor(t).view(-1, 1) + ) + + return Rate( + unmask_rate=unmask_rate, # (B, L, V) + length_rate=length_rate, # (B, L+1) + ) + + def sample_interpolant(self, t: Tensor, x1: Tensor) -> JointInterpolantResult: + """ + Shapes: + x1: [B, L] + t: [B] + Returns: + xt: [B, L] + st: [B, L] boolean mask of positions that corresponds to xt + xt_mask_indices: [B, L] boolean mask of positions that are masked at xt + x1_remained: [B, L] tokens that are not deleted, used for the training target + gap_counts: [B, L+1] the number of deleted tokens between xt slots + """ + # sample the stopping time (B, L, 2) + insertion_time, unmasking_time = self.hitting_time(t, x1) + + clean_tokens = x1.ne(self.pad_token) + deleted_tokens = clean_tokens & (t[:, None] < insertion_time) + masked_tokens = ( + clean_tokens + & (t[:, None] >= insertion_time) + & (t[:, None] < unmasking_time) + ) + + xt = torch.where( + deleted_tokens, + self.pad_token, # for deletion, change to pad token + torch.where( + masked_tokens, + self.mask_token, # for masking, change to mask token + x1, + ), + ) + + st = xt.ne(self.pad_token).argsort(dim=1, descending=True, stable=True) + xt = torch.gather(xt, 1, st) + st[xt == self.pad_token] = 0 + + return JointInterpolantResult( + xt=xt, st=st, _x1=x1, _pad_token=self.pad_token, _mask_token=self.mask_token + ) + + +class MDMInterpolant(JointInterpolant): + def __init__( + self, + unmask_schedule: Schedule, + vocab_size: int, + mask_token: int, + pad_token: int, + max_length: int, + ): + super().__init__(vocab_size, mask_token, pad_token, max_length) + self.unmask_schedule = unmask_schedule + + def elbo_weight(self, t: Tensor, x1: Tensor): + """ + Return the ELBO weight for the training, can be changed depends on the empirical results + there's no weight_delete for the vanilla MDM + """ + weight_unmask = self.unmask_schedule.rate_scale_factor(t) + weight_unmask_expanded = weight_unmask.unsqueeze(1).expand( + -1, x1.shape[1] + ) # (B,L) + return weight_unmask_expanded + + def to_actual_rate(self, xt: Tensor, prediction: Tensor, t: Tensor) -> Rate: + """ + Return the actual rate for the sampling + """ + token_posterior = F.softmax(prediction, dim=-1) # (B, L, V) + unmask_rate = token_posterior * self.unmask_schedule.rate_scale_factor(t).view( + -1, 1, 1 + ) + + return Rate( + unmask_rate=unmask_rate, # (B, L, V) + length_rate=None, # (B, L+1) + ) + + def sample_interpolant(self, t: Tensor, x1: Tensor) -> JointInterpolantResult: + # sample the stopping time (B, L, 2) + eps = 1e-6 + unmask_time = self.unmask_schedule.sample( + (x1.shape[0], x1.shape[1]), device=x1.device + ) + unmask_time = unmask_time * (1 - eps) + eps + + xt = torch.where( + t[:, None] < unmask_time, + self.mask_token, # for masking, change to mask token + x1, + ) + st = torch.arange(xt.shape[1], device=xt.device, dtype=torch.long).repeat( + xt.shape[0], 1 + ) + + return JointInterpolantResult( + xt=xt, st=st, _x1=x1, _pad_token=self.pad_token, _mask_token=self.mask_token + ) diff --git a/FlexMDM/lightning_modules/__init__.py b/FlexMDM/lightning_modules/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..c5f316f96eb32a02cc178997a4a21609c67105a4 --- /dev/null +++ b/FlexMDM/lightning_modules/__init__.py @@ -0,0 +1,10 @@ +from .mdm import MaskedDiffusionModule +from .autoregressive import AutoregressiveModule +from .any_order import AnyOrderInsertionFlowModule + + +__all__ = [ + "MaskedDiffusionModule", + "AutoregressiveModule", + "AnyOrderInsertionFlowModule", +] diff --git a/FlexMDM/lightning_modules/any_order.py b/FlexMDM/lightning_modules/any_order.py new file mode 100644 index 0000000000000000000000000000000000000000..6a29772671f4538055b090c79f0baac0d481ae11 --- /dev/null +++ b/FlexMDM/lightning_modules/any_order.py @@ -0,0 +1,255 @@ +import torch +import pytorch_lightning as pl +from omegaconf import DictConfig +import torch.nn.functional as F +from model.transformer import AnyOrderMaskInsertionFlow +from interpolant import AnyOrderMaskInsertionInterpolant, ModelPrediction +from bregman import jump_kernel_elbo, mse +from schedule import get_schedule_from_config + + +import re +from typing import Dict, Any + + +def strip_orig_mod_keys(state_dict: Dict[str, Any]) -> Dict[str, Any]: + """ + Returns a new state_dict where any key containing '._orig_mod.' is replaced + by removing the '_orig_mod' segment, e.g. + 'model._orig_mod.vocab_embed.embedding' + becomes + 'model.vocab_embed.embedding' + """ + new_state_dict: Dict[str, Any] = {} + for key, value in state_dict.items(): + # remove all occurrences of '._orig_mod.' + clean_key = re.sub(r"\._orig_mod\.", ".", key) + new_state_dict[clean_key] = value + return new_state_dict + + +class AnyOrderInsertionFlowModule(pl.LightningModule): + def __init__(self, config: DictConfig): + super().__init__() + self.config = config + self.model_type = config.interpolant.type + self.learning_rate = config.training.learning_rate + self.unmask_loss_fn = config.training.loss_fn.unmask + self.insert_loss_fn = config.training.loss_fn.insert + + # Initialize model based on type + self.model = AnyOrderMaskInsertionFlow(config) + self.model = torch.compile(self.model) + + insert_schedule = get_schedule_from_config(config.interpolant.insert_schedule) + unmask_schedule = get_schedule_from_config(config.interpolant.unmask_schedule) + + # Initialize interpolant + self.interpolant = AnyOrderMaskInsertionInterpolant( + insertion_schedule=insert_schedule, + unmask_schedule=unmask_schedule, + vocab_size=config.interpolant.tokens, + mask_token=config.interpolant.mask_token, + pad_token=config.interpolant.pad_token, + max_length=config.interpolant.max_length, + ) + + # Save hyperparameters + self.save_hyperparameters() + + self.ema_decay = config.training.ema_decay or 0.0 + self.use_ema = self.ema_decay > 0 + self._orig_params = {} + + def forward(self, x, t) -> ModelPrediction: + if self.config.training.only_embed_insert: + return self.model(x, self.interpolant.insertion_schedule.at(t)) + else: + return self.model(x, t) + + def training_loss(self, x1, t): + interpolant_sample = self.interpolant.sample_interpolant(t, x1) + unmask_weight, insert_weight = self.interpolant.elbo_weight(t, x1) + + prediction: ModelPrediction = self(interpolant_sample.xt, t) + + scale_factor = x1.shape[0] * self.config.interpolant.max_length + + match self.unmask_loss_fn: + case "elbo": + mask_indices = interpolant_sample.mask_indices + unmask_loss = unmask_weight[mask_indices] * F.cross_entropy( + prediction.token_logits[mask_indices], + interpolant_sample.unmasked[mask_indices], + reduction="none", + ) + unmask_loss = unmask_loss.sum() / scale_factor + case _: + raise ValueError(f"Invalid unmask loss type: {self.unmask_loss_fn}") + + match self.insert_loss_fn: + case "expectation": + gaps, gaps_mask = interpolant_sample.gaps_and_mask + insertion_loss = insert_weight[gaps_mask] * jump_kernel_elbo( + gaps[gaps_mask], prediction.expected_gaps[gaps_mask] + ) + insertion_loss = insertion_loss.sum() / scale_factor + + case "distribution": + gaps, gaps_mask = interpolant_sample.gaps_and_mask + insertion_loss = insert_weight[gaps_mask] * F.cross_entropy( + prediction.length_posterior[gaps_mask], gaps[gaps_mask] + ) + insertion_loss = insertion_loss.sum() / scale_factor + + total_loss = unmask_loss + insertion_loss + return unmask_loss, insertion_loss, total_loss + + def sample_time(self, batch_size: int, device: torch.device) -> torch.Tensor: + eps = 1e-6 + interval = 1.0 - eps + interval_size = interval / batch_size + u = torch.rand(batch_size, device=device) + return (torch.arange(batch_size, device=device, dtype=u.dtype) + u) * interval_size + + def training_step(self, batch, batch_idx): + # Extract input data + if isinstance(batch, dict): + batch = batch["input_ids"] + + x1 = batch + t = self.sample_time(x1.shape[0], x1.device) + + # Calculate the combined loss normally + unmask_loss, len_loss, loss = self.training_loss(x1, t) + + # Log component losses + self.log("train/unmask_loss", unmask_loss, prog_bar=True) + self.log("train/len_loss", len_loss, prog_bar=True) + self.log("train/total_loss", loss, prog_bar=True) + + + return loss + + def validation_step(self, batch, batch_idx): + if isinstance(batch, dict): + batch = batch["input_ids"] + + x1 = batch + t = self.sample_time(x1.shape[0], x1.device) + unmask_loss, len_loss, loss = self.training_loss(x1, t) + + self.log("val/unmask_loss", unmask_loss, prog_bar=True, sync_dist=True) + self.log("val/len_loss", len_loss, prog_bar=True, sync_dist=True) + self.log("val_loss", loss, prog_bar=True, sync_dist=True) + + return loss + + def configure_optimizers(self): + optimizer = torch.optim.AdamW( + self.parameters(), + lr=self.learning_rate, + weight_decay=self.config.training.weight_decay, + ) + + warmup_steps = self.config.training.warmup_steps + max_steps = self.config.training.max_steps + + # Always create a fresh schedule starting from step 0 + # This allows extending training beyond original max_steps + linear_scheduler = torch.optim.lr_scheduler.LinearLR( + optimizer, + start_factor=1e-6, + end_factor=1.0, + total_iters=warmup_steps, + last_epoch=-1, + ) + post_warmup = max_steps - warmup_steps + cosine_scheduler = torch.optim.lr_scheduler.CosineAnnealingLR( + optimizer, + T_max=post_warmup, + eta_min=0.0, + last_epoch=-1, + ) + + scheduler = torch.optim.lr_scheduler.SequentialLR( + optimizer, + schedulers=[linear_scheduler, cosine_scheduler], + milestones=[warmup_steps], + last_epoch=-1, + ) + return [optimizer], [{"scheduler": scheduler, "interval": "step"}] + + def optimizer_step( + self, + epoch: int, + batch_idx: int, + optimizer, + optimizer_closure=None, + ): + super().optimizer_step( + epoch, batch_idx, optimizer, optimizer_closure=optimizer_closure + ) + # log learning rate and gradient norm + lr = optimizer.param_groups[0]["lr"] + self.log("train/lr", lr, on_step=True, prog_bar=True) + grad_norm = torch.sqrt( + sum(p.grad.norm(2) ** 2 for p in self.parameters() if p.grad is not None) + ) + self.log("train/grad_norm", grad_norm, on_step=True, prog_bar=True) + + # update EMA + if self.use_ema: + for n, p in self.named_parameters(): + self.ema_params[n].mul_(self.ema_decay).add_( + p.data.clone().detach(), alpha=1 - self.ema_decay + ) + + def on_save_checkpoint(self, checkpoint): + checkpoint["config"] = self.config + # save EMA state + if self.use_ema: + checkpoint["ema_params"] = { + n: v.clone() for n, v in self.ema_params.items() + } + + def on_load_checkpoint(self, checkpoint): + self.config = checkpoint["config"] + + insert_schedule = get_schedule_from_config( + self.config.interpolant.insert_schedule + ) + unmask_schedule = get_schedule_from_config( + self.config.interpolant.unmask_schedule + ) + + self.interpolant = AnyOrderMaskInsertionInterpolant( + insertion_schedule=insert_schedule, + unmask_schedule=unmask_schedule, + vocab_size=self.config.interpolant.tokens, + mask_token=self.config.interpolant.mask_token, + pad_token=self.config.interpolant.pad_token, + max_length=self.config.interpolant.max_length, + ) + + self.ema_params = checkpoint["ema_params"] if self.use_ema else {} + + def swap_to_ema(self): + for name, p in self.named_parameters(): + self._orig_params[name] = p.data.clone() + p.data.copy_(self.ema_params[name].to(p.device)) + + def restore_original(self): + for name, p in self.named_parameters(): + p.data.copy_(self._orig_params[name]) + self._orig_params.clear() + + def on_train_start(self): + # initialize and move EMA buffers once model is on correct device + if self.use_ema: + self.ema_params = { + name: param.clone().detach().to(self.device) + for name, param in self.named_parameters() + } + for buf in self.ema_params.values(): + buf.requires_grad = False \ No newline at end of file diff --git a/FlexMDM/lightning_modules/autoregressive.py b/FlexMDM/lightning_modules/autoregressive.py new file mode 100644 index 0000000000000000000000000000000000000000..709735e9e0beed1677b7c0a6f8ead5f0575ccd43 --- /dev/null +++ b/FlexMDM/lightning_modules/autoregressive.py @@ -0,0 +1,61 @@ +import torch +import pytorch_lightning as pl +import torch.nn.functional as F +from model.casual_transformer import CausalDiT + + +class AutoregressiveModule(pl.LightningModule): + def __init__(self, config): + super().__init__() + self.config = config + self.learning_rate = config.training.learning_rate + + # Initialize model (causal transformer) + self.model = CausalDiT(config) + + def forward(self, x): + return self.model(x) + + def training_loss(self, x1): + # next token prediction loss + input_ids = x1[:, :-1] + logits = self.model(input_ids) + target_ids = x1[:, 1:] + loss = F.cross_entropy( + logits.reshape(-1, logits.shape[-1]), + target_ids.reshape(-1), + ignore_index=self.config.interpolant.pad_token, + ) + return loss + + def training_step(self, batch, batch_idx): + # Extract input data + if isinstance(batch, dict): + batch = batch["input_ids"] + + x1 = batch + loss = self.training_loss(x1) + + self.log("train/total_loss", loss, prog_bar=True) + + return loss + + def validation_step(self, batch, batch_idx): + if isinstance(batch, dict): + batch = batch["input_ids"] + + x1 = batch + loss = self.training_loss(x1) + + self.log("val_loss", loss, prog_bar=True) + + return loss + + def configure_optimizers(self): + return torch.optim.AdamW(self.parameters(), lr=self.learning_rate) + + def on_save_checkpoint(self, checkpoint): + checkpoint["config"] = self.config + + def on_load_checkpoint(self, checkpoint): + self.config = checkpoint["config"] diff --git a/FlexMDM/lightning_modules/mdm.py b/FlexMDM/lightning_modules/mdm.py new file mode 100644 index 0000000000000000000000000000000000000000..c493d7444c1c9317598f9ac380b126bbd47dda0d --- /dev/null +++ b/FlexMDM/lightning_modules/mdm.py @@ -0,0 +1,184 @@ +import torch +import pytorch_lightning as pl +import torch.nn.functional as F +from model.MDM_transformer import DDiTNoLengthModel +from interpolant import MDMInterpolant # replaced relative import +from schedule import get_schedule_from_config + + +class MaskedDiffusionModule(pl.LightningModule): + def __init__(self, config): + super().__init__() + self.config = config + self.learning_rate = config.training.learning_rate + + # Initialize model (no length head) + self.model = DDiTNoLengthModel(config) + self.model = torch.compile(self.model) + + unmask_schedule = get_schedule_from_config(config.interpolant.unmask_schedule) + + # Initialize interpolant + self.interpolant = MDMInterpolant( + unmask_schedule=unmask_schedule, + vocab_size=config.interpolant.tokens, + mask_token=config.interpolant.mask_token, + pad_token=config.interpolant.pad_token, + max_length=config.interpolant.max_length, + ) + + # Save hyperparameters + self.save_hyperparameters() + + self.ema_decay = config.training.ema_decay or 0.0 + self.use_ema = self.ema_decay > 0 + self._orig_params = {} + + def forward(self, x, t) -> torch.Tensor: + return self.model(x, t) + + def training_loss(self, x1, t): + # sample interpolant and elbo weight + + interpolant_result = self.interpolant.sample_interpolant(t, x1) + unmask_weight = self.interpolant.elbo_weight(t, x1) + + # model prediction + predicted_logits = self(interpolant_result.xt, t) + mask_indices = interpolant_result.mask_indices + + # compute unmask loss + loss = unmask_weight[mask_indices] * F.cross_entropy( + predicted_logits[mask_indices], + interpolant_result.unmasked[mask_indices], + reduction="none", + ) + + loss = loss.sum() / (x1.shape[0] * self.config.interpolant.max_length) + return loss + + def training_step(self, batch, batch_idx): + # Extract input data + if isinstance(batch, dict): + batch = batch["input_ids"] + + x1 = batch + batch_size = x1.shape[0] + t = torch.rand(batch_size, device=x1.device) + loss = self.training_loss(x1, t) + + self.log("train/total_loss", loss, prog_bar=True) + + return loss + + def validation_step(self, batch, batch_idx): + if isinstance(batch, dict): + batch = batch["input_ids"] + + x1 = batch + batch_size = x1.shape[0] + + t = torch.rand(batch_size, device=x1.device) + loss = self.training_loss(x1, t) + + self.log("val_loss", loss, prog_bar=True) + + return loss + + def configure_optimizers(self): + optimizer = torch.optim.AdamW( + self.parameters(), + lr=self.learning_rate, + weight_decay=self.config.training.weight_decay, + ) + warmup_steps = self.config.training.warmup_steps + max_steps = self.config.training.max_steps + + linear_scheduler = torch.optim.lr_scheduler.LinearLR( + optimizer, + start_factor=1e-6, + end_factor=1.0, + total_iters=warmup_steps, + ) + post_warmup = max_steps - warmup_steps + cosine_scheduler = torch.optim.lr_scheduler.CosineAnnealingWarmRestarts( + optimizer, + T_0=post_warmup // 10, + T_mult=1, + eta_min=0.0, + ) + + scheduler = torch.optim.lr_scheduler.SequentialLR( + optimizer, + schedulers=[linear_scheduler, cosine_scheduler], + milestones=[warmup_steps], + ) + return [optimizer], [{"scheduler": scheduler, "interval": "step"}] + + def optimizer_step( + self, + epoch: int, + batch_idx: int, + optimizer, + optimizer_closure=None, + ): + super().optimizer_step( + epoch, batch_idx, optimizer, optimizer_closure=optimizer_closure + ) + # log learning rate and gradient norm + lr = optimizer.param_groups[0]["lr"] + self.log("train/lr", lr, on_step=True, prog_bar=True) + grad_norm = torch.sqrt( + sum(p.grad.norm(2) ** 2 for p in self.parameters() if p.grad is not None) + ) + self.log("train/grad_norm", grad_norm, on_step=True, prog_bar=True) + + # update EMA + if self.use_ema: + for n, p in self.named_parameters(): + self.ema_params[n].mul_(self.ema_decay).add_( + p.data.clone().detach(), alpha=1 - self.ema_decay + ) + + def on_save_checkpoint(self, checkpoint): + checkpoint["config"] = self.config + # save EMA state + if self.use_ema: + checkpoint["ema_params"] = {n: v.cpu() for n, v in self.ema_params.items()} + + def on_load_checkpoint(self, checkpoint): + self.config = checkpoint["config"] + + unmask_schedule = get_schedule_from_config( + self.config.interpolant.unmask_schedule + ) + + self.interpolant = MDMInterpolant( + unmask_schedule=unmask_schedule, + vocab_size=self.config.interpolant.tokens, + mask_token=self.config.interpolant.mask_token, + pad_token=self.config.interpolant.pad_token, + max_length=self.config.interpolant.max_length, + ) + + self.ema_params = checkpoint["ema_params"] if self.use_ema else {} + + def swap_to_ema(self): + for name, p in self.named_parameters(): + self._orig_params[name] = p.data.clone() + p.data.copy_(self.ema_params[name].to(p.device)) + + def restore_original(self): + for name, p in self.named_parameters(): + p.data.copy_(self._orig_params[name]) + self._orig_params.clear() + + def on_train_start(self): + # initialize and move EMA buffers once model is on correct device + if self.use_ema: + self.ema_params = { + name: param.clone().detach().to(self.device) + for name, param in self.named_parameters() + } + for buf in self.ema_params.values(): + buf.requires_grad = False diff --git a/FlexMDM/metrics.py b/FlexMDM/metrics.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/FlexMDM/model/MDM_transformer.py b/FlexMDM/model/MDM_transformer.py new file mode 100644 index 0000000000000000000000000000000000000000..1728fb66bea910df6c3e00b85329249271e3d4ba --- /dev/null +++ b/FlexMDM/model/MDM_transformer.py @@ -0,0 +1,75 @@ +import torch +import torch.nn.functional as F +from . import rotary +from .transformer import EmbeddingLayer, TimestepEmbedder, DDiTBlock, DDitFinalLayer +from omegaconf import OmegaConf +from torch.nn.attention.flex_attention import create_block_mask + + +def _dense_mask(b, h, q_idx, kv_idx): + return torch.full_like(q_idx, True, dtype=torch.bool) + + +class DDiTNoLengthModel(torch.nn.Module): + """ + A DDiT‐style model that predicts only per‐token posteriors, + without any sequence‐length head, opt for the vanilla MDM + """ + + def __init__(self, config): + super().__init__() + # allowing dict configs too + if isinstance(config, dict): + config = OmegaConf.create(config) + + self.config = config + self.vocab_size = config.interpolant.tokens + self.pad_token = config.interpolant.pad_token + self.mask_token = config.interpolant.mask_token + + self.vocab_embed = EmbeddingLayer(config.model.hidden_size, self.vocab_size) + self.sigma_map = TimestepEmbedder(config.model.cond_dim) + self.rotary_emb = rotary.Rotary( + config.model.hidden_size // config.model.n_heads + ) + + self.blocks = torch.nn.ModuleList( + [ + DDiTBlock( + config.model.hidden_size, + config.model.n_heads, + config.model.cond_dim, + dropout=config.model.dropout, + ) + for _ in range(config.model.n_blocks) + ] + ) + # final per‐token head only / no length head + self.output_layer = DDitFinalLayer( + config.model.hidden_size, self.vocab_size, config.model.cond_dim + ) + + def forward(self, indices: torch.Tensor, t: torch.Tensor): + """ + indices: (B, L) token indices + t: (B,) timestep scalars + returns: ReparametrizedRate with only per_token_posterior set + """ + B, L = indices.shape + + block_mask = create_block_mask( + _dense_mask, B=B, H=None, Q_LEN=indices.shape[1], KV_LEN=indices.shape[1] + ) + print(block_mask) + + x = self.vocab_embed(indices) # (B, L, hidden) + c = F.silu(self.sigma_map(t)) # (B, cond_dim) + rotary_cos_sin = self.rotary_emb(x) # precompute rotary embeddings + + # run the stack + with torch.amp.autocast("cuda", dtype=torch.bfloat16): + for i in range(len(self.blocks)): + x = self.blocks[i](x, rotary_cos_sin, c, block_mask) + + token_logits = self.output_layer(x, c) + return token_logits diff --git a/FlexMDM/model/__init__.py b/FlexMDM/model/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/FlexMDM/model/casual_transformer.py b/FlexMDM/model/casual_transformer.py new file mode 100644 index 0000000000000000000000000000000000000000..fd6aaba43ad4eb11ee0bd3a3d7c525d99de9bd60 --- /dev/null +++ b/FlexMDM/model/casual_transformer.py @@ -0,0 +1,98 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F +from einops import rearrange +from flash_attn.flash_attn_interface import flash_attn_varlen_qkvpacked_func +from .fused_add_dropout_scale import modulate_fused, bias_dropout_add_scale_fused_train, bias_dropout_add_scale_fused_inference +from .transformer import LayerNorm, EmbeddingLayer +from . import rotary + + +class CausalDiTBlock(nn.Module): + def __init__(self, dim, n_heads, cond_dim, mlp_ratio=4, dropout=0.1): + super().__init__() + self.n_heads = n_heads + self.norm1 = LayerNorm(dim) + self.attn_qkv = nn.Linear(dim, 3 * dim, bias=False) + self.attn_out = nn.Linear(dim, dim, bias=False) + self.dropout1 = nn.Dropout(dropout) + self.norm2 = LayerNorm(dim) + self.mlp = nn.Sequential( + nn.Linear(dim, mlp_ratio * dim, bias=True), + nn.GELU(approximate="tanh"), + nn.Linear(mlp_ratio * dim, dim, bias=True) + ) + self.dropout2 = nn.Dropout(dropout) + self.dropout = dropout + # No time or label conditioning, so no adaLN_modulation + + def _get_bias_dropout_scale(self): + return ( + bias_dropout_add_scale_fused_train + if self.training + else bias_dropout_add_scale_fused_inference + ) + + def forward(self, x, rotary_cos_sin, seqlens=None): + batch_size, seq_len = x.shape[0], x.shape[1] + bias_dropout_scale_fn = self._get_bias_dropout_scale() + + # attention operation + x_skip = x + x = self.norm1(x) + # dtype0 = x.dtype + + qkv = self.attn_qkv(x) + qkv = rearrange(qkv, 'b s (three h d) -> b s three h d', three=3, h=self.n_heads) + with torch.cuda.amp.autocast(enabled=False): + cos, sin = rotary_cos_sin + qkv = rotary.apply_rotary_pos_emb( + qkv, cos.to(qkv.dtype), sin.to(qkv.dtype) + ) + qkv = rearrange(qkv, 'b s ... -> (b s) ...') + if seqlens is None: + cu_seqlens = torch.arange( + 0, (batch_size + 1) * seq_len, step=seq_len, + dtype=torch.int32, device=qkv.device + ) + else: + cu_seqlens = seqlens.cumsum(-1) + x = flash_attn_varlen_qkvpacked_func( + qkv, cu_seqlens, seq_len, 0., causal=True) + x = rearrange(x, '(b s) h d -> b s (h d)', b=batch_size) + + scale = torch.ones(1, device=x.device, dtype=x.dtype) + x = bias_dropout_scale_fn(self.attn_out(x), None, scale, x_skip, self.dropout) + + # mlp operation + x = bias_dropout_scale_fn( + self.mlp(self.norm2(x)), None, scale, x, self.dropout + ) + return x + +class CausalDiT(nn.Module): + def __init__(self, config): + super().__init__() + if isinstance(config, dict): + config = OmegaConf.create(config) + + self.config = config + self.vocab_size = config.interpolant.tokens + self.pad_token = config.interpolant.pad_token + + self.vocab_embed = EmbeddingLayer(config.model.hidden_size, self.vocab_size) + self.rotary_emb = rotary.Rotary(config.model.hidden_size // config.model.n_heads) + self.blocks = nn.ModuleList([ + CausalDiTBlock(config.model.hidden_size, config.model.n_heads, config.model.cond_dim, dropout=config.model.dropout) + for _ in range(config.model.n_blocks) + ]) + self.output_layer = nn.Linear(config.model.hidden_size, self.vocab_size) + + def forward(self, indices): + x = self.vocab_embed(indices) + rotary_cos_sin = self.rotary_emb(x) + with torch.amp.autocast('cuda', dtype=torch.bfloat16): + for block in self.blocks: + x = block(x, rotary_cos_sin, seqlens=None) + logits = self.output_layer(x) + return logits \ No newline at end of file diff --git a/FlexMDM/model/fused_add_dropout_scale.py b/FlexMDM/model/fused_add_dropout_scale.py new file mode 100644 index 0000000000000000000000000000000000000000..56266ada1b469d84225e8db3eb617c05efc06cc8 --- /dev/null +++ b/FlexMDM/model/fused_add_dropout_scale.py @@ -0,0 +1,66 @@ +import torch +import torch.nn.functional as F +from typing import Optional +from torch import Tensor + +# flags required to enable jit fusion kernels +torch._C._jit_set_profiling_mode(False) +torch._C._jit_set_profiling_executor(False) +torch._C._jit_override_can_fuse_on_cpu(True) +torch._C._jit_override_can_fuse_on_gpu(True) + + +def bias_dropout_add_scale( + x: Tensor, + bias: Optional[Tensor], + scale: Tensor, + residual: Optional[Tensor], + prob: float, + training: bool, +) -> Tensor: + if bias is not None: + out = scale * F.dropout(x + bias, p=prob, training=training) + else: + out = scale * F.dropout(x, p=prob, training=training) + + if residual is not None: + out = residual + out + return out + + +def get_bias_dropout_add_scale(training): + def _bias_dropout_add(x, bias, scale, residual, prob): + return bias_dropout_add_scale(x, bias, scale, residual, prob, training) + + return _bias_dropout_add + + +def modulate(x: Tensor, shift: Tensor, scale: Tensor) -> Tensor: + return x * (1 + scale) + shift + + +@torch.jit.script +def bias_dropout_add_scale_fused_train( + x: Tensor, + bias: Optional[Tensor], + scale: Tensor, + residual: Optional[Tensor], + prob: float, +) -> Tensor: + return bias_dropout_add_scale(x, bias, scale, residual, prob, True) + + +@torch.jit.script +def bias_dropout_add_scale_fused_inference( + x: Tensor, + bias: Optional[Tensor], + scale: Tensor, + residual: Optional[Tensor], + prob: float, +) -> Tensor: + return bias_dropout_add_scale(x, bias, scale, residual, prob, False) + + +@torch.jit.script +def modulate_fused(x: Tensor, shift: Tensor, scale: Tensor) -> Tensor: + return modulate(x, shift, scale) diff --git a/FlexMDM/model/rotary.py b/FlexMDM/model/rotary.py new file mode 100644 index 0000000000000000000000000000000000000000..a866fce03393b3b068b0af76484e802bccf19128 --- /dev/null +++ b/FlexMDM/model/rotary.py @@ -0,0 +1,48 @@ +import torch + + +class Rotary(torch.nn.Module): + def __init__(self, dim, base=10_000): + super().__init__() + inv_freq = 1.0 / (base ** (torch.arange(0, dim, 2).float() / dim)) + self.register_buffer("inv_freq", inv_freq) + self.seq_len_cached = None + self.cos_cached = None + self.sin_cached = None + + def forward(self, x, seq_dim=1): + seq_len = x.shape[seq_dim] + if seq_len != self.seq_len_cached: + self.seq_len_cached = seq_len + t = torch.arange(x.shape[seq_dim], device=x.device).type_as(self.inv_freq) + freqs = torch.einsum("i,j->ij", t, self.inv_freq.clone()) + emb = torch.cat((freqs, freqs), dim=-1).to(x.device) + # dims are: batch, seq_len, qkv, head, dim + self.cos_cached = emb.cos()[None, :, None, None, :].repeat(1, 1, 3, 1, 1) + self.sin_cached = emb.sin()[None, :, None, None, :].repeat(1, 1, 3, 1, 1) + # This makes the transformation on v an identity. + self.cos_cached[:, :, 2, :, :].fill_(1.0) + self.sin_cached[:, :, 2, :, :].fill_(0.0) + + return self.cos_cached, self.sin_cached + + +def rotate_half(x): + x1, x2 = x[..., : x.shape[-1] // 2], x[..., x.shape[-1] // 2 :] + return torch.cat((-x2, x1), dim=-1) + + +@torch.jit.script +def _apply_rotary_pos_emb_torchscript(qkv, cos, sin): + return (qkv * cos) + (rotate_half(qkv) * sin) + + +def apply_rotary_pos_emb(qkv, cos, sin): + try: + import flash_attn.layers.rotary + + cos = cos[0, :, 0, 0, : cos.shape[-1] // 2] + sin = sin[0, :, 0, 0, : sin.shape[-1] // 2] + return flash_attn.layers.rotary.apply_rotary_emb_qkv_(qkv, cos, sin) + except ImportError: + return _apply_rotary_pos_emb_torchscript(qkv, cos, sin) diff --git a/FlexMDM/model/transformer.py b/FlexMDM/model/transformer.py new file mode 100644 index 0000000000000000000000000000000000000000..d8204bda98e13f8048e51b87bae413f61291ec63 --- /dev/null +++ b/FlexMDM/model/transformer.py @@ -0,0 +1,352 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F +import math +from einops import rearrange +from omegaconf import OmegaConf +from interpolant import ModelPrediction +from torch.nn.attention.flex_attention import flex_attention, create_block_mask +from . import rotary +from .fused_add_dropout_scale import ( + bias_dropout_add_scale_fused_train, + bias_dropout_add_scale_fused_inference, + modulate_fused, +) + + +flex_attention = torch.compile(flex_attention, mode="max-autotune") + + +def modulate(x, shift, scale): + return x * (1 + scale.unsqueeze(1)) + shift.unsqueeze(1) + + +################################################################################# +# Layers # +################################################################################# +class LayerNorm(nn.Module): + def __init__(self, dim): + super().__init__() + self.weight = nn.Parameter(torch.ones([dim])) + self.dim = dim + + def forward(self, x): + with torch.amp.autocast("cuda", enabled=False): + x = F.layer_norm(x.float(), [self.dim]) + return x * self.weight[None, None, :] + + +################################################################################# +# Embedding Layers for Timesteps and Class Labels # +################################################################################# + + +class TimestepEmbedder(nn.Module): + """ + Embeds scalar timesteps into vector representations. + """ + + def __init__(self, hidden_size, frequency_embedding_size=256, silu=True): + super().__init__() + self.mlp = nn.Sequential( + nn.Linear(frequency_embedding_size, hidden_size, bias=True), + nn.SiLU(), + nn.Linear(hidden_size, hidden_size, bias=True), + ) + self.frequency_embedding_size = frequency_embedding_size + + @staticmethod + def timestep_embedding(t, dim, max_period=10000): + """ + Create sinusoidal timestep embeddings. + :param t: a 1-D Tensor of N indices, one per batch element. + These may be fractional. + :param dim: the dimension of the output. + :param max_period: controls the minimum frequency of the embeddings. + :return: an (N, D) Tensor of positional embeddings. + """ + # https://github.com/openai/glide-text2im/blob/main/glide_text2im/nn.py + half = dim // 2 + freqs = torch.exp( + -math.log(max_period) + * torch.arange(start=0, end=half, dtype=torch.float32) + / half + ).to(device=t.device) + args = t[:, None].float() * freqs[None] + embedding = torch.cat([torch.cos(args), torch.sin(args)], dim=-1) + if dim % 2: + embedding = torch.cat( + [embedding, torch.zeros_like(embedding[:, :1])], dim=-1 + ) + return embedding + + def forward(self, t): + t_freq = self.timestep_embedding(t, self.frequency_embedding_size) + t_emb = self.mlp(t_freq) + return t_emb + + +class LabelEmbedder(nn.Module): + """ + Embeds class labels into vector representations. Also handles label dropout for classifier-free guidance. + """ + + def __init__(self, num_classes, cond_size): + super().__init__() + self.embedding_table = nn.Embedding(num_classes + 1, cond_size) + self.num_classes = num_classes + + # TODO think of initializing with 0.02 std deviation like in original DiT paper + + def forward(self, labels): + embeddings = self.embedding_table(labels) + return embeddings + + +# length scalar head +class ScalarLengthHead(nn.Module): + def __init__(self, d_model: int, normalized_len: int, cond_dim: int | None = None): + super().__init__() + self.has_cond = cond_dim is not None + if self.has_cond: + self.adaLN = nn.Linear(cond_dim, 2 * d_model, bias=True) + self.adaLN.weight.data.zero_() + self.adaLN.bias.data.zero_() + + self.norm = LayerNorm(d_model) + self.proj1 = nn.Linear(d_model, d_model) + self.act = nn.GELU() + self.proj2 = nn.Linear(d_model, 1) + self.softplus = nn.Softplus() + self.normalized_len = normalized_len + + def forward(self, x: torch.Tensor, c: torch.Tensor | None = None): + x_fp32 = x.float() + c_fp32 = c.float() if (self.has_cond and c is not None) else None + if self.has_cond and c_fp32 is not None: + shift, scale = self.adaLN(c_fp32)[:, None].chunk(2, dim=2) + x_fp32 = modulate_fused(self.norm(x_fp32), shift, scale) + else: + x_fp32 = self.norm(x_fp32) + s = self.proj2(self.act(self.proj1(x_fp32))) + out = self.softplus(s).squeeze(-1) * self.normalized_len + return out.to(x.dtype) + + +################################################################################# +# Core Model # +################################################################################# + + +def get_mask_mod(seq_len: torch.Tensor): + def mask_mod(b, h, q_idx, kv_idx): + return (q_idx <= seq_len[b]) & (kv_idx <= seq_len[b]) + + return mask_mod + + +class DDiTBlock(nn.Module): + def __init__(self, dim, n_heads, cond_dim, mlp_ratio=4, dropout=0.1): + super().__init__() + self.n_heads = n_heads + + self.norm1 = LayerNorm(dim) + self.attn_qkv = nn.Linear(dim, 3 * dim, bias=False) + self.attn_out = nn.Linear(dim, dim, bias=False) + self.dropout1 = nn.Dropout(dropout) + + self.norm2 = LayerNorm(dim) + self.mlp = nn.Sequential( + nn.Linear(dim, mlp_ratio * dim, bias=True), + nn.GELU(approximate="tanh"), + nn.Linear(mlp_ratio * dim, dim, bias=True), + ) + self.dropout2 = nn.Dropout(dropout) + + self.dropout = dropout + + self.adaLN_modulation = nn.Linear(cond_dim, 6 * dim, bias=True) + self.adaLN_modulation.weight.data.zero_() + self.adaLN_modulation.bias.data.zero_() + + def _get_bias_dropout_scale(self): + return ( + bias_dropout_add_scale_fused_train + if self.training + else bias_dropout_add_scale_fused_inference + ) + + def forward(self, x, rotary_cos_sin, c, block_mask): + batch_size = x.shape[0] + + bias_dropout_scale_fn = self._get_bias_dropout_scale() + + shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = ( + self.adaLN_modulation(c)[:, None].chunk(6, dim=2) + ) + + # attention operation + x_skip = x + x = modulate_fused(self.norm1(x), shift_msa, scale_msa) + # dtype0 = x.dtype + + qkv = self.attn_qkv(x) + qkv = rearrange( + qkv, "b s (three h d) -> b s three h d", three=3, h=self.n_heads + ) + with torch.amp.autocast("cuda", enabled=False): + cos, sin = rotary_cos_sin + qkv = rotary.apply_rotary_pos_emb(qkv, cos.to(qkv.dtype), sin.to(qkv.dtype)) + + q, k, v = rearrange(qkv, "b s three h d -> three b h s d", three=3) + + x = flex_attention(q, k, v, block_mask=block_mask) + + x = rearrange(x, "b h s d -> b s (h d)", b=batch_size) + + x = bias_dropout_scale_fn( + self.attn_out(x), None, gate_msa, x_skip, self.dropout + ) + + # mlp operation + x = bias_dropout_scale_fn( + self.mlp(modulate_fused(self.norm2(x), shift_mlp, scale_mlp)), + None, + gate_mlp, + x, + self.dropout, + ) + + return x + + +class EmbeddingLayer(nn.Module): + def __init__(self, dim, vocab_dim): + super().__init__() + self.embedding = nn.Parameter(torch.empty((vocab_dim, dim))) + torch.nn.init.kaiming_uniform_(self.embedding, a=math.sqrt(5)) + + def forward(self, x): + return self.embedding[x] + + +class DDitFinalLayer(nn.Module): + def __init__(self, hidden_size, out_channels, cond_dim): + super().__init__() + self.norm_final = LayerNorm(hidden_size) + self.linear = nn.Linear(hidden_size, out_channels) + self.linear.weight.data.zero_() + self.linear.bias.data.zero_() + + self.adaLN_modulation = nn.Linear(cond_dim, 2 * hidden_size, bias=True) + self.adaLN_modulation.weight.data.zero_() + self.adaLN_modulation.bias.data.zero_() + + def forward(self, x, c): + shift, scale = self.adaLN_modulation(c)[:, None].chunk(2, dim=2) + x = modulate_fused(self.norm_final(x), shift, scale) + x = self.linear(x) + return x + + +class AnyOrderMaskInsertionFlow(nn.Module): + def __init__(self, config): + super().__init__() + + # hack to make loading in configs easier + if isinstance(config, dict): + config = OmegaConf.create(config) + + self.config = config + self.vocab_size = config.interpolant.tokens + self.pad_token = config.interpolant.pad_token + self.mask_token = config.interpolant.mask_token + + self.vocab_embed = EmbeddingLayer(config.model.hidden_size, self.vocab_size) + self.sigma_map = TimestepEmbedder(config.model.cond_dim) + self.rotary_emb = rotary.Rotary( + config.model.hidden_size // config.model.n_heads + ) + + self.blocks = nn.ModuleList( + [ + DDiTBlock( + config.model.hidden_size, + config.model.n_heads, + config.model.cond_dim, + dropout=config.model.dropout, + ) + for _ in range(config.model.n_blocks) + ] + ) + + self.output_layer = DDitFinalLayer( + config.model.hidden_size, self.vocab_size, config.model.cond_dim + ) + + self.len_predict_type = config.training.loss_fn.insert + if self.len_predict_type == "distribution": + self.len_pred = DDitFinalLayer( + config.model.hidden_size, + config.interpolant.max_length + 1, + config.model.cond_dim, + ) + elif self.len_predict_type == "expectation": + normalized_len = config.interpolant.max_length + self.len_pred = ScalarLengthHead( + config.model.hidden_size, normalized_len, config.model.cond_dim + ) + else: + raise ValueError(f"Invalid length prediction type: {self.len_predict_type}") + + def _get_bias_dropout_scale(self): + return ( + bias_dropout_add_scale_fused_train + if self.training + else bias_dropout_add_scale_fused_inference + ) + + def forward(self, indices: torch.Tensor, t: torch.Tensor): + B, L = indices.shape + indices = torch.cat( + [ + indices, + self.pad_token + * torch.ones((B, 1), device=indices.device, dtype=torch.int64), + ], + dim=-1, + ) + seq_lens = (indices != self.pad_token).sum(dim=-1) + block_mask = create_block_mask( + get_mask_mod(seq_lens), + B=B, + H=None, + Q_LEN=indices.shape[1], + KV_LEN=indices.shape[1], + ) + + x = self.vocab_embed(indices) + c = F.silu(self.sigma_map(t)) + + rotary_cos_sin = self.rotary_emb(x) + + with torch.amp.autocast("cuda", dtype=torch.bfloat16): + for i in range(len(self.blocks)): + x = self.blocks[i](x, rotary_cos_sin, c, block_mask) + + # --- unmasking --- + token_logits = self.output_layer(x[:, :-1], c) + + # --- length prediction --- + match self.len_predict_type: + case "distribution": + length_posterior = self.len_pred(x, c) + return ModelPrediction( + token_logits=token_logits, + length_posterior=length_posterior, + ) + case "expectation": + return ModelPrediction( + token_logits=token_logits, + expected_gaps=self.len_pred(x, c), + ) diff --git a/FlexMDM/notebook/sample_anyorder_bracket.ipynb b/FlexMDM/notebook/sample_anyorder_bracket.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..ac973db7fcae424a163dabd415c8fc6c39872a6d --- /dev/null +++ b/FlexMDM/notebook/sample_anyorder_bracket.ipynb @@ -0,0 +1,922 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "0dea6035", + "metadata": {}, + "outputs": [], + "source": [ + "from lightning_modules import (\n", + " AnyOrderInsertionFlowModule,\n", + ")\n", + "from sampling import any_order_mask_insertion_euler_sampling, SamplingTraceDatapoint\n", + "import torch\n", + "\n", + "\n", + "torch.set_float32_matmul_precision('high')" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "594f721e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Fri Jul 11 16:54:02 2025 \n", + "+-----------------------------------------------------------------------------------------+\n", + "| NVIDIA-SMI 575.57.08 Driver Version: 575.57.08 CUDA Version: 12.9 |\n", + "|-----------------------------------------+------------------------+----------------------+\n", + "| GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC |\n", + "| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |\n", + "| | | MIG M. |\n", + "|=========================================+========================+======================|\n", + "| 0 NVIDIA H100 80GB HBM3 On | 00000000:26:00.0 Off | Off |\n", + "| N/A 25C P0 67W / 700W | 0MiB / 81559MiB | 0% Default |\n", + "| | | Disabled |\n", + "+-----------------------------------------+------------------------+----------------------+\n", + " \n", + "+-----------------------------------------------------------------------------------------+\n", + "| Processes: |\n", + "| GPU GI CI PID Type Process name GPU Memory |\n", + "| ID ID Usage |\n", + "|=========================================================================================|\n", + "| No running processes found |\n", + "+-----------------------------------------------------------------------------------------+\n" + ] + } + ], + "source": [ + "!nvidia-smi" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "89c3826a", + "metadata": {}, + "outputs": [], + "source": [ + "def plot_length_distribution(samples, bin_size=5, figsize=(10, 6), color='blue', show_stats=True):\n", + " \"\"\"\n", + " Plot the distribution of sequence lengths from generated samples.\n", + " \n", + " Args:\n", + " samples: Tensor of shape [batch_size, max_length] containing the generated samples\n", + " bin_size: Size of bins for the histogram (default: 5)\n", + " figsize: Figure size as tuple (width, height) (default: (10, 6))\n", + " color: Color of the histogram bars (default: 'blue')\n", + " show_stats: Whether to print basic statistics (default: True)\n", + " \n", + " Returns:\n", + " fig, ax: The figure and axis objects for further customization\n", + " \"\"\"\n", + " import matplotlib.pyplot as plt\n", + " import numpy as np\n", + " \n", + " # Get the lengths of samples (number of non-padding tokens)\n", + " lengths = (samples != 3).sum(dim=-1).cpu().numpy()\n", + " \n", + " # Define bins with intervals of bin_size\n", + " min_length = int(np.floor(lengths.min() / bin_size) * bin_size) # Round down to nearest multiple of bin_size\n", + " max_length = int(np.ceil(lengths.max() / bin_size) * bin_size) # Round up to nearest multiple of bin_size\n", + " bins = np.arange(min_length, max_length + bin_size, bin_size) # +bin_size to include the max value\n", + " \n", + " # Count samples in each bin and calculate percentages\n", + " hist, bin_edges = np.histogram(lengths, bins=bins)\n", + " percentages = 100 * hist / len(lengths) # Convert to percentages\n", + " \n", + " # Create a histogram to visualize the distribution with percentages\n", + " fig, ax = plt.subplots(figsize=figsize)\n", + " ax.bar(\n", + " (bin_edges[:-1] + bin_edges[1:]) / 2, # Center of each bin\n", + " percentages,\n", + " width=np.diff(bin_edges) * 0.9, # Slightly narrower than bin width for visual clarity\n", + " alpha=0.7,\n", + " color=color,\n", + " edgecolor='black'\n", + " )\n", + " ax.set_xlabel('Sample Length (tokens)')\n", + " ax.set_ylabel('Percentage (%)')\n", + " ax.set_title(f'Distribution of Sample Lengths ({bin_size}-unit bins)')\n", + " ax.grid(alpha=0.3)\n", + " \n", + " # Add percentage values above each bar\n", + " for i, p in enumerate(percentages):\n", + " if p > 0: # Only add text for non-zero percentages\n", + " ax.text(\n", + " (bin_edges[i] + bin_edges[i+1]) / 2, # Center of the bar\n", + " p + 0.5, # Slightly above the bar\n", + " f'{p:.1f}%', # Format with 1 decimal place\n", + " ha='center'\n", + " )\n", + " \n", + " plt.tight_layout()\n", + " plt.show()\n", + " \n", + " # Display some basic statistics\n", + " if show_stats:\n", + " print(f\"Min length: {lengths.min()}\")\n", + " print(f\"Max length: {lengths.max()}\")\n", + " print(f\"Mean length: {lengths.mean():.2f}\")\n", + " print(f\"Median length: {np.median(lengths):.2f}\")\n", + " print(f\"Standard deviation: {np.std(lengths):.2f}\")\n", + " print(f\"Most common length: {np.bincount(lengths).argmax()}\")\n", + " print(f\"Sum of percentages: {np.sum(percentages):.1f}%\") # Should be 100%\n", + " \n", + " return fig, ax\n", + "\n", + "def plot_length_evolution(trace, sample_id, steps=None, normalize_x=True, model=None, final_length=None, valid=None):\n", + " \"\"\"\n", + " Plot the evolution of the length (number of non-pad tokens) for a specific sample during sampling.\n", + " \n", + " Args:\n", + " trace: List of tensors with shape [batch_size, max_length] representing the sampling state at each step\n", + " sample_id: Index of the sample to inspect (0 to batch_size-1)\n", + " steps: Number of steps to plot. If None, plot all steps.\n", + " normalize_x: If True, normalize x-axis to range [0, 1] representing sampling progress\n", + " model: The model used for sampling, to access its insertion schedule\n", + " final_length: The final expected length, used to scale the insertion schedule\n", + " valid: List of boolean tensors indicating which samples are valid at each step\n", + " \"\"\"\n", + " import matplotlib.pyplot as plt\n", + " import numpy as np\n", + " import torch\n", + " \n", + " # Validate sample_id\n", + " if sample_id < 0 or sample_id >= trace[0].shape[0]:\n", + " raise ValueError(f\"sample_id must be between 0 and {trace[0].shape[0]-1}\")\n", + " \n", + " # If steps is not provided, use the length of trace\n", + " if steps is None:\n", + " steps = len(trace)\n", + " else:\n", + " steps = min(steps, len(trace))\n", + " \n", + " # Calculate length at each step for the specified sample\n", + " lengths = []\n", + " for step in range(steps):\n", + " # Count non-pad tokens (tokens that are not 3)\n", + " length = (trace[step][sample_id] != 3).sum().item()\n", + " lengths.append(length)\n", + " \n", + " # Create the plot with two y-axes\n", + " fig, ax1 = plt.subplots(figsize=(10, 6))\n", + " \n", + " if normalize_x:\n", + " # Normalize x-axis from 0 to 1 (representing progress percentage)\n", + " x_values = np.linspace(0, 1, steps)\n", + " ax1.set_xlabel('Normalized Sampling Progress')\n", + " else:\n", + " # Use raw step numbers\n", + " x_values = np.arange(steps)\n", + " ax1.set_xlabel('Step')\n", + " \n", + " # Plot the actual length evolution on the first y-axis\n", + " ax1.plot(x_values, lengths, marker='o', linestyle='-', markersize=4, color='blue', label='Actual Length')\n", + " ax1.set_ylabel('Length (non-pad tokens)', color='blue')\n", + " ax1.tick_params(axis='y', labelcolor='blue')\n", + " \n", + " # Add annotations for start and end lengths\n", + " ax1.text(x_values[0], lengths[0], f'{lengths[0]}', verticalalignment='bottom', color='blue')\n", + " ax1.text(x_values[-1], lengths[-1], f'{lengths[-1]}', verticalalignment='bottom', color='blue')\n", + " \n", + " # Highlight significant changes\n", + " length_changes = np.diff(lengths)\n", + " significant_steps = [i+1 for i, change in enumerate(length_changes) if abs(change) > 0]\n", + " \n", + " for step in significant_steps:\n", + " x_pos = x_values[step]\n", + " ax1.axvline(x=x_pos, color='r', linestyle='--', alpha=0.2)\n", + " \n", + " # If model is provided, plot the theoretical insertion schedule\n", + " if model is not None and hasattr(model, 'interpolant') and hasattr(model.interpolant, 'insertion_schedule'):\n", + " # Generate time points\n", + " time_points = torch.linspace(0, 1, 100, device=trace[0].device)\n", + " \n", + " # Get the schedule values\n", + " schedule_values = model.interpolant.insertion_schedule.at(time_points).cpu().numpy()\n", + " \n", + " # If final_length is not provided, use the actual final length\n", + " if final_length is None:\n", + " final_length = lengths[-1]\n", + " \n", + " # Scale the schedule values to match the token count\n", + " scaled_schedule = schedule_values * final_length\n", + " \n", + " # Plot the schedule\n", + " ax1.plot(time_points.cpu().numpy(), scaled_schedule, 'r--', label='Theoretical Schedule')\n", + " \n", + " # If valid is provided, plot cumulative invalid count on a second y-axis\n", + " if valid is not None and len(valid) > 0:\n", + " # Create a second y-axis\n", + " ax2 = ax1.twinx()\n", + " ax2.set_ylabel('Cumulative Invalid Count', color='green')\n", + " ax2.tick_params(axis='y', labelcolor='green')\n", + " \n", + " # Extract boolean values for the specific sample\n", + " invalid_counts = []\n", + " cumulative_invalid = 0\n", + " \n", + " # Ensure we don't go beyond the length of valid\n", + " valid_steps = min(steps, len(valid))\n", + " \n", + " for step in range(valid_steps):\n", + " # Check if the sample is valid at this step\n", + " if not valid[step][sample_id].item():\n", + " cumulative_invalid += 1\n", + " invalid_counts.append(cumulative_invalid)\n", + " \n", + " # If valid has fewer steps than trace, pad with the last value\n", + " if valid_steps < steps:\n", + " invalid_counts.extend([invalid_counts[-1]] * (steps - valid_steps))\n", + " \n", + " # Plot the cumulative invalid count\n", + " ax2.plot(x_values, invalid_counts, color='green', linestyle='-', marker='.', label='Cumulative Invalid')\n", + " \n", + " # Add a legend for the second y-axis\n", + " lines1, labels1 = ax1.get_legend_handles_labels()\n", + " lines2, labels2 = ax2.get_legend_handles_labels()\n", + " ax2.legend(lines1 + lines2, labels1 + labels2, loc='upper left')\n", + " else:\n", + " ax1.legend(loc='upper left')\n", + " \n", + " plt.title(f'Evolution of Length for Sample {sample_id}')\n", + " plt.grid(True, alpha=0.3)\n", + " plt.tight_layout()\n", + " plt.show()\n", + " \n", + " # Print summary statistics\n", + " print(f\"Initial length: {lengths[0]}\")\n", + " print(f\"Final length: {lengths[-1]}\")\n", + " print(f\"Number of length changes: {len(significant_steps)}\")\n", + " if valid is not None:\n", + " print(f\"Final cumulative invalid count: {invalid_counts[-1] if invalid_counts else 0}\")\n", + " \n", + " # Return the lengths for further analysis if needed\n", + " return lengths" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "485bd86f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "AnyOrderInsertionFlowModule(\n", + " (model): OptimizedModule(\n", + " (_orig_mod): AnyOrderMaskInsertionFlow(\n", + " (vocab_embed): EmbeddingLayer()\n", + " (sigma_map): TimestepEmbedder(\n", + " (mlp): Sequential(\n", + " (0): Linear(in_features=256, out_features=64, bias=True)\n", + " (1): SiLU()\n", + " (2): Linear(in_features=64, out_features=64, bias=True)\n", + " )\n", + " )\n", + " (rotary_emb): Rotary()\n", + " (blocks): ModuleList(\n", + " (0-2): 3 x DDiTBlock(\n", + " (norm1): LayerNorm()\n", + " (attn_qkv): Linear(in_features=256, out_features=768, bias=False)\n", + " (attn_out): Linear(in_features=256, out_features=256, bias=False)\n", + " (dropout1): Dropout(p=0.0, inplace=False)\n", + " (norm2): LayerNorm()\n", + " (mlp): Sequential(\n", + " (0): Linear(in_features=256, out_features=1024, bias=True)\n", + " (1): GELU(approximate='tanh')\n", + " (2): Linear(in_features=1024, out_features=256, bias=True)\n", + " )\n", + " (dropout2): Dropout(p=0.0, inplace=False)\n", + " (adaLN_modulation): Linear(in_features=64, out_features=1536, bias=True)\n", + " )\n", + " )\n", + " (length_block): ModuleList(\n", + " (0): DDiTBlock(\n", + " (norm1): LayerNorm()\n", + " (attn_qkv): Linear(in_features=256, out_features=768, bias=False)\n", + " (attn_out): Linear(in_features=256, out_features=256, bias=False)\n", + " (dropout1): Dropout(p=0.0, inplace=False)\n", + " (norm2): LayerNorm()\n", + " (mlp): Sequential(\n", + " (0): Linear(in_features=256, out_features=1024, bias=True)\n", + " (1): GELU(approximate='tanh')\n", + " (2): Linear(in_features=1024, out_features=256, bias=True)\n", + " )\n", + " (dropout2): Dropout(p=0.0, inplace=False)\n", + " (adaLN_modulation): Linear(in_features=64, out_features=1536, bias=True)\n", + " )\n", + " )\n", + " (output_layer): DDitFinalLayer(\n", + " (norm_final): LayerNorm()\n", + " (linear): Linear(in_features=256, out_features=4, bias=True)\n", + " (adaLN_modulation): Linear(in_features=64, out_features=512, bias=True)\n", + " )\n", + " (len_pred): ScalarLengthHead(\n", + " (adaLN): Linear(in_features=64, out_features=512, bias=True)\n", + " (norm): LayerNorm()\n", + " (proj1): Linear(in_features=256, out_features=256, bias=True)\n", + " (act): GELU(approximate='none')\n", + " (proj2): Linear(in_features=256, out_features=1, bias=True)\n", + " (softplus): Softplus(beta=1.0, threshold=20.0)\n", + " )\n", + " )\n", + " )\n", + ")" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "checkpoint_path = \"/n/netscratch/albergo_lab/Lab/brianlck/interpretable-flow/checkpoints/bracket/any_order/20250711-151647/last.ckpt\"\n", + "\n", + "model = AnyOrderInsertionFlowModule.load_from_checkpoint(checkpoint_path)\n", + "# model.swap_to_ema()\n", + "model.eval()" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "14e5855e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "hi 32 20\n", + "hi 64 20\n", + "0.00013176546296497739 1.0980455247081448e-05\n" + ] + }, + { + "data": { + "text/plain": [ + "0.5714285714285714" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "lengths = {4: 0.1, 16: 0.4, 32: 0.4, 64: 0.1}\n", + "\n", + "def binomial_mass(k, n, p):\n", + " \"\"\"\n", + " Calculate the probability mass function (PMF) for a binomial distribution.\n", + " \n", + " Args:\n", + " k (int): Number of successes\n", + " n (int): Number of trials\n", + " p (float): Probability of success in a single trial\n", + " \n", + " Returns:\n", + " float: Probability mass P(X = k)\n", + " \"\"\"\n", + " import math\n", + " \n", + " # Calculate binomial coefficient (n choose k)\n", + " try:\n", + " binom_coef = math.factorial(n) / (math.factorial(k) * math.factorial(n - k))\n", + " except ValueError:\n", + " # Handle cases where k > n or negative values\n", + " return 0.0\n", + " \n", + " # Calculate probability mass\n", + " return binom_coef * (p ** k) * ((1 - p) ** (n - k))\n", + "\n", + "\n", + "def calculate_rate(alpha_t, len_t):\n", + " nom, denom = 0, 0\n", + "\n", + " for length, probability in lengths.items():\n", + " if length >= len_t:\n", + " print(\"hi\", length, len_t)\n", + " nom += (length - len_t) * probability * binomial_mass(len_t, length, alpha_t)\n", + " denom += probability * binomial_mass(len_t, length, alpha_t)\n", + " \n", + " print(nom, denom)\n", + " return nom / ((len_t + 1) * denom)\n", + "\n", + "\n", + "calculate_rate(0.9, 20)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "6caf3a17", + "metadata": {}, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'model' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mNameError\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[5]\u001b[39m\u001b[32m, line 12\u001b[39m\n\u001b[32m 9\u001b[39m len_t = \u001b[32m20\u001b[39m\n\u001b[32m 10\u001b[39m t = \u001b[32m0.9\u001b[39m\n\u001b[32m---> \u001b[39m\u001b[32m12\u001b[39m xt = \u001b[43mconstruct_tensor\u001b[49m\u001b[43m(\u001b[49m\u001b[43mlen_t\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[32;43m64\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[32m 13\u001b[39m t = torch.tensor([t], device=model.device)\n\u001b[32m 14\u001b[39m alpha_t = model.interpolant.insertion_schedule.at(t).item()\n", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[5]\u001b[39m\u001b[32m, line 5\u001b[39m, in \u001b[36mconstruct_tensor\u001b[39m\u001b[34m(length, max_length)\u001b[39m\n\u001b[32m 1\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34mconstruct_tensor\u001b[39m(length, max_length):\n\u001b[32m 2\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m torch.tensor(\n\u001b[32m 3\u001b[39m [\u001b[32m0\u001b[39m] * (length) + [\u001b[32m3\u001b[39m] * (max_length - length),\n\u001b[32m 4\u001b[39m dtype=torch.int64,\n\u001b[32m----> \u001b[39m\u001b[32m5\u001b[39m device=\u001b[43mmodel\u001b[49m.device\n\u001b[32m 6\u001b[39m ).unsqueeze(\u001b[32m0\u001b[39m)\n", + "\u001b[31mNameError\u001b[39m: name 'model' is not defined" + ] + } + ], + "source": [ + "def construct_tensor(length, max_length):\n", + " return torch.tensor(\n", + " [0] * (length) + [3] * (max_length - length),\n", + " dtype=torch.int64,\n", + " device=model.device\n", + " ).unsqueeze(0) # Add batch dimension\n", + "\n", + "\n", + "len_t = 20\n", + "t = 0.9\n", + "\n", + "xt = construct_tensor(len_t, 64)\n", + "t = torch.tensor([t], device=model.device)\n", + "alpha_t = model.interpolant.insertion_schedule.at(t).item()\n", + "\n", + "print(xt.tolist(), t.item(), alpha_t)\n", + "\n", + "print(model(xt, t).expected_gaps, calculate_rate(alpha_t, len_t))" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "2b5d5cc4", + "metadata": {}, + "outputs": [], + "source": [ + "# First, fix the calculate_rate function to remove debugging print statements\n", + "def calculate_rate(alpha_t, len_t):\n", + " nom, denom = 0, 0\n", + "\n", + " for length, probability in lengths.items():\n", + " if length >= len_t:\n", + " nom += (length - len_t) * probability * binomial_mass(len_t, length, alpha_t)\n", + " denom += probability * binomial_mass(len_t, length, alpha_t)\n", + " \n", + " # Ensure denom is not zero to prevent division by zero\n", + " if denom == 0:\n", + " return 0.0\n", + " \n", + " return nom / (denom) # Prevent division by zero\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bf89eaa6", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "a0b49879", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Final step, removing mask token from sampling\n", + "tensor([0., 0., 0., ..., 0., 0., 0.], device='cuda:0')\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA90AAAJOCAYAAACqS2TfAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjMsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvZiW1igAAAAlwSFlzAAAPYQAAD2EBqD+naQAAeCVJREFUeJzt3Xd4FPX6/vF7A6SQTkgIJfQaqdKMKDUYEBEQpaiHIoJiECF69GBBUDCIiqAgqIcDWFAEARWVKgRRQAhEinTBoJBCgFQSQnZ+f+THfl0SIMEMm/J+XVeu43xmduaZ3Wd3uc/MzlgMwzAEAAAAAACKnJOjCwAAAAAAoLQidAMAAAAAYBJCNwAAAAAAJiF0AwAAAABgEkI3AAAAAAAmIXQDAAAAAGASQjcAAAAAACYhdAMAAAAAYBJCNwAAAAAAJiF0A4ADTJo0SRaL5aZsq3PnzurcubNtetOmTbJYLFq2bNlN2f6wYcNUu3btm7KtG5WWlqZHH31UgYGBslgsGjdunKNLMsXN7LvirnPnzmratKnp2/nll1/k7OysP/74w/Rt/RMLFy6UxWLRiRMnbujxJ06ckMVi0ZtvvnndZc3uw0GDBmnAgAGmrR8ACovQDQD/0OV/rF7+c3V1VbVq1RQWFqZ33nlHqampRbKdU6dOadKkSYqJiSmS9RWl4lxbQbz22mtauHChRo8erY8//lj/+te/rrrsxYsXNWvWLLVq1UpeXl7y8fHRLbfcolGjRungwYM3sWrHu9z7O3fudHQp+SoOffnCCy9o8ODBqlWrlm1s2LBhdp8Zl/8aN27ssDrz895772nhwoWOLqPQnnvuOX355Zf69ddfHV0KAEiSyju6AAAoLV555RXVqVNH2dnZiouL06ZNmzRu3DjNmDFDX3/9tZo3b25b9sUXX9R//vOfQq3/1KlTmjx5smrXrq2WLVsW+HFr164t1HZuxLVq+/DDD2W1Wk2v4Z/44YcfdNttt+nll1++7rL9+/fX999/r8GDB2vkyJHKzs7WwYMHtWrVKt1+++3FLjiVZTf6nikqMTExWr9+vX7++ec881xcXPTf//7Xbszb2/tmlZbHv/71Lw0aNEguLi62sffee0+VK1fWsGHDinRbN/L5VxitWrVSmzZt9NZbb+mjjz4ybTsAUFCEbgAoIj179lSbNm1s0xMmTNAPP/yge+65R/fee68OHDggNzc3SVL58uVVvry5H8EZGRmqWLGinJ2dTd3O9VSoUMGh2y+IhIQEBQcHX3e5HTt2aNWqVZo6daqef/55u3mzZ8/W+fPnTaoQJdGCBQtUs2ZN3XbbbXnmlS9fXg8//LADqspfuXLlVK5cuZuyrZvx+TdgwAC9/PLLeu+99+Th4WHqtgDgeji9HABM1LVrV7300kv6448/9Mknn9jG8/tN47p163THHXfIx8dHHh4eatSokS3Ybdq0SW3btpUkDR8+3HY66uVTPy//PjU6OlodO3ZUxYoVbY+98jfdl+Xk5Oj5559XYGCg3N3dde+99+rkyZN2y9SuXTvfo1x/X+f1asvvN93p6el6+umnFRQUJBcXFzVq1EhvvvmmDMOwW85isWjMmDFauXKlmjZtKhcXF91yyy1avXp1/k/4FRISEjRixAhVqVJFrq6uatGihRYtWmSbf/n37cePH9e3335rq/1qv2s9duyYJKlDhw555pUrV05+fn626T/++ENPPPGEGjVqJDc3N/n5+emBBx7Is+7Lp2hv2bJFY8eOlb+/v3x8fPTYY4/p4sWLOn/+vIYMGSJfX1/5+vrq2WeftXue/v5b2rffflu1atWSm5ubOnXqpH379hXoefrkk0/UunVrubm5qVKlSho0aFCeXvgn/vrrLz3yyCOqUqWK7TX83//+Z7fM5dfiiy++0NSpU1WjRg25urqqW7duOnr0aJ51zpkzR3Xr1pWbm5vatWunH3/8sVB9edlvv/2mLl26qGLFiqpevbqmT5+eZ1vvvvuubrnlFlWsWFG+vr5q06aNFi9efN39Xrlypbp27XrV3y/n5OQoJSXluuu50tWuk5Df50pB30NX/qa7du3a2r9/v6KiomzPXX6fI/m5Xh/+kzpTU1M1btw41a5dWy4uLgoICFD37t21a9cuu+W6d++u9PR0rVu3rkA1A4CZONINACb717/+peeff15r167VyJEj811m//79uueee9S8eXO98sorcnFx0dGjR/XTTz9Jkpo0aaJXXnlFEydO1KhRo3TnnXdKkm6//XbbOpKSktSzZ08NGjRIDz/8sKpUqXLNuqZOnSqLxaLnnntOCQkJmjlzpkJDQxUTE2M7Il8QBant7wzD0L333quNGzdqxIgRatmypdasWaN///vf+uuvv/T222/bLb9lyxYtX75c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v15S7u+94+Li7P4uXrxY4O3+3Y2+h27EjfThZQWpc/r06dq3b5+CgoLUrl07TZo06ar/54FhGNyXHkCxwG+6AcBkf/75p5KTk/OEhL9zc3PT5s2btXHjRn377bdavXq1lixZoq5du2rt2rUFuqpwYX6HXVDXugDUzbrS8dW2U9AwY6aqVatq0KBB6t+/v2655RZ98cUXWrhwocqXL68nn3xSCxYs0Lhx4xQSEiJvb29ZLBYNGjQo3/8z5Wr7md94Ue271WqVxWLR999/n+92/unviy/v58MPP6yhQ4fmu8zfb6Un3dzXuyDbatKkiQ4dOqRVq1Zp9erV+vLLL/Xee+9p4sSJmjx58lXXffk3yAUNtpcvtnf27FlJ0smTJ1WnTh27ZTZu3KjOnTtf832Zn+L8Hvq7gtQ5YMAA3XnnnVqxYoXWrl2rN954Q6+//rqWL19u+x3+ZefOncv3/wQAgJuN0A0AJvv4448lSWFhYddczsnJSd26dVO3bt00Y8YMvfbaa3rhhRe0ceNGhYaGFvkRmyNHjthNG4aho0eP2oUgX1/ffG+D9ccff6hu3bq26cLUVqtWLa1fv16pqal2R7sPHjxom18UatWqpT179shqtdod7S7q7Ui5p603b95cR44csZ2evWzZMg0dOlRvvfWWbbnMzEzTbit25espSYcPH77m0d969erJMAzVqVNHDRs2LPKa/P395enpqZycHIWGhhbJOi+/bkePHlWXLl1s45cuXdKJEyfs+reo3jPu7u4aOHCgBg4cqIsXL+q+++7T1KlTNWHCBLm6uub7mMv3az9+/HiBtpGamqozZ87YLuIXGBiY58rbLVq0kHTt92VRupHn70b6sLCqVq2qJ554Qk888YQSEhJ06623aurUqXah+9KlSzp58qTuvffeItsuANwoTi8HABP98MMPevXVV1WnTh099NBDV13u8tGtv2vZsqUkKSsrS1LuP/wlFVlou3yV4cuWLVum06dP2/3DtV69etq2bZvdaa2rVq3KczupwtR29913KycnR7Nnz7Ybf/vtt2WxWPIcrbpRd999t+Li4rRkyRLb2KVLl/Tuu+/Kw8NDnTp1KvQ6jxw5otjY2Dzj58+f19atW+Xr62sLTeXKlctzJPHdd9+96tHIf2rlypX666+/bNO//PKLtm/ffs3n87777lO5cuU0efLkPLUahmH7XfKNKleunPr3768vv/wy39uXJSYmFnqdbdq0kZ+fnz788ENdunTJNv7pp5/mOapcFO+ZK58DZ2dnBQcHyzAMZWdnX/Vx1atXV1BQkHbu3Gk3npmZme/1HV599VUZhqEePXpIklxdXRUaGmr3d/lU+3r16ik5OVl79uyxPf706dNasWLFDe9nftzd3Qv93N1IHxZUTk5Onp9mBAQEqFq1arbPyct+++03ZWZmXvUuCgBwM3GkGwCKyPfff6+DBw/q0qVLio+P1w8//KB169apVq1a+vrrr696REySXnnlFW3evFm9evVSrVq1lJCQoPfee081atTQHXfcISn3H9o+Pj6aN2+ePD095e7urvbt2+c5BbWgKlWqpDvuuEPDhw9XfHy8Zs6cqfr169vd1uzRRx/VsmXL1KNHDw0YMEDHjh3TJ598Ynexo8LW1rt3b3Xp0kUvvPCCTpw4oRYtWmjt2rX66quvNG7cuDzrvlGjRo3S+++/r2HDhik6Olq1a9fWsmXL9NNPP2nmzJnX/I391fz666968MEH1bNnT915552qVKmS/vrrLy1atEinTp3SzJkzbafI3nPPPfr444/l7e2t4OBgbd26VevXr7/mbc7+ifr16+uOO+7Q6NGjlZWVpZkzZ8rPz0/PPvvsVR9Tr149TZkyRRMmTLDddsvT01PHjx/XihUrNGrUKD3zzDPX3fb//ve/fO+d/tRTT2natGnauHGj2rdvr5EjRyo4OFhnz57Vrl27tH79+nz/D6drcXZ21qRJk/Tkk0+qa9euGjBggE6cOKGFCxeqXr16dkdni+I9c9dddykwMFAdOnRQlSpVdODAAc2ePVu9evW6bg/16dNHK1assPttcVxcnFq1aqXBgwfbjoavWbNG3333nXr06KE+ffpct6ZBgwbpueeeU79+/TR27FhlZGRo7ty5atiwYb4X6btRrVu31ty5czVlyhTVr19fAQEB6tq16zUfcyN9WFCpqamqUaOG7r//frVo0UIeHh5av369duzYYXdGiSStW7dOFStWVPfu3f/xdgHgH7up10oHgFLo8q12Lv85OzsbgYGBRvfu3Y1Zs2bZ3ZrqsitvmbNhwwajT58+RrVq1QxnZ2ejWrVqxuDBg43Dhw/bPe6rr74ygoODjfLly9vdCqlTp05Xva3R1W4Z9tlnnxkTJkwwAgICDDc3N6NXr1753vrqrbfeMqpXr264uLgYHTp0MHbu3JlnndeqLb/bG6Wmphrjx483qlWrZlSoUMFo0KCB8cYbb9jdMsswcm8jlN/tga52K7MrxcfHG8OHDzcqV65sODs7G82aNcv39lEFvWVYfHy8MW3aNKNTp05G1apVjfLlyxu+vr5G165djWXLltkte+7cOdu2PTw8jLCwMOPgwYN5ar/abbcu98iVt5EbOnSo4e7ubpu+fKumN954w3jrrbeMoKAgw8XFxbjzzjuNX3/9Nd91XunLL7807rjjDsPd3d1wd3c3GjdubISHhxuHDh265vNxZe9f+Xfy5Enb8xYeHm4EBQUZFSpUMAIDA41u3boZH3zwgW1dl/ty6dKldtu4vH9Xvm7vvPOOUatWLcPFxcVo166d8dNPPxmtW7c2evToYbdcYd8zV/br+++/b3Ts2NHw8/MzXFxcjHr16hn//ve/jeTk5Gs+N4ZhGLt27TIkGT/++KNt7Ny5c8bDDz9s1K9f36hYsaLh4uJi3HLLLcZrr71mXLx48brrvGzt2rVG06ZNDWdnZ6NRo0bGJ598ctVbcRXkPZTfLcPi4uKMXr16GZ6ennlux3alf9qHBakzKyvL+Pe//220aNHC8PT0NNzd3Y0WLVoY7733Xp7HtW/f3nj44YevWi8A3EwWwyhmV9EAAAAFduLECdWpU0dvvPFGgY5Kl1ZWq1X+/v6677779OGHHzq6HJtu3bqpWrVqtms7wHwxMTG69dZbtWvXLtvPdADAkfhNNwAAKFEyMzPz/Ab9o48+0tmzZ9W5c2fHFHUVr732mpYsWVLkFznD1U2bNk33338/gRtAscFvugEAQImybds2jR8/Xg888ID8/Py0a9cuzZ8/X02bNtUDDzzg6PLstG/f/obvr40b8/nnnzu6BACwQ+gGAAAlSu3atRUUFKR33nlHZ8+eVaVKlTRkyBBNmzZNzs7Oji4PAAA7/KYbAAAAAACT8JtuAAAAAABMQugGAAAAAMAkpf433VarVadOnZKnp6csFoujywEAAAAAlAKGYSg1NVXVqlWTk9PVj2eX+tB96tQpBQUFOboMAAAAAEApdPLkSdWoUeOq80t96Pb09JSU+0R4eXk5uBp7VqtViYmJ8vf3v+b/M4LSjT6ARB8gF30AiT5ALvoAEn1Q3KWkpCgoKMiWOa+m1Ifuy6eUe3l5FcvQnZmZKS8vL95EZRh9AIk+QC76ABJ9gFz0AST6oKS43s+YeeUAAAAAADAJoRsAAAAAAJMQugEAAAAAMAmhGwAAAAAAkxC6AQAAAAAwCaEbAAAAAACTELoBlHjTpk2TxWLRuHHjJElnz57Vk08+qUaNGsnNzU01a9bU2LFjlZycfM31GIahiRMnqmrVqnJzc1NoaKiOHDlim5+VlaV//etf8vLyUsOGDbV+/Xq7x7/xxht68skni3z/AAAAUHIRugGUaDt27ND777+v5s2b28ZOnTqlU6dO6c0339S+ffu0cOFCrV69WiNGjLjmuqZPn6533nlH8+bN0/bt2+Xu7q6wsDBlZmZKkj744ANFR0dr69atGjVqlB588EEZhiFJOn78uD788ENNnTrVvJ0FAABAiUPoBlBipaWl6aGHHtKHH34oX19f23jTpk315Zdfqnfv3qpXr566du2qqVOn6ptvvtGlS5fyXZdhGJo5c6ZefPFF9enTR82bN9dHH32kU6dOaeXKlZKkAwcO6N5779Utt9yi8PBwJSYm6syZM5Kk0aNH6/XXX5eXl5fp+w0AAICSg9ANoMQKDw9Xr169FBoaet1lk5OT5eXlpfLly+c7//jx44qLi7Nbl7e3t9q3b6+tW7dKklq0aKEtW7bowoULWrNmjapWrarKlSvr008/laurq/r161c0OwYAAIBSI/9/fQJAMff5559r165d2rFjx3WXPXPmjF599VWNGjXqqsvExcVJkqpUqWI3XqVKFdu8Rx55RHv27FFwcLAqV66sL774QufOndPEiRO1adMmvfjii/r8889Vr149/e9//1P16tX/wR4CAACgNCB0AyhxTp48qaeeekrr1q2Tq6vrNZdNSUlRr169FBwcrEmTJv2j7VaoUEFz5syxGxs+fLjGjh2r3bt3a+XKlfr11181ffp0jR07Vl9++eU/2h4AAABKPk4vB1DiREdHKyEhQbfeeqvKly+v8uXLKyoqSu+8847Kly+vnJwcSVJqaqp69OghT09PrVixQhUqVLjqOgMDAyVJ8fHxduPx8fG2eVfauHGj9u/frzFjxmjTpk26++675e7urgEDBmjTpk1Fs7MAAAAo0QjdAEqcbt26ae/evYqJibH9tWnTRg899JBiYmJUrlw5paSk6K677pKzs7O+/vrr6x4Rr1OnjgIDA7Vh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9fTTT2vv3r1avny5brvtNjNqBAAAAACgRCp06J4xY4bS0tIkSZMnT1ZaWpqWLFmiBg0acOVyAAAAAAD+ptChu27durb/dnd317x584q0IAAAAAAASotC/6a7bt26SkpKyjN+/vx5u0AOAAAAAEBZV+jQfeLECeXk5OQZz8rK0l9//VUkRQEAAAAAUBoU+PTyr7/+2vbfa9askbe3t206JydHGzZsUO3atYu0OAAAAAAASrICh+6+fftKkiwWi4YOHWo3r0KFCqpdu7beeuutIi0OAAAAAICSrMCh22q1SpLq1KmjHTt2qHLlyqYVBQAAAABAaVDoq5cfP37cjDoAAAAAACh1Ch26JWnDhg3asGGDEhISbEfAL/vf//5XJIUBAAAAAFDSFTp0T548Wa+88oratGmjqlWrymKxmFEXAAAAAAAlXqFD97x587Rw4UL961//MqMeAAAAAABKjULfp/vixYu6/fbbzagFAAAAAIBSpdCh+9FHH9XixYvNqAUAAAAAgFKl0KeXZ2Zm6oMPPtD69evVvHlzVahQwW7+jBkziqw4AAAAAABKskKH7j179qhly5aSpH379tnN46JqAAAAAAD8n0KH7o0bN5pRBwAAAAAApU6hf9N92dGjR7VmzRpduHBBkmQYRpEVBQAAAABAaVDo0J2UlKRu3bqpYcOGuvvuu3X69GlJ0ogRI/T0008XeYEAAAAAAJRUhQ7d48ePV4UKFRQbG6uKFSvaxgcOHKjVq1cXal2RkZFq27atPD09FRAQoL59++rQoUN2y2RmZio8PFx+fn7y8PBQ//79FR8fX9iyAQAAAAC46QoduteuXavXX39dNWrUsBtv0KCB/vjjj0KtKyoqSuHh4dq2bZvWrVun7Oxs3XXXXUpPT7ctM378eH3zzTdaunSpoqKidOrUKd13332FLRsAAAAAgJuu0BdSS09PtzvCfdnZs2fl4uJSqHVdeWR84cKFCggIUHR0tDp27Kjk5GTNnz9fixcvVteuXSVJCxYsUJMmTbRt2zbddttthS0fAAAAAICbptBHuu+880599NFHtmmLxSKr1arp06erS5cu/6iY5ORkSVKlSpUkSdHR0crOzlZoaKhtmcaNG6tmzZraunXrP9oWAAAAAABmK/SR7unTp6tbt27auXOnLl68qGeffVb79+/X2bNn9dNPP91wIVarVePG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C7RcsQ7dPXv2tP138+bN1b59e9WqVUtffPGFRowYke9jJkyYoIiICNt0SkqKgoKC5O/vLy8vL9NrLgyr1SqLxSJ/f39lZGToyJGT8vHxlLt7gKNLM016eprOnz8pT09PBQSU3v0sjL/3AV+qUlpaWpl4L0h53w/0Acri94LEd8OV+F6ARB8gF98Lxft7wdXVtUDLFevQfSUfHx81bNhQR48eveoyLi4ucnFxyTPu5ORULD+wLBaLnJycbKdPGIZFhlH86iwquftn2PYbuS4/HzwnKjPvBSnv+4E+gFT2vhckvhvyw+cBJPoAufheKL77WtDaiu8e5CMtLU3Hjh1T1apVHV0KAAAAAADXVaxD9zPPPKOoqCidOHFCP//8s/r166dy5cpp8ODBji4NAAAAAIDrKtanl//5558aPHiwkpKS5O/vrzvuuEPbtm2Tv7+/o0sDAAAAAOC6inXo/vzzzx1dAgAAAAAAN6xYn14OAAAAAEBJRugGAAAAAMAkhG4AAAAAAExC6AYAAAAAwCSEbgAAAAAATELoBgAAAADAJIRuAAAAAABMQugGAAAAAMAkhG4AAAAAAExC6AYAAAAAwCSEbgAAAAAATELoBgAAAADAJIRuAAAAAABMQugGAAAAAMAkhG4AAAAAAExC6AYAAAAAwCSEbgAAAAAATELoBgAAAADAJIRuAAAAAABMQugGAAAAAMAkhG4AAAAAAExC6AYAAAAAwCSEbgAAAAAATELoBgAAAADAJIRuAAAAAABMQugGSpE5c+aodu3acnV1Vfv27fXLL79cddns7Gy98sorqlevnlxdXdWiRQutXr3abplPP/1UQUFB8vX1VUREhN28EydOqGHDhkpJSTFlXwAAAIDSgNANlBJLlixRRESEXn75Ze3atUstWrRQWFiYEhIS8l3+xRdf1Pvvv693331Xv/32mx5//HH169dPu3fvliSdOXNGjz76qN58802tXbtWn3zyiVatWmV7/BNPPKFp06bJy8vrpuwfAAAAUBIRuoFSYsaMGRo5cqSGDx+u4OBgzZs3TxUrVtT//ve/fJf/+OOP9fzzz+vuu+9W3bp1NXr0aN1999166623JEm///67vL29NXDgQLVt21ZdunTRgQMHJEmfffaZKlSooPvuu++m7R8AAABQEhG6gVLg4sWLio6OVmhoqG3MyclJoaGh2rp1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Fy9q0aJFqlu3ruLj47VhwwYlJSU5ujRcxZYtW3Ts2DH179/f0aX8Y87OznrwwQf1zjvv6M4773R0OQCAYoIj3QCAUuH8+fP68ccf9frrr6tLly6qVauW2rVrpwkTJujee++1LTdjxgw1a9ZM7u7uCgoK0hNPPKG0tDTb/IULF8rHx0erVq1So0aNVLFiRd1///3KyMjQokWLVLt2bfn6+mrs2LF2p0PXrl1br776qgYPHix3d3dVr15dc+bMuWbNJ0+e1IABA+Tj46NKlSqpT58+OnHixA0/B1arVZGRkapTp47c3NzUokULLVu2zDZ/06ZNslgs2rBhg9q0aaOKFSvq9ttv16FDh+zWM2XKFAUEBMjT01OPPvqo/vOf/9iOPE+aNEmLFi3SV199JYvFIovFok2bNtke+/vvv6tLly6qWLGiWrRooa1bt16z5s8//1zdu3eXq6urpNznf/Lkyfr1119t61+4cKEkKTY2Vn369JGHh4e8vLw0YMAAxcfHX3Xdx44dU926dTVmzBgZhqGsrCw988wzql69utzd3dW+fXu72i+/9mvWrFGTJk3k4eGhHj166PTp03bPYbt27eTu7i4fHx916NBBf/zxh21+79699fXXX+vChQvX3G8AQNlB6AYAlAoeHh7y8PDQypUrlZWVddXlnJyc9M4772j//v1atGiRfvjhBz377LN2y2RkZOidd97R559/rtWrV2vTpk3q16+fvvvuO3333Xf6+OOP9f7779sFWkl644031KJFC+3evVv/+c9/9NRTT2ndunX51pGdna2wsDB5enrqxx9/1E8//WQLeRcvXryh5yAyMlIfffSR5s2bp/3792v8+PF6+OGHFRUVZbfcCy+8oLfeeks7d+5U+fLl9cgjj9jmffrpp5o6dapef/11RUdHq2bNmpo7d65t/jPPPKMBAwbYwujp06d1++232637mWeeUUxMjBo2bKjBgwfr0qVLV635xx9/VJs2bWzTAwcO1NNPP61bbrnFtv6BAwfKarWqT58+Onv2rKKiorRu3Tr9/vvvGjhwYL7r3bNnj+644w49+OCDmj17tiwWi8aMGaOtW7fq888/1549e/TAAw+oR48eOnLkiO1xGRkZevPNN/Xxxx9r8+bNio2N1TPPPCNJunTpkvr27atOnTppz5492rp1q0aNGiWLxWJ7fJs2bXTp0iVt3779ei8XAKCsMAAAKCWWLVtm+Pr6Gq6ursbtt99uTJgwwfj111+v+ZilS5cafn5+tukFCxYYkoyjR4/axh577DGjYsWKRmpqqm0sLCzMeOyxx2zTtWrVMnr06GG37oEDBxo9e/a0TUsyVqxYYRiGYXz88cdGo0aNDKvVapuflZVluLm5GWvWrMm31uPHjxuSjN27d+eZl5mZaVSsWNH4+eef7cZHjBhhDB482DAMw9i4caMhyVi/fr1t/rfffmtIMi5cuGAYhmG0b9/eCA8Pt1tHhw4djBYtWtimhw4davTp0yff2v773//axvbv329IMg4cOJDv/hiGYXh7exsfffSR3djLL79stz3DMIy1a9ca5cqVM2JjY/Os/5dffrF73E8//WT4+voab775pm3ZP/74wyhXrpzx119/2a23W7duxoQJEwzDyP+1nzNnjlGlShXDMAwjKSnJkGRs2rTpqvtjGIbh6+trLFy48JrLAADKDo50AwBKjf79++vUqVP6+uuv1aNHD23atEm33nqr7fRkSVq/fr26deum6tWry9PTU//617+UlJSkjIwM2zIVK1ZUvXr1bNNVqlRR7dq15eHhYTeWkJBgt/2QkJA80wcOHMi31l9//VVHjx6Vp6en7Sh9pUqVlJmZqWPHjhV6348ePaqMjAx1797dtj4PDw999NFHedbXvHlz239XrVpVkmz7cujQIbVr185u+Sunr+Va687PhQsXbKeWX8uBAwcUFBSkoKAg21hwcLB8fHzsnuPY2Fh1795dEydO1NNPP20b37t3r3JyctSwYUO75ycqKsru+bnyta9ataqt/kqVKmnYsGEKCwtT7969NWvWLLtTzy9zc3Oz6ycAQNnGhdQAAKWKq6urunfvru7du+ull17So48+qpdfflnDhg3TiRMndM8992j06NGaOnWqKlWqpC1btmjEiBG6ePGiKlasKEmqUKGC3TotFku+Y1ar9YbrTEtLU+vWrfXpp5/mmefv739D65Okb7/9VtWrV7eb5+LiYjf99325fGr0P9mXf7LuypUr69y5c0WybSn3uatWrZo+++wzPfLII/Ly8pKU+/yUK1dO0dHRtqukX/b3/zMlv9fZMAzb9IIFCzR27FitXr1aS5Ys0Ysvvqh169bptttusy1z9uzZG3oNAQClE0e6AQClWnBwsNLT0yVJ0dHRslqteuutt3TbbbepYcOGOnXqVJFta9u2bXmmmzRpku+yt956q44cOaKAgADVr1/f7s/b27vQ2w4ODpaLi4tiY2PzrO/vR4evp1GjRtqxY4fd2JXTzs7O//ie2pe1atVKv/3223XX36RJE508eVInT560jf322286f/68goODbWNubm5atWqVXF1dFRYWptTUVNt2cnJylJCQkOf5CQwMLHTNEyZM0M8//6ymTZtq8eLFtnnHjh1TZmamWrVqVah1AgBKL0I3AKBUSEpKUteuXfXJJ59oz549On78uJYuXarp06erT58+kqT69esrOztb7777rn7//Xd9/PHHmjdvXpHV8NNPP2n69Ok6fPiw5syZo6VLl+qpp57Kd9mHHnpIlStXVp8+ffTjjz/q+PHj2rRpk8aOHas///zzmts5dOiQYmJi7P5cXV31zDPPaPz48Vq0aJGOHTumXbt26d1339WiRYsKvA9PPvmk5s+fr0WLFunIkSOaMmWK9uzZY3exsNq1a2vPnj06dOiQzpw5o+zs7AKv/0phYWHasmWL3Vjt2rV1/PhxxcTE6MyZM8rKylJoaKiaNWumhx56SLt27dIvv/yiIUOGqFOnTnYXYpMkd3d3ffvttypfvrx69uyptLQ0NWzYUA899JCGDBmi5cuX6/jx4/rll18UGRmpb7/9tkC1Hj9+XBMmTNDWrVv1xx9/aO3atTpy5Ijd/7Hy448/qm7dunanqAMAyjZCNwCgVPDw8FD79u319ttvq2PHjmratKleeukljRw5UrNnz5YktWjRQjNmzNDrr7+upk2b6tNPP1VkZGSR1fD0009r586datWqlaZMmaIZM2YoLCws32UrVqyozZs3q2bNmrrvvvvUpEkTjRgxQpmZmbZToq9m0KBBatWqld1ffHy8Xn31Vb300kuKjIxUkyZN1KNHD3377beqU6dOgffhoYce0oQJE/TMM8/o1ltv1fHjxzVs2DC7312PHDlSjRo1Ups2beTv76+ffvqpwOvPb3v79++3u21Z//791aNHD3Xp0kX+/v767LPPZLFY9NVXX8nX11cdO3ZUaGio6tatqyVLluS7Xg8PD33//fcyDEO9evVSenq6FixYoCFDhujpp59Wo0aN1LdvX+3YsUM1a9YsUK0VK1bUwYMH1b9/fzVs2FCjRo1SeHi4HnvsMdsyn332mUaOHHnDzwcAoPSxGH//oRIAALghtWvX1rhx4zRu3DhHl1LkunfvrsDAQH388cemrP/f//63UlJS9P7775uy/ptl//796tq1qw4fPnxDPxEAAJROXEgNAADYZGRkaN68eQoLC1O5cuX02Wefaf369Ve933hReOGFF/Tee+/JarXKyanknoR3+vRpffTRRwRuAIAdQjcAALCxWCz67rvvNHXqVGVmZqpRo0b68ssvFRoaato2fXx89Pzzz5u2/pvFzOcIAFBycXo5AAAAAAAmKbnncAEAAAAAUMwRugEAAAAAMAmhGwAAAAAAkxC6AQAAAAAwCaEbAAAAAACTELoBAAAAADAJoRsAAAAAAJMQugEAAAAAMAmhGwAAAAAAk/w/+Cv9xXHzOiwAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Min length: 3\n", + "Max length: 64\n", + "Mean length: 23.83\n", + "Median length: 16.00\n", + "Standard deviation: 14.60\n", + "Most common length: 16\n", + "Sum of percentages: 100.0%\n" + ] + }, + { + "data": { + "text/plain": [ + "(
,\n", + " )" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from sampling import any_order_mask_insertion_tau_leaping_sampling\n", + "\n", + "steps = 20000\n", + "batch_size = 2000\n", + "\n", + "samples, trace = any_order_mask_insertion_euler_sampling(\n", + " model,\n", + " steps=steps,\n", + " mask=0,\n", + " pad=3,\n", + " batch_size=batch_size,\n", + " max_length=64,\n", + " return_trace=False,\n", + ")\n", + "\n", + "\n", + "plot_length_distribution(samples, bin_size=5, figsize=(10, 6), color='blue', show_stats=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f244d9ca", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "tensor([ 8., 4., 9., 4., 4., 4., 10., 3., 10., 4., 11., 3., 4., 4.,\n", + " 4., 4., 2., 4., 3., 4., 10., 4., 3., 10., 4., 9., 4., 3.,\n", + " 4., 4., 4., 3., 4., 4., 4., 4., 3., 4., 4., 4., 3., 12.,\n", + " 9., 4., 4., 4., 8., 4., 3., 4., 2., 8., 3., 4., 4., 3.,\n", + " 3., 9., 3., 9., 3., 3., 8., 3., 3., 10., 4., 10., 4., 4.,\n", + " 4., 3., 2., 3., 9., 4., 4., 4., 7., 10., 3., 3., 9., 4.,\n", + " 3., 9., 11., 4., 4., 4., 4., 3., 3., 12., 4., 4., 4., 3.,\n", + " 8., 3.], device='cuda:0')" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "lengths = {4: 0.1, 16: 0.4, 32: 0.4, 64: 0.1}\n", + "\n", + "def binomial_mass(k, n, p):\n", + " \"\"\"\n", + " Calculate the probability mass function (PMF) for a binomial distribution.\n", + " \n", + " Args:\n", + " k (int): Number of successes\n", + " n (int): Number of trials\n", + " p (float): Probability of success in a single trial\n", + " \n", + " Returns:\n", + " float: Probability mass P(X = k)\n", + " \"\"\"\n", + " import math\n", + " \n", + " # Calculate binomial coefficient (n choose k)\n", + " try:\n", + " binom_coef = math.factorial(n) / (math.factorial(k) * math.factorial(n - k))\n", + " except ValueError:\n", + " # Handle cases where k > n or negative values\n", + " return 0.0\n", + " \n", + " # Calculate probability mass\n", + " return binom_coef * (p ** k) * ((1 - p) ** (n - k))\n", + "\n", + "def calculate_rate_batch(alpha_t, len_t):\n", + " \"\"\"\n", + " Calculate rate for a batch of alpha_t and len_t values.\n", + " \n", + " Args:\n", + " alpha_t (torch.Tensor): Tensor of shape (batch_size,)\n", + " len_t (torch.Tensor): Tensor of shape (batch_size,)\n", + " \n", + " Returns:\n", + " torch.Tensor: Tensor of shape (batch_size,) containing calculated rates\n", + " \"\"\"\n", + " batch_size = alpha_t.shape[0]\n", + " device = alpha_t.device\n", + " \n", + " # Initialize tensors for numerator and denominator\n", + " nom = torch.zeros(batch_size, device=device)\n", + " denom = torch.zeros(batch_size, device=device)\n", + " \n", + " for length, probability in lengths.items():\n", + " # Create mask for valid entries where len_t <= length\n", + " valid_mask = (len_t <= length) & (len_t >= 0)\n", + " \n", + " if not valid_mask.any():\n", + " continue\n", + " \n", + " valid_indices = valid_mask.nonzero(as_tuple=True)[0]\n", + " valid_len_t = len_t[valid_indices]\n", + " valid_alpha_t = alpha_t[valid_indices]\n", + " \n", + " # Calculate binomial probabilities efficiently using torch distribution\n", + " binom_dist = torch.distributions.Binomial(total_count=length, probs=valid_alpha_t)\n", + " binom_probs = binom_dist.log_prob(valid_len_t).exp()\n", + " \n", + " # Update numerator and denominator for valid indices\n", + " nom[valid_indices] += (length - valid_len_t) * probability * binom_probs\n", + " denom[valid_indices] += probability * binom_probs\n", + " \n", + " # Handle division by zero in a vectorized way\n", + " result = torch.zeros_like(nom)\n", + " div_mask = denom > 0\n", + " result[div_mask] = nom[div_mask] / (denom[div_mask])\n", + " \n", + " return result\n", + "\n", + "# Keep the original function for backward compatibility\n", + "def calculate_rate(alpha_t, len_t):\n", + " \"\"\"Legacy scalar version of calculate_rate\"\"\"\n", + " if isinstance(alpha_t, torch.Tensor) and alpha_t.ndim > 0:\n", + " return calculate_rate_batch(alpha_t, len_t)\n", + " \n", + " nom, denom = 0, 0\n", + " for length, probability in lengths.items():\n", + " if length >= len_t:\n", + " nom += (length - len_t) * probability * binomial_mass(len_t, length, alpha_t)\n", + " denom += probability * binomial_mass(len_t, length, alpha_t)\n", + " \n", + " if denom == 0:\n", + " return 0.0\n", + " \n", + " return nom / denom\n", + "\n", + "insertion_schedule = model.interpolant.insertion_schedule\n", + "\n", + "def sample(batch_size):\n", + " steps = 10000\n", + " dt = 1.0 / steps\n", + " t = torch.full((batch_size,), 0.0, device=model.device)\n", + " x = torch.full((batch_size,), 0, device=model.device) # Initialize with padding token (3)\n", + "\n", + " for i in range(steps):\n", + " rate = calculate_rate(insertion_schedule.at(t), x)\n", + " rate = rate * insertion_schedule.rate_scale_factor(t)\n", + " inc = torch.poisson(rate * dt).long()\n", + " x = x + inc\n", + "\n", + " t = t + dt\n", + "\n", + " return x\n", + "\n", + "sample(100)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "a46aee98", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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V/gF9XVA9pcQ+UVHm24nN9eli+OPpArfII1uMzqdY7d4A0yZop7h1unUSWD4O2WK42XZIzc3l48//pgZM2bQo0cPi3l9+vThs88+44EHHqBZs2asWrWKESNGWC0nODiYvLw8i7RKlSpx4sQJNE0zdq6Wf0it9evX884779C7d28AkpOTLTo5c1XLli356quvSExMJMjJq53169cnOTmZkydPEhsbC6gO4MwFBwcDFNgGQgghhCh+/n4zmBDFKT0dYmLg/HlvRyLcpQRdkvOOfv3gq6/UrR+hoerv119DcQ1D/f3333Pu3DlGjhxJkyZNLF79+/dn7ty5AEyaNInPPvuMSZMm8d9///H3339bXD1OTEzkt99+49ixY8ZGc5cuXTh16hSvvvoqBw4c4O233+ann36yqL9u3bp88skn/Pfff2zevJkhQ4bYf1Vd0yA7G/R60tPT2bFjh8UrOTmZ0aNHc/bsWQYPHswff/zBgQMH+PnnnxkxYgR5ly+r/EW44YYbqF27NsOGDeOvv/5i/fr1PPvsswDGHxMqV65M2bJljR21paen27cO9q5jdrb//xSpaXD8uHqZr4u1dE2DY8dg2zb1V6+3ntdWHceOQXKyun9q61aV350xe5Ij282eeY7U4Up8znK2vKLymc83P57sPbaKM3ZfYn4OHTvmnuPLXXGZfybYey64o15/36elikYVjlOF44DsLyHMueurqfAN0uh2g3791Fh6ly+rv8XV4AZ1a3n37t2Jjo4uMK9///78+eef/PXXX3Tp0oUvvviCb7/9lhYtWtCtWzdjj90AL7zwAocPH6Z27drGTsUaNmzIO++8w9tvv03z5s3ZsmUL48aNK1D/uXPnaNmyJXfffTePPvoolStXtn8FcnJAr2fNmjUkJSVZvKZMmULVqlVZv349eXl59OjRg6ZNmzJmzBhioqMJyMtT+YsQGBjIsmXLyMjI4JprruHee+819l4eerWLx6CgIN58803ef/99qlatym233Wb/OtizjiVhiDFNUw8UpaYW/MKcP93wRXfXLlPDyFpeW3UcPw4pKbBli3pew5VGtz31FidHtps98xypw5X4nOVseUXlM59vfjzZe2wVZ+y+xPwcOn7cPceXu+Iy/0yw91xwR73+vk9LER0aVUilCqnopNEthCjB5PZyP/Pdd9/ZnNemTRuLZ7L79etH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6Nq1KxdddBEPPPBA6TpLlizhtNNOo1GjRmRkZHDiiSfy448/lsvH4XDw73//m3POOYfatWvTuXNnFi5cyNq1aznppJOoU6cO/fv3Z926daVp7r33Xnr27Mm///1vsrOzqV27NsOHD6egoMBnvBUfL2/dujUPPvggV1xxBWlpabRs2ZIXXnihXJoFCxbQs2dPUlNT6dOnT+kTEcuWLaty39SuXZumTZvSpk0b7r33Xtq3b8+HH35YGse4ceO44YYbaNSoEYMGDQLgm2++oW/fvqSkpNCsWTNuv/12iouLS/Pcs2cPF198MXXq1KFZs2b861//8lqminUS4LvvvuP444+nVq1aZGdnM378ePbt21ea7tlnn6V9+/akpqaSmZnJ3/72t9Jl77zzDt26daNWrVo0bNiQgQMHlksrIiJihRrdIiIJICkpiQcffJCpU6eSn5/vdZ2lS5cyfPhwLrzwQlasWMG9997LPffcw/Tp08ut9+ijj9KjRw9ycnK45557ANi/fz9PPfUUM2fO5LPPPmPu3Lmcf/75fPrpp3z66ae88sor/Pvf/+add94pzefQoUPcf//9LF++nPfff5+NGzdy2WWXBVymJUuWsHnzZjZv3kx+fj7HHnssxx9/fGnegwYNIi0tjXnz5jF//nzq1q3LGWecUXon9LHHHmP69On897//5bvvvuOvv/7ivffeq3KbTZs2ZdGiRfz2228+19mzZw+jR4/mu+++Y9GiRbRv356zzjqLPXv2lFvP3VBctmwZnTp1YuTIkVx11VXccccd/PDDDxiGwbhx48qlWbt2LW+99RYfffQRn332GTk5OVxzzTUB7zN3ufv06VOa9uqrryY3NxeA3bt3c+6559KtWzd+/PFH7r//fm677TZL+bvVqlWr3F3nl19+meTkZObPn8/zzz/P77//zllnncXRRx/N8uXLee6555g2bRr//Oc/S9NMmDCB+fPn8+GHH/Lll18yb968Sn/AgMp1ct26dZxxxhkMGzaMn376iTfffJPvvvuudH/+8MMPjB8/nsmTJ5Obm8tnn33GCSecAMDmzZu56KKLuOKKK1i1ahVz585l6NChfv8gIyIi4pMhIiLV2ujRo43BgwcbhmEYxx57rHHFFVcYhmEY7733nuH538DIkSON0047rVzaW265xejSpUvp91atWhlDhgwpt85LL71kAMbatWtL51111VVG7dq1jT179pTOGzRokHHVVVf5jHPJkiUGUJpmzpw5BmDs3LmzdDsZGRle044fP95o1aqVsW3bNsMwDOOVV14xOnbsaLhcrtJ1CgsLjVq1ahmff/65YRiG0axZM+ORRx4pXX7o0CEjKyurdF9588cffxjHHnusARgdOnQwRo8ebbz55ptGSUmJzzQlJSVGWlqa8dFHH5XOA4y777679PvChQsNwJg2bVrpvDfeeMNITU0t/T5p0iQjKSnJyM/PL533v//9z3A6ncbmzZsNwyh/rA3DME488UTj+uuvL/3eqlUr45JLLin97nK5jCZNmhjPPfecYRiG8dxzzxkNGzY0Dhw4ULrOiy++aABGTk6OzzJ6bqe4uNh45ZVXDMB4+umnS5f36tWrXJo777yz0jF65plnjLp16xolJSXG7t27jZo1axpvv/126fJdu3YZtWvXrlSminVyzJgxxtixY8vNmzdvnuF0Oo0DBw4Y7777rpGenm7s3r27UlmWLl1qAMbGjRt9lldERMQK3ekWEUkgDz/8MC+//DKrVq2qtGzVqlUMGDCg3LwBAwbw66+/lnssvE+fPpXS1q5dm7Zt25Z+z8zMpHXr1tStW7fcPM/Hx5cuXcq5555Ly5YtSUtL48QTTwRg06ZNlsr0wgsvMG3aND788EMaN24MwPLly1m7di1paWnUrVuXunXr0qBBAw4ePMi6desoKChg8+bNHHPMMaX51KhRw2vZPDVr1oyFCxeyYsUKrr/+eoqLixk9ejRnnHEGLpcLgK1bt3LllVfSvn17MjIySE9PZ+/evZXK1b1793L7BqBbt27l5h08eJDdu3eXzmvZsiUtWrQo/d6vXz9cLlfpnepAeG7X4XDQtGnT0uOSm5tL9+7dSU1NLV2nb9++AeX77LPPUrduXWrVqsWVV17JjTfeyNVXX126vHfv3uXWX7VqFf369Sv3SsCAAQPYu3cv+fn5rF+/nkOHDpXbfkZGBh07dqy07YrHbfny5UyfPr302NetW5dBgwbhcrnYsGEDp512Gq1ataJNmzaMGjWK1157jf379wPQo0cPTj31VLp168YFF1zAiy++aHtfCCIikljU6BYRSSAnnHACgwYN4o477gg6jzp16lSaV7NmzXLfHQ6H13nuhum+ffsYNGgQ6enpvPbaayxZsqT00W4rHWHNmTOH6667jhkzZpRrTO7du5fevXuzbNmycp81a9YwcuTIgPP35cgjj+Saa67h1Vdf5csvv+TLL7/km2++AWD06NEsW7aMJ598kgULFrBs2TIaNmxYqVye+8fd8PQ2z73P7FLVcQnFxRdfzLJly9iwYQP79u3j8ccfx+ks+5nhrd7YpWLee/fu5aqrrip37JcvX86vv/5K27ZtSUtL48cff+SNN96gWbNmTJw4kR49erBr1y6SkpL48ssv+d///keXLl2YOnUqHTt2ZMOGDWGLX0REqjc1ukVEEsyUKVP46KOPWLhwYbn5nTt3Zv78+eXmzZ8/nw4dOpCUlGRrDKtXr+bPP/9kypQpHH/88XTq1KnKTtS8Wbt2LX/729+48847GTp0aLllRx11FL/++itNmjShXbt25T4ZGRlkZGTQrFkzvv/++9I0xcXFLF261HJZunTpAlDa0db8+fMZP348Z511VmmndDt27LCcrzebNm3ijz/+KP2+aNEinE6n17u/wejYsSMrVqygsLCwdN6SJUsCSpuRkUG7du1o0aJFuca2L+7O4wyPd6Xnz59PWloaWVlZtGnThpo1a5bbfkFBQUDDxh111FGsXLmy0rFv164dycnJgPlkw8CBA3nkkUf46aef2LhxI19//TVg/iFiwIAB3HfffeTk5JCcnOz3fX8RERFf1OgWEUkw3bp14+KLL+app54qN/+mm25i9uzZ3H///axZs4aXX36Zp59+mptvvtn2GFq2bElycjJTp05l/fr1fPjhh5bGkz5w4ADnnnsuvXr1YuzYsWzZsqX0A+Zd10aNGjF48GDmzZvHhg0bmDt3LuPHjy/tSO76669nypQpvP/++6xevZprrrmGXbt2Vbndq6++mvvvv5/58+fz22+/sWjRIi699FIaN25Mv379AGjfvj2vvPIKq1at4vvvv+fiiy+mVq1awe2oClJTUxk9ejTLly9n3rx5jB8/nuHDh9O0aVNb8h85ciQul4uxY8eyatUqPv/8cx599FEA28d0v+aaa8jLy+O6665j9erVfPDBB0yaNIkJEybgdDpJS0tj9OjR3HLLLcyZM4dffvmFMWPG4HQ6/cZy2223sWDBAsaNG8eyZcv49ddf+eCDD0o7Uvv444956qmnWLZsGb/99hszZszA5XLRsWNHvv/+ex588EF++OEHNm3axKxZs9i+fTudO3e2tfwiIpI41OgWEUlAkydPrvRI8VFHHcVbb73FzJkzOfLII5k4cSKTJ0+21KN4oBo3bsz06dN5++236dKlC1OmTClt3AVi69atrF69mtmzZ9O8eXOaNWtW+gHzHfNvv/2Wli1bMnToUDp37syYMWM4ePAg6enpgPlHhlGjRjF69Gj69etHWloa559/fpXbHThwIIsWLeKCCy6gQ4cODBs2jNTUVGbPnk3Dhg0BmDZtGjt37uSoo45i1KhRjB8/3rbxxdu1a8fQoUM566yzOP300+nevTvPPvusLXkDpKen89FHH7Fs2TJ69uzJXXfdxcSJEwHKvedthxYtWvDpp5+yePFievTowT/+8Q/GjBnD3XffXbrO448/Tr9+/TjnnHMYOHAgAwYMoHPnzn5j6d69O9988w1r1qzh+OOPp1evXkycOJHmzZsDUK9ePWbNmsUpp5xC586def7553njjTfo2rUr6enpfPvtt5x11ll06NCBu+++m8cee4wzzzzT1vKLiEjicBiGxsAQERGJdffeey/vv/++3/Gy7fbaa69x+eWXU1BQYNsd+2Dt27ePFi1a8NhjjzFmzJioxiIiIhKoGtEOQERERGLHjBkzaNOmDS1atGD58uXcdtttDB8+PCoN7pycHFavXk3fvn0pKChg8uTJAAwePDjisYiIiARLjW4REREptWXLFiZOnMiWLVto1qwZF1xwAQ888EDU4nn00UfJzc0lOTmZ3r17M2/ePBo1ahS1eERERKzS4+UiIiIiIiIiYaKO1ERERERERETCRI1uERERERERkTBRo1tEREREREQkTNToFhEREREREQkTNbpFREREREREwkSNbhEREREREZEwUaNbREREREREJEzU6BYREREREREJEzW6RURERERERMLk/wH0+Gbd6XUwZAAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Initial length: 0\n", + "Final length: 32\n", + "Number of length changes: 32\n", + "Final cumulative invalid count: 0\n" + ] + } + ], + "source": [ + "sample_id = 100\n", + "lengths = plot_length_evolution(trace, sample_id, normalize_x=True, model=model, final_length=None, valid=valid)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8058f57f", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA0kAAAIjCAYAAADWYVDIAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjMsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvZiW1igAAAAlwSFlzAAAPYQAAD2EBqD+naQAAaKJJREFUeJzt3Xd0FHX7/vFrQ0ivQAol9A4SEQQjipRgQERUHinCA/ggKgaVYsMGKAiiIqAIVooKIqjY6RAFAWkRRERAEBSS0JJsEhIgO78/+LK/WRIgCUk2u7xf5+TofGZ25p65Z/fsxc7OWgzDMAQAAAAAkCR5OLsAAAAAAChLCEkAAAAAYEJIAgAAAAATQhIAAAAAmBCSAAAAAMCEkAQAAAAAJoQkAAAAADAhJAEAAACACSEJAAAAAEwISQCK1ZgxY2SxWEplW+3atVO7du3s02vWrJHFYtGiRYtKZfsDBw5UzZo1S2VbRZWRkaH7779fkZGRslgsGjZsmLNLKhGled6Vde3atVPTpk1LfDu//PKLvLy89Pfff5f4tq7E7NmzZbFYdODAgSI9/sCBA7JYLHrttdcuu2xJn4e9e/dWz549S2z9AP4/QhKAizr/5uL8n4+Pj6pUqaK4uDhNmzZNVqu1WLZz+PBhjRkzRomJicWyvuJUlmsriJdfflmzZ8/WkCFD9NFHH+m///3vRZc9ffq0pk6dqubNmysoKEghISFq0qSJHnjgAf3xxx+lWLXznT/3N2/e7OxS8lUWzstnn31Wffr0UY0aNexjAwcOdHjNOP/XsGFDp9WZn7fffluzZ892dhmF9tRTT+nzzz/Xr7/+6uxSALfn6ewCAJR9L774omrVqqUzZ84oKSlJa9as0bBhwzR58mR9/fXXatasmX3Z5557Tk8//XSh1n/48GGNHTtWNWvW1LXXXlvgxy1btqxQ2ymKS9X23nvvyWazlXgNV2LVqlW64YYbNHr06Msu26NHD/3www/q06ePBg8erDNnzuiPP/7Qt99+qxtvvLHMvdG9mhX1OVNcEhMTtWLFCv3888955nl7e+v99993GAsODi6t0vL473//q969e8vb29s+9vbbb6tSpUoaOHBgsW6rKK9/hdG8eXO1bNlSr7/+uubOnVti2wFASAJQAF26dFHLli3t06NGjdKqVat0++2364477tCuXbvk6+srSfL09JSnZ8m+tGRlZcnPz09eXl4lup3LKV++vFO3XxApKSlq3LjxZZfbtGmTvv32W40fP17PPPOMw7y33npLqampJVQhXNGsWbNUvXp13XDDDXnmeXp6ql+/fk6oKn/lypVTuXLlSmVbpfH617NnT40ePVpvv/22AgICSnRbwNWMy+0AFEmHDh30/PPP6++//9bHH39sH8/vmvzly5frpptuUkhIiAICAtSgQQP7G/E1a9bo+uuvlyTdd9999stzzl8Kc/77FVu2bFHbtm3l5+dnf+yF30k6Lzc3V88884wiIyPl7++vO+64Q4cOHXJYpmbNmvn+K7J5nZerLb/vJGVmZmrkyJGKioqSt7e3GjRooNdee02GYTgsZ7FYNHToUC1evFhNmzaVt7e3mjRpoiVLluR/wC+QkpKiQYMGKSIiQj4+PoqOjtacOXPs889/P2v//v367rvv7LVf7HsZ+/btkyS1adMmz7xy5cqpYsWK9um///5bDz/8sBo0aCBfX19VrFhR99xzT551n79kbe3atXr00UcVFhamkJAQPfjggzp9+rRSU1PVv39/hYaGKjQ0VE8++aTDcTJ/F+SNN95QjRo15Ovrq1tuuUW//fZbgY7Txx9/rBYtWsjX11cVKlRQ796985wLV+Lff//V//73P0VERNh7+OGHHzosc74Xn332mcaPH69q1arJx8dHHTt21N69e/Osc/r06apdu7Z8fX3VqlUr/fTTT4U6L8/7/fff1b59e/n5+alq1aqaNGlSnm29+eabatKkifz8/BQaGqqWLVtq3rx5l93vxYsXq0OHDhf9/k1ubq7S09Mvu54LXex7fvm9rhT0OXThd5Jq1qypnTt3KiEhwX7s8nsdyc/lzsMrqdNqtWrYsGGqWbOmvL29FR4erk6dOmnr1q0Oy3Xq1EmZmZlavnx5gWoGUDR8kgSgyP773//qmWee0bJlyzR48OB8l9m5c6duv/12NWvWTC+++KK8vb21d+9erVu3TpLUqFEjvfjii3rhhRf0wAMP6Oabb5Yk3XjjjfZ1HD9+XF26dFHv3r3Vr18/RUREXLKu8ePHy2Kx6KmnnlJKSoqmTJmi2NhYJSYm2j/xKoiC1GZmGIbuuOMOrV69WoMGDdK1116rpUuX6oknntC///6rN954w2H5tWvX6osvvtDDDz+swMBATZs2TT169NDBgwcd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ynl4XkHqnDRpkn777TdFRUWpVatWGjNmzEXDnmEY/C4YUML4ThKAIvvnn3+UlpaW502dma+vr3788UetXr1a3333nZYsWaIFCxaoQ4cOWrZsWYHuOlWY7xEV1KW+cF5ad8K62HYK+uazJFWuXFm9e/dWjx491KRJE3322WeaPXu2PD099cgjj2jWrFkaNmyYYmJiFBwcLIvFot69e+cbfi+2n/mNF9e+22w2WSwW/fDDD/lu50q/H3N+P/v166cBAwbku4z51vhS6fa7INtq1KiRdu/erW+//VZLlizR559/rrffflsvvPCCxo4de9F1n/8OTUGDyPmbe5w4cUKSdOjQIdWqVcthmdWrV6tdu3aXfF7mpyw/h8wKUmfPnj11880368svv9SyZcv06quv6pVXXtEXX3xh/x7ZeSdPnsw3tAEoPoQkAEX20UcfSZLi4uIuuZyHh4c6duyojh07avLkyXr55Zf17LPPavXq1YqNjS32fxHds2ePw7RhGNq7d6/Dm9bQ0NB8b2v9999/q3bt2vbpwtRWo0YNrVixQlar1eHTpPM/xGr+0c0rUaNGDW3fvl02m83h06Ti3o507jK+Zs2aac+ePfbL1RYtWqQBAwbo9ddfty+XnZ1dYrcJv7CfkvTnn39e8tOVOnXqyDAM1apVS/Xr1y/2msLCwhQYGKjc3FzFxsYWyzrP923v3r1q3769ffzs2bM6cOCAw/lbXM8Zf39/9erVS7169dLp06d19913a/z48Ro1apR8fHzyfcz538vav39/gbZhtVp17Ngx+01DIiMj89yZLTo6WtKln5fFqSjHryjnYWFVrlxZDz/8sB5++GGlpKTouuuu0/jx4x1C0tmzZ3Xo0CHdcccdxbZdAHlxuR2AIlm1apVeeukl1apVS3379r3ocuf/9djs/I9f5uTkSDr3Rk1Ssb3JPn8XqvMWLVqkI0eOOLzRqFOnjjZs2OBwmc+3336b5/bQhanttttuU25urt566y2H8TfeeEMWiyXPvwYX1W233aakpCQtWLDAPnb27Fm9+eabCggI0C233FLode7Zs0cHDx7MM56amqr169crNDTU/ia3XLlyef6l/s0337zov/ZfqcWLF+vff/+1T//yyy/auHHjJY/n3XffrXLlymns2LF5ajUMw/69mqIqV66cevTooc8//zzf25EfPXq00Ots2bKlKlasqPfee09nz561j3/yySd5PrUpjufMhcfAy8tLjRs3lmEYOnPmzEUfV7VqVUVFRWnz5s0O49nZ2fl+P/Gll16SYRjq3LmzJMnHx0exsbEOf+cvPaxTp47S0tK0fft2++OPHDmiL7/8ssj7mR9/f/9CH7uinIcFlZubm+dS1fDwcFWpUsX+Onne77//ruzs7IveZRNA8eCTJACX9cMPP+iPP/7Q2bNnlZycrFWrVmn58uWqUaOGvv7664v+i7Mkvfjii/rxxx/VtWtX1ahRQykpKXr77bdVrVo13XTTTZLOvTEKCQnRzJkzFRgYKH9/f7Vu3TrPJTkFVaFCBd1000267777lJycrClTpqhu3boOtym///77tWjRInXu3Fk9e/bUvn379PHHHzt8ubqwtXXr1k3t27fXs88+qwMHDig6OlrLli3TV199pWHDhuVZd1E98MADeueddzRw4EBt2bJFNWvW1KJFi7Ru3TpNmTLlkt8Ru5hff/1V9957r7p06aKbb75ZFSpU0L///qs5c+bo8OHDmjJliv2Sodtvv10fffSRgoOD1bhxY61fv14rVqy45G3Lr0TdunV10003aciQIcrJydGUKVNUsWJFPfnkkxd9TJ06dTRu3DiNGjXKfhvtwMBA7d+/X19++aUeeOABPf7445fd9ocffpjvb1c99thjmjhxolavXq3WrVtr8ODBaty4sU6cOKGtW7dqxYoV+f4DwaV4eXlpzJgxeuSRR9ShQwf17NlTBw4c0OzZs1WnTh2HTz+K4zlz6623KjIyUm3atFFERIR27dqlt956S127dr3sOdS9e3d9+eWXDt+NSUpKUvPmzdWnTx/7p01Lly7V999/r86dO6t79+6Xral379566qmndNddd+nRRx9VVlaWZsyYofr16+d7U5CiatGihWbMmKFx48apbt26Cg8PV4cOHS75mKKchwVltVpVrVo1/ec//1F0dLQCAgK0YsUKbdq0yeETW+nc7875+fmpU6dOV7xdAJdQqvfSA+BSzt869/yfl5eXERkZaXTq1MmYOnWqw62mz7vwFrgrV640unfvblSpUsXw8vIyqlSpYvTp08f4888/HR731VdfGY0bNzY8PT0dbm18yy23XPQ2xRe7Bfj8+fONUaNGGeHh4Yavr6/RtWvXfG9l/frrrxtVq1Y1vL29jTZt2hib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zMxMRUdHa/r06RddpnPnzjpy5Ij9b/78+Zdc54IFCzRixAiNHj1aW7duVXR0tOLi4pSSkiJJ+uabbzRv3jwtW7ZMkyZN0v33369jx45JktLS0vTss89esh4AAADAnRGSnKxLly4aN26c7rrrrosu4+3trcjISPtfaGjoJdc5efJkDR48WPfdd58aN26smTNnys/PTx9++KEkadeuXWrXrp1atmypPn36KCgoSPv375ckPfnkkxoyZIiqV69efDsJAAAAuBBCkgtYs2aNwsPD1aBBAw0ZMkTHjx+/6LKnT5/Wli1bFBsbax/z8PBQbGys1q9fL0mKjo7W5s2bdfLkSW3ZskWnTp1S3bp1tXbtWm3dulWPPvpoie8TAAAAUFYRksq4zp07a+7cuVq5cqVeeeUVJSQkqEuXLsrNzc13+WPHjik3N1cREREO4xEREUpKSpIkxcXFqV+/frr++us1cOBAzZkzR/7+/hoyZIhmzpypGTNmqEGDBmrTpo127txZ4vsIAAAAlCUFvrsdnKN37972/7/mmmvUrFkz1alTR2vWrFHHjh2LvN4xY8ZozJgx9umxY8cqNjZW5cuX17hx47Rjxw59++236t+/v7Zs2XIluwAAAAC4FD5JcjG1a9dWpUqVtHfv3nznV6pUSeXKlVNycrLDeHJysiIjI/N9zB9//KGPP/5YL730ktasWaO2bdsqLCxMPXv21NatW2W1Wot9PwAAAICyipDkYv755x8dP35clStXzne+l5eXWrRooZUrV9rHbDabVq5cqZiYmDzLG4ahBx98UJMnT1ZAQIByc3N15swZSbL/92KX9gEAAADuiJDkZBkZGUpMTFRiYqIkaf/+/UpMTNTBgweVkZGhJ554Qhs2bNCBAwe0cuVKde/eXXXr1lVcXJx9HR07dtRbb71lnx4xYoTee+89zZkzR7t27dKQIUOUmZmp++67L8/233//fYWFhdl/F6lNmzZatWqVNmzYoDfeeEONGzdWSEhIiR4DAAAAoCzhO0lOtnnzZrVv394+PWLECEnSgAEDNGPGDG3fvl1z5sxRamqqqlSpoltvvVUvvfSSvL297Y/Zt2+f/XeOJKlXr146evSoXnjhBSUlJenaa6/VkiVL8tzMITk5WePHj9fPP/9sH2vVqpVGjhyprl27Kjw8XHPmzCmpXQcAAADKJEKSk7Vr106G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vrkeelZyfHx8CrRcmQ5JFwoJCVH9+vW1d+/eiy7j7e0tb2/vPOMeHh5OO8ksFos8PDzsl2EYhkWG4Zon/LnaDfs+ubPz++iu++mO56O798wd8froenieuSb65nroWcko6PF0qaOekZGhffv2qXLlys4uBQAAAICbKtMh6fHHH1dCQoIOHDign3/+WXfddZfKlSunPn36OLs0AAAAAG6qTF9u988//6hPnz46fvy4wsLCdNNNN2nDhg0KCwtzdmkAAAAA3FSZDkmffvqps0sAAAAAcJUp05fbAQAAAEBpIyQBAAAAgAkhCQAAAABMCEkAAAAAYEJIAgAAAAATQhIAAAAAmBCSAAAAAMCEkAQAAAAAJoQkAAAAADAhJAEAAACACSEJAAAAAEwISQAAAABgQkgCAAAAABNCEgAAAACYEJIAAAAAwISQBAAAAAAmhCSgGPz444/q1q2bqlSpIovFosWLF19y+TVr1shiseT5S0pKsi/zySefKCoqSqGhoRoxYoTD4w8cOKD69esrPT29JHYHAAC4EN6HFD9CElAMMjMzFR0drenTpxfqcbt379aRI0fsf+Hh4ZKkY8eO6f7779drr72mZcuW6eOPP9a3335rf9zDDz+siRMnKigoqFj3AwAAuB7ehxQ/T2cXALiDLl26qEuXLoV+XHh4uEJCQvKM//XXXwoODlavXr0kSe3bt9euXbt0++23a/78+SpfvrzuvvvuKy0bAAC4Ad6HFD8+SQKc6Nprr1XlypXVqVMnrVu3zj5er149ZWVladu2bTpx4oQ2bdqkZs2a6eTJk3r++ef11ltvObFqAADgDngfcnGEJMAJKleurJkzZ+rzzz/X559/rqioKLVr105bt26VJIWGhmrOnDnq37+/WrVqpf79+ysuLk6PP/64hg4dqv3796t58+Zq2rSpFi1a5OS9AQAAroT3IZfH5XaAEzRo0EANGjSwT994443at2+f3njjDX300UeSpLvuukt33XWXfZmEhARt375db775purWrav58+crMjJSrVq1Utu2be3XEQMAAFwK70Muj0+SgDKiVatW2rt3b77zcnJy9PDDD+udd97R3r17dfbsWd1yyy1q0KCB6tevr40bN5ZytQAAwJ3wPsQRIQkoIxITE1W5cuV8540bN06dO3fWddddp9zcXJ09e9Y+78yZM8rNzS2tMgEAgBvifYgjLrcDikFGRobDv77s379fiYmJqlChgqpXr65Ro0bp33//1dy5cyVJU6ZMUa1atdSkSRNlZ2fr/fff16pVq7Rs2bI86/7999+1YMECbdu2TZLUsGFDeXh46IMPPlBkZKT++OMPXX/99aWzowAAoMzhfUjxIyQBxWDz5s1q3769ffr8j64NGDBAs2fP1pEjR3Tw4EH7/NOnT2vkyJH6999/5efnp2bNmmnFihUO65AkwzD0wAMPaPLkyfL395ck+fr6avbs2YqPj1dOTo7eeustVa1atRT2EgAAlEW8Dyl+hCSgGLRr106GYVx0/uzZsx2mn3zyST355JOXXa/FYtHatWvzjN9+++26/fbbC10nAABwP7wPKX58JwkAAAAATAhJAAAAAGBCSAIAAAAAE76TBFzG0aNHlZ6e7uwyrlhQUJDCwsKcXQYAACgkd3gv4mrvQwhJwCUcO3ZMffs+rOPHc5xdyhWrWNFb8+bNcKkXKAAArnZHjx7VvfcOcfn3Iq72PoSQBFxCenq6jh/Pkbf3SPn6Rjm7nCI7deqQjh9/Xenp6S7z4gQAANzjvYgrvg8hJAEF4OsbJX//Os4u44rkuPY/QAEAcFVz9fcirvY+hBs3AAAAAIAJIQkAAAAATAhJAAAAAGBCSAIAAAAAE0ISAAAAAJgQkgAAAADAhJAEAAAAACaEJAAAAAAwISQBAAAAgIlLhKTp06erZs2a8vHxUevWrfXLL784uyQAAAAAbqrMh6QFCxZoxIgRGj16tLZu3aro6GjFxcUpJSXF2aUBAAAAcENlPiRNnjxZgwcP1n333afGjRtr5syZ8vPz04cffujs0gAAAAC4IU9nF3App0+f1pYtWzRq1Cj7mIeHh2JjY7V+/fp8H5OTk6OcnBz7dFpamiQpNTVVNputZAvOh81mU3p6ury8vJSeni6b7awyMnYpNze91GspDqdO/Sub7azS09OVmprqMC81NVUnT550TmHFJDQ0VCEhIfa+ZWZmunzPpPz75o7n4/nnmoeHh1udj2butF9Xy+ujO/RMOte3oKCgq+J5JrlH3y72XHOX/bqQO/fMXV8fnSE9/dwxNAzjkstZjMst4USHDx9W1apV9fPPPysmJsY+/uSTTyohIUEbN27M85gxY8Zo7NixpVkmAAAAABdy6NAhVatW7aLzy/QnSUUxatQojRgxwj5ts9l04sQJVaxYURaLpdTrSU9PV1RUlA4dOqSgoKBS3z6Khr65HnrmeuiZ66Fnrom+uR56VnIMw5DValWVKlUuuVyZDkmVKlVSuXLllJyc7DCenJysyMjIfB/j7e0tb29vh7H8PpItbUFBQZzkLoi+uR565nromeuhZ66JvrkeelYygoODL7tMmb5xg5eXl1q0aKGVK1fax2w2m1auXOlw+R0AAAAAFJcy/UmSJI0YMUIDBgxQy5Yt1apVK02ZMkWZmZm67777nF0aAAAAADdU5kNSr169dPToUb3wwgtKSkrStddeqyVLligiIsLZpRWIt7e3Ro8enecSQJRt9M310DPXQ89cDz1zTfTN9dAz5yvTd7cDAAAAgNJWpr+TBAAAAACljZAEAAAAACaEJAAAAAAwISQBAAAAgAkhqYRNnz5dNWvWlI+Pj1q3bq1ffvnF2SXh//z444/q1q2bqlSpIovFosWLFzvMNwxDL7zwgipXrixfX1/FxsZqz549zikWkqQJEybo+uuvV2BgoMLDw3XnnXdq9+7dDstkZ2crPj5eFStWVEBAgHr06JHnB6lRembMmKFmzZrZfxAxJiZGP/zwg30+/Sr7Jk6cKIvFomHDhtnH6FvZM2bMGFksFoe/hg0b2ufTs7Lp33//Vb9+/VSxYkX5+vrqmmuu0ebNm+3zeS/iPISkErRgwQKNGDFCo0eP1tatWxUdHa24uDilpKQ4uzRIyszMVHR0tKZPn57v/EmTJmnatGmaOXOmNm7cKH9/f8XFxSk7O7uUK8V5CQkJio+P14YNG7R8+XKdOXNGt956qzIzM+3LDB8+XN98840WLlyohIQEHT58WHfffbcTq766VatWTRMnTtSWLVu0efNmdejQQd27d9fOnTsl0a+ybtOmTXrnnXfUrFkzh3H6VjY1adJER44csf+tXbvWPo+elT0nT55UmzZtVL58ef3www/6/fff9frrrys0NNS+DO9FnMhAiWnVqpURHx9vn87NzTWqVKliTJgwwYlVIT+SjC+//NI+bbPZjMjISOPVV1+1j6Wmphre3t7G/PnznVAh8pOSkmJIMhISEgzDONej8uXLGwsXLrQvs2vXLkOSsX79emeViQuEhoYa77//Pv0q46xWq1GvXj1j+fLlxi233GI89thjhmHwPCurRo8ebURHR+c7j56VTU899ZRx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MAzDOH78uCHJWLNmzUX3xzAMIzQ01Jg9e/YllwEAd8AnSQDgAnr06KHDhw/r66+/VufOnbVmzRpdd9119su1JGnFihXq2LGjqlatqsDAQP33v//V8ePHlZWVZV/Gz89PderUsU9HRESoZs2aCggIcBhLSUlx2H5MTEye6V27duVb66+//qq9e/cqMDDQ/ilYhQoVlJ2drX379hV63/fu3ausrCx16tTJvr6AgADNnTs3z/qaNWtm///KlStLkn1fdu/erVatWjksf+H0pVxq3fk5deqU/VK7S9m1a5eioqIUFRVlH2vcuLFCQkIcjvHBgwfVqVMnvfDCCxo5cqR9fMeOHcrNzVX9+vUdjk9CQoLD8bmw95UrV7bXX6FCBQ0cOFBxcXHq1q2bpk6d6nAp3nm+vr4O5xMAuCtu3AAALsLHx0edOnVSp06d9Pzzz+v+++/X6NGjNXDgQB04cEC33367hgwZovHjx6tChQpau3atBg0apNOnT8vPz0+SVL58eYd1WiyWfMdsNluR68zIyFCLFi30ySef5JkXFhZWpPVJ0nfffaeqVas6zPP29naYNu/L+UvFrmRfrmTdlSpV0smTJ4tl29K5Y1elShXNnz9f//vf/xQUFCTp3PEpV66ctmzZYr+L3nnm8Jtfnw3DsE/PmjVLjz76qJYsWaIFCxboueee0/Lly3XDDTfYlzlx4kSReggAroZPkgDARTVu3FiZmZmSpC1btshms+n111/XDTfcoPr16+vw4cPFtq0NGzbkmW7UqFG+y1533XXas2ePwsPDVbduXYe/4ODgQm+7cePG8vb21sGDB/Osz/zpy+U0aNBAmzZtchi7cNrLy+uKf9PovObNm+v333+/7PobNWqkQ4cO6dChQ/ax33//XampqWrcuLF9zNfXV99++618fHwUFxcnq9Vq305ubq5SUlLyHJ/IyMhC1zxq1Cj9/PPPatq0qebNm2eft2/fPmVnZ6t58+aFWicAuCJCEgCUccePH1eHDh308ccfa/v27dq/f78WLlyoSZMmqXv37pKkunXr6syZM3rzzTf1119/6aOPPtLMmTOLrYZ169Zp0qRJ+vPPPzV9+nQtXLhQjz32WL7L9u3bV5UqVVL37t31008/af/+/VqzZo0effRR/fPPP5fczu7du5WYmOjw5+Pjo8cff1zDhw/XnDlztG/fPm3dulVvvvmm5syZU+B9eOSRR/TBBx9ozpw52rNnj8aNG6ft27c73JygZs2a2r59u3bv3q1jx47pzJkzBV7/heLi4rR27VqHsZo1a2r//v1KTEzUsWPHlJOTo9jYWF1zzTXq27evtm7dql9++UX9+/fXLbfc4nDjB0ny9/fXd999J09PT3Xp0kUZGRmqX7+++vbtq/79++uLL77Q/v379csvv2jChAn67rvvClTr/v37NWrUKK1fv15///23li1bpj179jgE4Z9++km1a9d2uGQPANwVIQkAyriAgAC1bt1ab7zxhtq2baumTZvq+eef1+DBg/XWW29JkqKjozV58mS98soratq0qT755BNNmDCh2GoYOXKkNm/erObNm2vcuHGaPHmy4uLi8l3Wz89PP/74o6pXr667775bjRo10qBBg5SdnW2/ROxievfurebNmzv8JScn66WXXtLzzz+vCRMmqFGjRurcubO+++471apVq8D70LdvX40aNUqPP/64rrvuOu3fv18DBw50+N7Q4MGD1aBBA7Vs2VJhYWFX9GO9ffv21c6dOx1uQ96jRw917txZ7du3V1hYmObPny+LxaKvvvpKoaGhatu2rWJjY1W7dm0tWLAg3/UGBATohx9+kGEY6tq1qzIzMzVr1iz1799fI0eOVIMGDXTnnXdq06ZNql69eoFq9fPz0x9//KEePXqofv36euCBBxQfH68HH3zQvsz8+fM1ePDgIh8PAHAlFsN8QTIAABeoWbOmhg0bpmHDhjm7lGLXqVMnRUZG6qOPPiqR9T/xxBNKT0/XO++8UyLrLy07d+5Uhw4d9OeffxbpkkkAcDXcuAEAcFXIysrSzJkzFRcXp3Llymn+/PlasWLFRX/vqTg8++yzevvtt2Wz2eTh4boXbxw5ckRz584lIAG4ahCSAABXBYvFou+//17jx49Xdna2GjRooM8//1yxsbElts2QkBA988wzJbb+0lKSxwgAyiIutwMAAAAAE9f97B8AAAAASgAhCQAAAABMCEkAAAAAYEJIAgAAAAATQhIAAAAAmBCSAAAAAMCEkAQAAAAAJoQkAAAAADD5f4IbY54goU/bAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Min length: 3\n", + "Max length: 64\n", + "Mean length: 21.45\n", + "Median length: 16.00\n", + "Sum of percentages: 100.0%\n" + ] + } + ], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "03650899", + "metadata": {}, + "outputs": [ + { + "ename": "ImportError", + "evalue": "cannot import name 'get' from 'data' (/n/netscratch/albergo_lab/Lab/brianlck/interpretable-flow/data/__init__.py)", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mImportError\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[49]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mdata\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m get\n", + "\u001b[31mImportError\u001b[39m: cannot import name 'get' from 'data' (/n/netscratch/albergo_lab/Lab/brianlck/interpretable-flow/data/__init__.py)" + ] + } + ], + "source": [ + "from data import get" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "01564892", + "metadata": {}, + "outputs": [], + "source": [ + "def process_trace(trace: list[SamplingTraceDatapoint]):\n", + " event_type_mapping = dict(change=\"c\", insertion=\"i\")\n", + " token_mapping = {0: \"m\", 1: \"(\", 2: \")\"}\n", + "\n", + " def _process_datapoint(datapoint: SamplingTraceDatapoint):\n", + " return dict(\n", + " t=datapoint.t,\n", + " a=event_type_mapping[datapoint.event_type],\n", + " tk=token_mapping[datapoint.token],\n", + " i=datapoint.position,\n", + " )\n", + "\n", + " return [_process_datapoint(datapoint) for datapoint in trace]" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "63efdf7b", + "metadata": {}, + "outputs": [ + { + "ename": "KeyError", + "evalue": "3", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mKeyError\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[34]\u001b[39m\u001b[32m, line 8\u001b[39m\n\u001b[32m 5\u001b[39m \u001b[38;5;28mid\u001b[39m = \u001b[32m19\u001b[39m\n\u001b[32m 6\u001b[39m \u001b[38;5;28;01massert\u001b[39;00m \u001b[32m0\u001b[39m <= \u001b[38;5;28mid\u001b[39m < batch_size, \u001b[33m\"\u001b[39m\u001b[33mid must be in [0, batch_size)\u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m8\u001b[39m events = \u001b[43mprocess_trace\u001b[49m\u001b[43m(\u001b[49m\u001b[43mtrace\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;28;43mid\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 9\u001b[39m \u001b[38;5;28mprint\u001b[39m(events)\n\u001b[32m 11\u001b[39m display(\n\u001b[32m 12\u001b[39m HTML(\u001b[33mf\u001b[39m\u001b[33m\"\"\"\u001b[39m\n\u001b[32m 13\u001b[39m \u001b[33m\n", + "
\n", + "\n", + "\n", + "\"\"\")\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7d5210d9", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "interpretable-flow", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/FlexMDM/notebook/sample_semiauto_bracket.ipynb b/FlexMDM/notebook/sample_semiauto_bracket.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..36a7c577e03bfbeb4b0554aee041441bbd76fa8b --- /dev/null +++ b/FlexMDM/notebook/sample_semiauto_bracket.ipynb @@ -0,0 +1,216 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from train_semi_auto import BracketFlowModule\n", + "from sampling import semiauto_euler_sampling, SamplingTraceDatapoint" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "checkpoint_path = \"checkpoints/bracket-flow/last-v48.ckpt\"\n", + "model = BracketFlowModule.load_from_checkpoint(checkpoint_path)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/n/netscratch/albergo_lab/Lab/brianlck/interpretable-flow/model/transformer.py:36: FutureWarning: `torch.cuda.amp.autocast(args...)` is deprecated. Please use `torch.amp.autocast('cuda', args...)` instead.\n", + " with torch.cuda.amp.autocast(enabled=False):\n", + "/n/netscratch/albergo_lab/Lab/brianlck/interpretable-flow/model/transformer.py:167: FutureWarning: `torch.cuda.amp.autocast(args...)` is deprecated. Please use `torch.amp.autocast('cuda', args...)` instead.\n", + " with torch.cuda.amp.autocast(enabled=False):\n" + ] + } + ], + "source": [ + "steps = 2000\n", + "batch_size = 10\n", + "samples, trace = semiauto_euler_sampling(\n", + " model,\n", + " model.interpolant,\n", + " steps=steps,\n", + " mask=0,\n", + " pad=3,\n", + " batch_size=10,\n", + " max_length=64,\n", + " return_trace=True,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "def process_trace(trace: list[SamplingTraceDatapoint]):\n", + " event_type_mapping = dict(change=\"c\", insertion=\"i\")\n", + " token_mapping = {0: \"m\", 1: \"(\", 2: \")\"}\n", + "\n", + " def _process_datapoint(datapoint: SamplingTraceDatapoint):\n", + " return dict(\n", + " t=datapoint.t,\n", + " a=event_type_mapping[datapoint.event_type],\n", + " tk=token_mapping[datapoint.token],\n", + " i=datapoint.position,\n", + " )\n", + "\n", + " return [_process_datapoint(datapoint) for datapoint in trace]" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[{'t': 0.09800010919570923, 'a': 'i', 'tk': 'm', 'i': 0}, {'t': 0.13750000298023224, 'a': 'i', 'tk': 'm', 'i': 1}, {'t': 0.13999997079372406, 'a': 'i', 'tk': 'm', 'i': 2}, {'t': 0.1689995974302292, 'a': 'i', 'tk': 'm', 'i': 3}, {'t': 0.2329987734556198, 'a': 'i', 'tk': 'm', 'i': 4}, {'t': 0.2429986447095871, 'a': 'i', 'tk': 'm', 'i': 5}, {'t': 0.2569984793663025, 'a': 'c', 'tk': '(', 'i': 1}, {'t': 0.26299840211868286, 'a': 'i', 'tk': 'm', 'i': 6}, {'t': 0.2679983377456665, 'a': 'i', 'tk': 'm', 'i': 7}, {'t': 0.2779982089996338, 'a': 'i', 'tk': 'm', 'i': 8}, {'t': 0.29399800300598145, 'a': 'c', 'tk': '(', 'i': 2}, {'t': 0.29649797081947327, 'a': 'i', 'tk': 'm', 'i': 9}, {'t': 0.319497674703598, 'a': 'i', 'tk': 'm', 'i': 10}, {'t': 0.3339974880218506, 'a': 'i', 'tk': 'm', 'i': 11}, {'t': 0.34299737215042114, 'a': 'i', 'tk': 'm', 'i': 12}, {'t': 0.3479973077774048, 'a': 'i', 'tk': 'm', 'i': 13}, {'t': 0.35149726271629333, 'a': 'i', 'tk': 'm', 'i': 14}, {'t': 0.35549721121788025, 'a': 'i', 'tk': 'm', 'i': 15}, {'t': 0.36499708890914917, 'a': 'i', 'tk': 'm', 'i': 16}, {'t': 0.36949703097343445, 'a': 'i', 'tk': 'm', 'i': 17}, {'t': 0.3774969279766083, 'a': 'i', 'tk': 'm', 'i': 18}, {'t': 0.37799692153930664, 'a': 'i', 'tk': 'm', 'i': 19}, {'t': 0.4369961619377136, 'a': 'c', 'tk': '(', 'i': 8}, {'t': 0.44799602031707764, 'a': 'c', 'tk': '(', 'i': 15}, {'t': 0.4524959623813629, 'a': 'c', 'tk': '(', 'i': 3}, {'t': 0.5064956545829773, 'a': 'i', 'tk': 'm', 'i': 20}, {'t': 0.5249965190887451, 'a': 'i', 'tk': 'm', 'i': 21}, {'t': 0.5254965424537659, 'a': 'i', 'tk': 'm', 'i': 22}, {'t': 0.5339969396591187, 'a': 'i', 'tk': 'm', 'i': 23}, {'t': 0.555497944355011, 'a': 'i', 'tk': 'm', 'i': 24}, {'t': 0.565498411655426, 'a': 'c', 'tk': '(', 'i': 21}, {'t': 0.5664984583854675, 'a': 'c', 'tk': ')', 'i': 10}, {'t': 0.5744988322257996, 'a': 'c', 'tk': '(', 'i': 0}, {'t': 0.5754988789558411, 'a': 'i', 'tk': 'm', 'i': 25}, {'t': 0.5784990191459656, 'a': 'c', 'tk': ')', 'i': 24}, {'t': 0.5914996266365051, 'a': 'i', 'tk': 'm', 'i': 26}, {'t': 0.5944997668266296, 'a': 'c', 'tk': ')', 'i': 25}, {'t': 0.5964998602867126, 'a': 'i', 'tk': 'm', 'i': 27}, {'t': 0.6185008883476257, 'a': 'c', 'tk': ')', 'i': 26}, {'t': 0.6290013790130615, 'a': 'i', 'tk': 'm', 'i': 28}, {'t': 0.6500023603439331, 'a': 'c', 'tk': ')', 'i': 5}, {'t': 0.6720033884048462, 'a': 'c', 'tk': '(', 'i': 23}, {'t': 0.6780036687850952, 'a': 'c', 'tk': '(', 'i': 9}, {'t': 0.7150053977966309, 'a': 'c', 'tk': '(', 'i': 19}, {'t': 0.7225057482719421, 'a': 'i', 'tk': 'm', 'i': 29}, {'t': 0.7655077576637268, 'a': 'c', 'tk': ')', 'i': 17}, {'t': 0.7710080146789551, 'a': 'c', 'tk': ')', 'i': 16}, {'t': 0.7965092062950134, 'a': 'c', 'tk': ')', 'i': 13}, {'t': 0.8275106549263, 'a': 'c', 'tk': ')', 'i': 4}, {'t': 0.8355110287666321, 'a': 'c', 'tk': '(', 'i': 12}, {'t': 0.8445114493370056, 'a': 'c', 'tk': ')', 'i': 6}, {'t': 0.8500117063522339, 'a': 'c', 'tk': '(', 'i': 14}, {'t': 0.8590121269226074, 'a': 'c', 'tk': ')', 'i': 7}, {'t': 0.9100145101547241, 'a': 'i', 'tk': 'm', 'i': 30}, {'t': 0.9115145802497864, 'a': 'c', 'tk': ')', 'i': 29}, {'t': 0.9205150008201599, 'a': 'c', 'tk': ')', 'i': 11}, {'t': 0.9620169401168823, 'a': 'c', 'tk': '(', 'i': 30}, {'t': 0.9725174307823181, 'a': 'c', 'tk': ')', 'i': 28}, {'t': 0.9805178046226501, 'a': 'c', 'tk': '(', 'i': 22}, {'t': 0.9830179214477539, 'a': 'c', 'tk': '(', 'i': 18}, {'t': 0.9890182018280029, 'a': 'c', 'tk': ')', 'i': 27}, {'t': 0.9935184121131897, 'a': 'c', 'tk': '(', 'i': 20}, {'t': 0.999018669128418, 'a': 'i', 'tk': 'm', 'i': 31}, {'t': 0.9995186924934387, 'a': 'c', 'tk': ')', 'i': 31}]\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + "
\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from IPython.display import display, HTML\n", + "import json\n", + "\n", + "# Select the index of the tensor to visualize\n", + "id = 0\n", + "assert 0 <= id < batch_size, \"id must be in [0, batch_size)\"\n", + "\n", + "events = process_trace(trace[id])\n", + "print(events)\n", + "display(\n", + " HTML(f\"\"\"\n", + "\n", + "
\n", + "\n", + "\n", + "\"\"\")\n", + ")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.5" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/FlexMDM/notebook/test_interpolant.ipynb b/FlexMDM/notebook/test_interpolant.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..ce55c39304016653f1e9d5d143a689866ec31d39 --- /dev/null +++ b/FlexMDM/notebook/test_interpolant.ipynb @@ -0,0 +1,198 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "af26c690", + "metadata": {}, + "outputs": [], + "source": [ + "from interpolant import AnyOrderMaskInsertionInterpolant\n", + "import torch\n", + "from schedule import LinearSchedule" + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "id": "6c834d3f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "rate tensor([100.0001])\n", + "xt tensor([[1, 2, 2, 2, 1, 2, 2, 3]])\n", + "st tensor([[0, 1, 2, 3, 4, 5, 6, 9]])\n", + "posterior tensor([[[0., 0., 0.],\n", + " [0., 0., 0.],\n", + " [0., 0., 0.],\n", + " [0., 0., 0.],\n", + " [0., 0., 0.],\n", + " [0., 0., 0.],\n", + " [0., 0., 0.],\n", + " [0., 0., 0.]]])\n", + "length tensor([[[1., 0., 0., 0., 0., 0., 0., 0., 0.],\n", + " [1., 0., 0., 0., 0., 0., 0., 0., 0.],\n", + " [1., 0., 0., 0., 0., 0., 0., 0., 0.],\n", + " [1., 0., 0., 0., 0., 0., 0., 0., 0.],\n", + " [1., 0., 0., 0., 0., 0., 0., 0., 0.],\n", + " [1., 0., 0., 0., 0., 0., 0., 0., 0.],\n", + " [1., 0., 0., 0., 0., 0., 0., 0., 0.],\n", + " [1., 0., 0., 0., 0., 0., 0., 0., 0.],\n", + " [1., 0., 0., 0., 0., 0., 0., 0., 0.]]])\n" + ] + } + ], + "source": [ + "mask_token = 0\n", + "pad_token = 3\n", + "max_length = 8\n", + "mask_schedule = LinearSchedule()\n", + "interpolant = AnyOrderMaskInsertionInterpolant(mask_schedule, 3, 0, 3, 8)\n", + "t = torch.Tensor([0.99])\n", + "x1 = torch.Tensor([[1, 2, 2, 2, 1, 2, 2, 3]]).to(torch.int64)\n", + "xt, st = interpolant.sample_interpolant(t, x1)\n", + "\n", + "\n", + "reparametrised_rate = interpolant.reparametrised_conditional_rate(xt, st, t, x1)\n", + "\n", + "print(\"xt\", xt)\n", + "print(\"st\", st)\n", + "print(\"posterior\", reparametrised_rate.per_token_posterior)\n", + "print(\"length\", reparametrised_rate.length_posterior)" + ] + }, + { + "cell_type": "code", + "execution_count": 64, + "id": "5134a5cb", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "tensor([[1, 2, 2, 2, 1, 2, 2, 3]])\n", + "tensor([[ True, True, True, True, True, True, True, False]])\n", + "tensor([[False, False, False, False, False, False, False, False]])\n", + "tensor([[1, 2, 2, 2, 1, 2, 2, 3]])\n", + "tensor([[0, 0, 0, 0, 0, 0, 0, 0, 0]])\n" + ] + } + ], + "source": [ + "t = torch.Tensor([0.99])\n", + "x1 = torch.Tensor([[1, 2, 2, 2, 1, 2, 2, 3]]).to(torch.int64)\n", + "xt, st, xt_mask_indices, x1_remained, gap_counts = interpolant.jay_sample_interpolant(t, x1)\n", + "\n", + "print(xt)\n", + "print(st)\n", + "print(xt_mask_indices)\n", + "print(x1_remained)\n", + "print(gap_counts)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "id": "7ad929b8", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "tensor([[[0., 0., 0., 0.],\n", + " [0., 0., 0., 0.],\n", + " [0., 0., 0., 0.],\n", + " [0., 0., 0., 0.],\n", + " [0., 0., 0., 0.],\n", + " [0., 0., 0., 0.],\n", + " [0., 0., 0., 0.],\n", + " [0., 0., 0., 0.]]])\n" + ] + } + ], + "source": [ + "x1_one_hot = torch.nn.functional.one_hot(x1_remained, num_classes=4).to(xt.dtype)\n", + "# Expand to match x1_one_hot shape: (B, L, vocab_size)\n", + "mask = xt_mask_indices.unsqueeze(-1) # (B, L, 1)\n", + "x1_one_hot = x1_one_hot * mask / (1 - t.view(-1, 1, 1))\n", + "\n", + "print(x1_one_hot)" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "44072b22", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "tensor([[[0., 0., 0., 1., 0., 0.],\n", + " [1., 0., 0., 0., 0., 0.],\n", + " [1., 0., 0., 0., 0., 0.],\n", + " [1., 0., 0., 0., 0., 0.],\n", + " [1., 0., 0., 0., 0., 0.],\n", + " [1., 0., 0., 0., 0., 0.]],\n", + "\n", + " [[1., 0., 0., 0., 0., 0.],\n", + " [1., 0., 0., 0., 0., 0.],\n", + " [1., 0., 0., 0., 0., 0.],\n", + " [0., 1., 0., 0., 0., 0.],\n", + " [1., 0., 0., 0., 0., 0.],\n", + " [1., 0., 0., 0., 0., 0.]]])" + ] + }, + "execution_count": 37, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "reparametrised_rate.length_posterior" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c04150ba", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e831e031", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/FlexMDM/notebook/test_text.ipynb b/FlexMDM/notebook/test_text.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..aee27ed275e4e7bf913ac2d0185b697bd98e18fb --- /dev/null +++ b/FlexMDM/notebook/test_text.ipynb @@ -0,0 +1,435 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "6ab727cd", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "\n", + "os.environ[\"HF_HOME\"] = \"/n/netscratch/albergo_lab/Everyone/hf_cache\"\n", + "\n", + "from datasets import load_dataset\n", + "from transformers import GPT2TokenizerFast\n", + "\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "185f88b7", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "0be6f5d7f95a49ca872f6f09945b84c6", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Loading dataset shards: 0%| | 0/83 [00:00 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (1585 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (1503 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (1166 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (1531 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (1379 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (2034 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (2850 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (1463 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (1213 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (1330 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (1387 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (11756 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (3123 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (1051 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (1133 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (2382 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (1114 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (1930 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (4085 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (1678 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (1946 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (4631 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (4681 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (2464 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (1446 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (1551 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (1998 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (14865 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (2155 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (1079 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (1165 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (2120 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (1679 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (3594 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (1287 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (3350 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (1359 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (1081 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (9631 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (1140 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (1177 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (1221 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (2380 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (1072 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (1746 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (1040 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (2325 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (4584 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (1180 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (3565 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (1034 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (1081 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (1757 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (2878 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (2390 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (15255 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (1166 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (3392 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (1373 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (2410 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (1626 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (1059 > 1024). Running this sequence through the model will result in indexing errors\n", + "Token indices sequence length is longer than the specified maximum sequence length for this model (16244 > 1024). Running this sequence through the model will result in indexing errors\n" + ] + } + ], + "source": [ + "tokeniser = GPT2TokenizerFast.from_pretrained(\"gpt2\")\n", + "detokeniser = None\n", + "\n", + "\n", + "def preprocess_batch(batch, max_length=512):\n", + " all_input_ids = []\n", + " all_lengths = []\n", + "\n", + " for text in batch[\"text\"]:\n", + " if detokeniser is not None:\n", + " text = detokeniser(text)\n", + "\n", + " tokens = tokeniser(text, return_attention_mask=False)[\"input_ids\"]\n", + " chunks = [\n", + " tokens[i : min(len(tokens), i + max_length)]\n", + " for i in range(0, len(tokens), max_length)\n", + " ]\n", + "\n", + " all_input_ids.extend(chunks)\n", + " all_lengths.extend([len(chunk) for chunk in chunks])\n", + "\n", + " return {\n", + " \"input_ids\": all_input_ids,\n", + " \"length\": all_lengths,\n", + " }\n", + "\n", + "\n", + "# Apply the function to the dataset using batched=True and remove_columns as needed\n", + "tokenised_ds_1 = ds.map(\n", + " lambda batch: preprocess_batch(batch, max_length=1024),\n", + " batched=True,\n", + " num_proc=64,\n", + " remove_columns=[\"text\"], # Optional, keeps things clean\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "98163b3d", + "metadata": {}, + "outputs": [], + "source": [ + "tokeniser = GPT2TokenizerFast.from_pretrained(\"gpt2\")\n", + "detokeniser = None\n", + "\n", + "\n", + "def find_delimiter_positions(tokens, delimiter_tokens):\n", + " \"\"\"Return the start indices where the delimiter occurs in the token sequence.\"\"\"\n", + " positions = []\n", + " n = len(delimiter_tokens)\n", + " for i in range(len(tokens) - n + 1):\n", + " if tokens[i : i + n] == delimiter_tokens:\n", + " positions.append(i)\n", + " return positions\n", + "\n", + "\n", + "def recursive_split(tokens, max_length, delimiter_tokens):\n", + " if len(tokens) <= max_length:\n", + " return [tokens]\n", + "\n", + " # Find all positions where the delimiter sequence occurs\n", + " split_candidates = find_delimiter_positions(tokens, delimiter_tokens)\n", + " if not split_candidates:\n", + " # Safe fallback: naive split\n", + " return [\n", + " tokens[i : min(i + max_length, len(tokens))]\n", + " for i in range(0, len(tokens), max_length)\n", + " ]\n", + "\n", + " # Find delimiter closest to the midpoint\n", + " midpoint = len(tokens) // 2\n", + " split_point = min(split_candidates, key=lambda x: abs(x - midpoint))\n", + "\n", + " # Recurse on both sides, skipping the delimiter\n", + " dlen = len(delimiter_tokens)\n", + " left = recursive_split(tokens[:split_point], max_length, delimiter_tokens)\n", + " right = recursive_split(tokens[split_point + dlen :], max_length, delimiter_tokens)\n", + "\n", + " return left + right\n", + "\n", + "\n", + "def recursive_chunk_preprocess(batch, max_length=512):\n", + " all_input_ids = []\n", + " all_lengths = []\n", + "\n", + " for text in batch[\"text\"]:\n", + " if detokeniser is not None:\n", + " text = detokeniser(text)\n", + "\n", + " tokens = tokeniser.encode(text, add_special_tokens=False)\n", + " chunks = recursive_split(tokens, max_length, [198, 198])\n", + "\n", + " all_input_ids.extend(chunks)\n", + " all_lengths.extend([len(chunk) for chunk in chunks])\n", + "\n", + " return {\n", + " \"input_ids\": all_input_ids,\n", + " \"length\": all_lengths,\n", + " }\n", + "\n", + "\n", + "tokenised_ds_2 = ds.map(\n", + " lambda batch: recursive_chunk_preprocess(batch, max_length=1024),\n", + " batched=True,\n", + " num_proc=64,\n", + " remove_columns=[\"text\"],\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0a23b285", + "metadata": {}, + "outputs": [], + "source": [ + "def plot_lengths(length_list, log_scale=False):\n", + " lengths_np = np.array(length_list)\n", + "\n", + " # Sort and compute CDF\n", + " sorted_lengths = np.sort(lengths_np)\n", + " cdf = np.arange(1, len(sorted_lengths) + 1) / len(sorted_lengths)\n", + "\n", + " # Compute percentiles\n", + " percentiles = [10, 20, 30, 40, 50, 60, 70, 80, 90, 95, 99]\n", + " results = {p: int(np.percentile(lengths_np, p)) for p in percentiles}\n", + "\n", + " # Plot CDF\n", + " plt.figure(figsize=(10, 6))\n", + " plt.plot(sorted_lengths, cdf, label=\"CDF of token lengths\", color=\"blue\")\n", + "\n", + " # Add percentile lines\n", + " for p in percentiles:\n", + " val = results[p]\n", + " plt.axvline(val, color=\"red\", linestyle=\"--\", linewidth=1)\n", + " plt.text(\n", + " val,\n", + " 0.02,\n", + " f\"{p}%\",\n", + " rotation=90,\n", + " color=\"red\",\n", + " fontsize=9,\n", + " verticalalignment=\"bottom\",\n", + " horizontalalignment=\"right\",\n", + " )\n", + "\n", + " # Optional: reference max_length line\n", + "\n", + " # Labels and formatting\n", + " plt.xlabel(\"Token Length\")\n", + " plt.ylabel(\"Cumulative Proportion\")\n", + " if log_scale:\n", + " plt.xscale(\"log\")\n", + " plt.title(\"CDF of Tokenized Chunk Lengths with Percentile Markers\")\n", + " plt.grid(True, which=\"both\", linestyle=\"--\", alpha=0.5)\n", + " plt.legend()\n", + " plt.tight_layout()\n", + " plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "9a4bbee1", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/tmp/ipykernel_3421044/2484663865.py:34: UserWarning: Creating legend with loc=\"best\" can be slow with large amounts of data.\n", + " plt.tight_layout()\n", + "/n/netscratch/albergo_lab/Lab/brianlck/interpretable-flow/.venv/lib64/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Creating legend with loc=\"best\" can be slow with large amounts of data.\n", + " fig.canvas.print_figure(bytes_io, **kw)\n" + ] + }, + { + "data": { + "image/png": "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6mDmzJk4fPgw5s2bhwkTJuDLL790PTGXcPDgQQBAvXr1AFg/jlK+P3jooYeQnp6ON954A9u3b8eGDRsc3nWyb98+jBw5EuPHj8e4ceNw2WWXoaCgAN27d8eePXtwzz334IorrkBWVha+++47HD9+HDExMZAkCUOGDMH69evxn//8B61atcKuXbvw6quvYv/+/Vi2bJlDjevXr8c333yDBx54ABEREXjttdcwfPhwHD16FPXq1cNNN92E/fv34/PPP8err75qeyW//A8xnli1ahX69++P1NRUzJw5E2azGR9++CGuv/56rFu3Dp07d/bocUaOHIlPPvkEL7zwAkwmE7KysvDrr79i8eLFDn8oKLdo0SKEh4fjkUceQXh4OFatWoUZM2YgLy8Pc+fOdbjv2bNn0b9/f9x222248847q2Sj3DvvvIP77rsPTzzxhO0z5t72d8sttyA5ORnPP/+87Y/Jw4cPx7///ouJEyciISEBp0+fxm+//YajR4/W+IEuiagaZCKiGpSbmysDkIcOHerx9zRr1kweOHCgy9tfffVVGYC8fPly2zYATv99+OGHl3yu//73vzIAed26dbZt+fn5cmJiopyQkCBbLBaH53jwwQc97kOWZXnLli0OdRQXF8uxsbFy27Zt5QsXLtju93//938yAHnGjBm2baNHj5bDwsJkWZbl9evXy5GRkfLAgQPlixcv2u4zduxYuVGjRnJWVpbD8952221yVFSUfP78eVmWZXn16tUyALlVq1ZyUVGR7X7z58+XAci7du1yeN5mzZq57GnDhg1yrVq15Hvuuce27ZlnnpHDwsLk/fv3O9x36tSpckBAgHz06FFZlmV52bJlMgD5xRdftN2ntLRU7t69u0fzVadOHfnyyy+/5H0qatasmQxAXrt2rW3b6dOn5eDgYHny5Mm2bTNnzpSd/Yr88MMPZQByenq6149Z/r1btmyRMzIy5DZt2sjNmzeXDx8+7Lbu8vlasmSJy/t4Oubp6ekyALlevXpydna27X7Lly+XAcjff/+9bVu7du3kuLg4OT8/37ZtzZo1MgCHTJw5c0YGIM+cObNKXaNHj5YByE8//bTD9o4dO8qpqam26w8//LAcGRkpl5aWXnowKlmyZIkMQN69e7csy7L83XffycHBwfKQIUPkESNG2O7Xvn17ediwYbbrzuayTZs2cs+e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pCRERES4/dkgIjHwlW4iqnFJSUn47LPPMGLECLRq1QqjRo1C27ZtUVxcjD/++ANLliypcl7XEydO2P6aX1BQgN27d2PJkiXIzMzE5MmTMX78eEVqmzp1Kj7//HP0798fDz30EOrWrYuPPvoI6enp+Prrr2E2K/u3ylq1amHOnDkYM2YMevbsiZEjR9pOGZaQkIBJkyY5/b7atWvj//7v/3D99dejf//++P3339G2bVu88MILWL16Nbp06YJx48ahdevWyM7OxrZt27BixQpkZ2f7XHP5wYR69OhR5RWWrl27onnz5nj00Ufx3XffYdCgQbZTZxUWFmLXrl1YunQpDh8+jJiYGAwePBjdunXD1KlTcfjwYbRu3RrffPNNlc9TulKnTh18++23GDBgADp06IA777wTqampAIBt27bh888/r9a7KW644QY0bdoUY8eOxaOPPoqAgAB88MEHqF+/Po4ePer14zkzd+5c5Obm4sEHH0RERATuvPNOt9/z9ddfVznAE2D9vKinY+6N559/HkOHDkW3bt0wZswYnDt3Dm+88Qbatm3rsICoXbs2WrdujS+//BIpKSmoW7cu2rZt69XxD+69915kZ2fj+uuvR1xcHI4cOYLXX38dHTp0cPsulhYtWqBhw4bYt2+fw0KrR48eePzxxwHAo0V3amoq3nrrLTz77LNo0aIFYmNjq7wDxV969uyJ8ePHY/bs2dixYwduuOEG1KpVC2lpaViyZAnmz5/v9tR4jz76KJYuXYpbbrkF99xzD1JTU5GdnY3vvvsOCxcuxOWXX4677roLX331Fe677z6sXr0a3bp1g8Viwd69e/HVV1/Zzv3tjfKfuf/973+47bbbUKtWLQwePNi2GL8Us9mM9957D/3790ebNm0wZswYNGnSBCdOnMDq1asRGRmJ77//3qt6evbs6fQdOBV17doVderUwejRo/HQQw/BZDJh8eLFPr1tvkWLFvj1119x7bXXom/fvli1ahUiIyN97m///v3o1asXbr31VrRu3RqBgYH49ttvcerUKdx2223VrpeIapBKR00nIpL3798vjxs3Tk5ISJCDgoLkiIgIuVu3bvLrr7/ucLqW8tPjAJBNJpMcGRkpt2nTRh43bpy8adMmp4+NapzOq9zBgwflm2++WY6OjpZDQkLkzp07y//3f/+nyHNUPmVYuS+//FLu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ITc3F9nZ2QgICNA7HPJzrEcF5eaWPSp8LPM0v65FM+1zVXKpRRxerUVV2scdRoq1KgbNxWO1aND8lWKWNlTl2GiQ9iztU1Z1dt/vRi8/efIkmjVrpncYREREREREZAInTpxA08tMb+Z3nW5N03D69GnUrVsXFotF73Ccys7ORrNmzXDixAllpzUj/8F6JFWwFkkVrEVSBWuRVOKP9SiEQE5ODho3bnzZUdv97vJyq9V62U8hVFKvXj2/KVhSH+uRVMFaJFWwFkkVrEVSib/VY2RkZJXbcCA1IiIiIiIiIi9hp5uIiIiIiIjIS9jpVlBISAimTZuGkJAQvUMhYj2SMliLpArWIqmCtUgqYT265ncDqRERERERERH5Cs90ExEREREREXkJO91EREREREREXsJONxEREREREZGXsNOtoIULF6Jly5YIDQ1Fr169sH37dr1DIhOZOXMmevTogbp166JRo0a47bbbkJSU5LDNpUuXMGbMGDRs2BARERG48847kZ6e7rBNSkoKBg8ejDp16qBRo0aYNGkSiouLfZkKmcysWbNgsVgwYcIE+zLWIvnSqVOncN9996Fhw4YICwtDx44d8euvv9rXCyEwdepUxMXFISwsDImJiUhOTnb4HefPn8eIESNQr149REVF4eGHH0Zubq6vUyEDs9lseOGFF9CqVSuEhYWhTZs2eOmll1B+GCbWInnLzz//jFtvvRWNGzeGxWLBN99847DeU7W3b98+9O/fH6GhoWjWrBnmzJnj7dR0xU63YpYtW4aJEydi2rRp2LVrFzp37oyBAwfizJkzeodGJrFhwwaMGTMGW7duxZo1a1BUVIQbb7wReXl59m2eeOIJfP/991i+fDk2bNiA06dP44477rCvt9lsGDx4MAoLC7F582Z88skn+PjjjzF16lQ9UiIT2LFjB95991106tTJYTlrkXzlwoUL6Nu3L4KCgrBq1Srs378f8+fPR/369e3bzJkzB2+99RYWL16Mbdu2ITw8HAMHDsSlS5fs24wYMQJ//PEH1qxZg5UrV+Lnn3/GI488okdKZFCzZ8/GokWLsGDBAhw4cACzZ8/GnDlz8Pbbb9u3YS2St+Tl5aFz585YuHCh0/WeqL3s7GzceOONaNGiBXbu3Im5c+di+vTpeO+997yen24EKaVnz55izJgx9uc2m000btxYzJw5U8eoyMzOnDkjAIgNGzYIIYTIzMwUQUFBYvny5fZtDhw4IACILVu2CCGE+O9//yusVqtIS0uzb7No0SJRr149UVBQ4NsEyPBycnJEfHy8WLNmjbj22mvF+PHjhRCsRfKtyZMni379+rlcr2maiI2NFXPnzrUvy8zMFCEhIeKLL74QQgixf/9+AUDs2LHDvs2qVauExWIRp06d8l7wZCqDBw8Wo0aNclh2xx13iBEjRgghWIvkOwDEihUr7M89VXvvvPOOqF+/vsP/6cmTJ4v27dt7OSP98Ey3QgoLC7Fz504kJibal1mtViQmJmLLli06RkZmlpWVBQBo0KABAGDnzp0oKipyqMMrr7wSzZs3t9fhli1b0LFjR8TExNi3GThwILKzs/HHH3/4MHoygzFjxmDw4MEONQewFsm3vvvuOyQkJGDYsGFo1KgRunbtivfff9++/ujRo0hLS3Oox8jISPTq1cuhHqOiopCQkGDfJjExEVarFdu2bfNdMmRoffr0wU8//YRDhw4BAPbu3YuNGzfi5ptvBsBaJP14qva2bNmCP//5zwgODrZvM3DgQCQlJeHChQs+ysa3AvUOgMpkZGTAZrM5vHkEgJiYGBw8eFCnqMjMNE3DhAkT0LdvX3To0AEAkJaWhuDgYERFRTlsGxMTg7S0NPs2zuq0dB2Ru5YuXYpdu3Zhx44dldaxFsmXjhw5gkWLFmHixIl49tlnsWPHDowbNw7BwcEYOXKkvZ6c1Vv5emzUqJHD+sDAQDRo0ID1SG6bMmUKsrOzceWVVyIgIAA2mw2vvPIKRowYAQCsRdKNp2ovLS0NrVq1qvQ7SteVv63HLNjpJvJjY8aMwe+//46NGzfqHQr5oRMnTmD8+PFYs2YNQkND9Q6H/JymaUhISMCrr74KAOjatSt+//13LF68GCNHjtQ5OvInX375JZYsWYLPP/8cf/rTn7Bnzx5MmDABjRs3Zi0SGRQvL1dIdHQ0AgICKo3Mm56ejtjYWJ2iIrMaO3YsVq5ciXXr1qFp06b25bGxsSgsLERmZqbD9uXrMDY21mmdlq4jcsfOnTtx5swZdOvWDYGBgQgMDMSGDRvw1ltvITAwEDExMaxF8pm4uDhcffXVDsuuuuoqpKSkACirp8v9j46Nja008GlxcTHOnz/PeiS3TZo0CVOmTME999yDjh074v7778cTTzyBmTNnAmAtkn48VXv++L+bnW6FBAcHo3v37vjpp5/syzRNw08//YTevXvrGBmZiRACY8eOxYoVK7B27dpKl/d0794dQUFBDnWYlJSElJQUex327t0bv/32m8NBdc2aNahXr16lN61ErgwYMAC//fYb9uzZY/9KSEjAiBEj7N+zFslX+vbtW2n6xEOHDqFFixYAgFatWiE2NtahHrOzs7Ft2zaHeszMzMTOnTvt26xduxaapqFXr14+yILM4OLFi7BaHd+iBwQEQNM0AKxF0o+naq937974+eefUVRUZN9mzZo1aN++vSkvLQfA0ctVs3TpUhESEiI+/vhjsX//fvHII4+IqKgoh5F5iWpj9OjRIjIyUqxfv16kpqbavy5evGjf5tFHHxXNmzcXa9euFb/++qvo3bu36N27t319cXGx6NChg7jxxhvFnj17xOrVq8UVV1whnnnmGT1SIhMpP3q5EKxF8p3t27eLwMBA8corr4jk5GSxZMkSUadOHfHZZ5/Zt5k1a5aIiooS3377rdi3b5/461//Klq1aiXy8/Pt29x0002ia9euYtu2bWLjxo0iPj5eDB8+XI+UyKBGjhwpmjRpIlauXCmOHj0qvv76axEdHS2efvpp+zasRfKWnJwcsXv3brF7924BQLz22mti9+7d4vjx40IIz9ReZmamiImJEffff7/4/fffxdKlS0WdOnXEu+++6/N8fYWdbgW9/fbbonnz5iI4OFj07NlTbN26Ve+QyEQAOP366KOP7Nvk5+eLxx57TNSvX1/UqVNH3H777SI1NdXh9xw7dkzcfPPNIiwsTERHR4snn3xSFBUV+TgbMpuKnW7WIvnS999/Lzp06CBCQkLElVdeKd577z2H9ZqmiRdeeEHExMSIkJAQMWDAAJGUlOSwzblz58Tw4cNFRESEqFevnnjooYdETk6OL9Mgg8vOzhbjx48XzZs3F6GhoaJ169biueeec5heibVI3rJu3Tqn7xNHjhwphPBc7e3du1f069dPhISEiCZNmohZs2b5KkVdWIQQQp9z7ERERERERETmxnu6iYiIiIiIiLyEnW4iIiIiIiIiL2Gnm4iIiIiIiMhL2OkmIiIiIiIi8hJ2uomIiIiIiIi8hJ1uIiIiIiIiIi9hp5uIiIiIiIjIS9jpJiIiIiIiIvISdrqJiIgUduzYMVgsFuzZs0fvUJRx3XXX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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot_lengths(tokenised_ds_2[\"train\"][\"length\"])" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "20b68318", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/tmp/ipykernel_3421044/2484663865.py:34: UserWarning: Creating legend with loc=\"best\" can be slow with large amounts of data.\n", + " plt.tight_layout()\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA90AAAJOCAYAAACqS2TfAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjMsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvZiW1igAAAAlwSFlzAAAPYQAAD2EBqD+naQAA3XtJREFUeJzs3Xd8E+XjB/DPJV10A6VQaKGlFNkCRRAQUJmCgMoSQYaKOBgyHKiAqIiKICgqigPBhSAC/hBRGQqIi6Eoq5SyW0otnUBH7n5/HMm3oQ0kzSX3JPm8X6++jl4ul+e553NHn9zdc5KiKAqIiIiIiIiISHMGvQtARERERERE5K3Y6SYiIiIiIiJyEXa6iYiIiIiIiFyEnW4iIiIiIiIiF2Gnm4iIiIiIiMhF2OkmIiIiIiIichF2uomIiIiIiIhchJ1uIiIiIiIiIhdhp5uIiIiIiIjIRdjpJiKqpLNnz2LgwIGoXr06JEnCggULXPp5o0aNQmhoqEs/w9bnxsfHu/Uzjx07BkmSsHTpUk3XGx8fj9tvv13Tddpr6dKlkCQJf/75py6frxe9cnst5vY4duyY3cv6WttpIT4+HqNGjbL8vnXrVkiShK1bt+pWJi3peUwhIs/BTjcR6SY1NRVjx45F/fr1ERQUhPDwcHTs2BELFy7ExYsXLcvFx8dDkiRIkgSDwYDIyEg0b94cDz74IH777bcK121e/sqfWrVqaVb+SZMmYePGjZg2bRqWL1+OXr16lVtm1KhRNstS9qfsH6XkuLNnz2Lq1Klo1KgRgoODERISguTkZLz44ovIycnRu3hOM3dUVq1apXdRKnThwgU899xzHt+RevvttzX/ogcAnnvuOav9PTg4GE2aNMGzzz6LvLw8zT/P3X755Rc899xzuuxr5i/oJEnCiy++WOEyw4YNgyRJQn75Q0S+wU/vAhCRb1q/fj0GDRqEwMBAjBgxAs2aNUNxcTG2b9+Oxx9/HP/++y/ee+89y/ItW7bElClTAAD5+fk4cOAAVq5ciSVLlmDSpEmYP39+uc/o3r07RowYYTWvSpUqmtVh8+bN6N+/P6ZOnWpzmbFjx6Jbt26W39PS0jBjxgw8+OCD6NSpk2V+YmKiZuXS2pIlSyDLst7FsOmPP/5A7969UVBQgOHDhyM5ORkA8Oeff+Lll1/Gzz//jO+//17nUnq3CxcuYNasWQCAm2++Wd/C2Onee+/F3XffjcDAQMu8t99+G1FRUS77Euydd95BaGgoCgoK8P3332P27NnYvHkzduzYAUmSXPKZ7vDLL79g1qxZGDVqFCIjI61eO3ToEAwG15/jCQoKwueff45nn33Wan5hYSHWrl2LoKAgl5eBiMgWdrqJyO3S0tJw9913o169eti8eTNiYmIsrz366KM4cuQI1q9fb/WeOnXqYPjw4VbzXnnlFdxzzz14/fXXkZSUhIcfftjq9YYNG5Z7j5YyMzPL/YF5pfbt26N9+/aW3//880/MmDED7du3d2nZtOTv7693EWzKycnBnXfeCaPRiD179qBRo0ZWr8+ePRtLlizRqXQkMqPRCKPR6NbPHDhwIKKiogAADz30EAYMGIDVq1fj119/tTpOOEpRFFy6dEnTLxW1UvZLDVfq3bs3Vq9ejb/++gvXX3+9Zf7atWtRXFyMXr16YfPmzZp93oULFxAcHKzZ+mwpLCxESEiIyz+HiFyLl5cTkdu9+uqrKCgowAcffGDV4TZr0KABJk6ceM31VKlSBcuXL0e1atUwe/ZsKIqiSfmOHj2KQYMGoVq1aggODsaNN95o9SWA+f5ORVHw1ltvWS5tdMbKlSuRnJyMKlWqICoqCsOHD8fp06ev+b69e/eiRo0auPnmm1FQUAAAOH36NO677z7UrFkTgYGBaNq0KT788EOr95kvV/7yyy8xe/ZsxMbGIigoCF27dsWRI0eslr3ynu6bb77Z5mXyZS/NzcnJwWOPPYa4uDgEBgaiQYMGeOWVV8qdNc/JycGoUaMQERGByMhIjBw50u7LVN99912cPn0a8+fPL9fhBoCaNWuWO/MFANu3b0fbtm0RFBSE+vXrY9myZVavmy8HvlJF9wGb7+m81jorcv78ebRt2xaxsbE4dOiQHTW+Onu2ufly3Ndeew3vvfceEhMTERgYiBtuuAF//PFHuXWuXLkSTZo0QVBQEJo1a4avv/7aKhPHjh1DjRo1AACzZs2yZOG5556zWs/p06dxxx13IDQ0FDVq1MDUqVNhMpmslvniiy+QnJyMsLAwhIeHo3nz5li4cOFV69y6dWvcddddVvOaN28OSZLw999/W+atWLECkiThwIEDAMq3ZXx8PP7991/89NNPljpceda+qKgIkydPRo0aNRASEoI777wT586du2r5rubWW28FoH4RCQCyLGPBggVo2rQpgoKCULNmTYwdOxbnz5+3ep85cxs3bkSbNm1QpUoVvPvuuwDUDEyaNAnx8fEIDAxEbGwsRowYgaysLKt6zJw5Ew0aNEBgYCDi4uLwxBNPoKioyOpzJEnCuHHjsGbNGjRr1sxyPPnuu+8syzz33HN4/PHHAQAJCQmWbVd2u9pz5cBvv/2GXr16ISIiAsHBwejSpQt27Nhh97Zs3749EhIS8Nlnn1nN//TTT9GrVy9Uq1at3HvWrl2LPn36oHbt2ggMDERiYiJeeOGFcrm8+eab0axZM+zatQudO3dGcHAwnn76aZtl+fjjj+Hn52fZLvbWz3zc2b9/P+655x5UrVoVN910EwAgIyMDo0ePRmxsLAIDAxETE4P+/fvbNSYBEemPZ7qJyO2++eYb1K9fHx06dHB6XaGhobjzzjvxwQcfYP/+/WjatKnltUuXLln9oQkAYWFhVz3zcvbsWXTo0AEXLlzAhAkTUL16dXz88cfo168fVq1ahTvvvBOdO3fG8uXLce+991Z4Cbujli5ditGjR+OGG27AnDlzcPbsWSxcuBA7duzAnj17bJ5N/+OPP9CzZ0+0adMGa9euRZUqVXD27FnceOONlj+Wa9SogQ0bNuD+++9HXl4eHnvsMat1vPzyyzAYDJg6dSpyc3Px6quvYtiwYTbvlQeAZ555Bg888IDVvE8++QQbN25EdHQ0APUsUJcuXXD69GmMHTsWdevWxS+//IJp06YhPT3dMuicoijo378/tm/fjoceegiNGzfG119/jZEjR9q17datW4cqVapg4MCBdi0PAEeOHMHAgQNx//33Y+TIkfjwww8xatQoJCcnW+XHEZVZZ1ZWFrp3747s7Gz89NNPTt9iYO82N/vss8+Qn5+PsWPHQpIkvPrqq7jrrrtw9OhRy9UN69evx5AhQ9C8eXPMmTMH58+fx/333486depY1lOjRg288847ePjhh3HnnXdaOsAtWrSwLGMymdCzZ0+0a9cOr732Gn788UfMmzcPiYmJlitUfvjhBwwdOhRdu3bFK6+8AgA4cOAAduzYcdUv4Tp16oTPP//c8nt2djb+/fdfGAwGbNu2zVKObdu2oUaNGmjcuHGF61mwYAHGjx+P0NBQPPPMMwDUL23KGj9+PKpWrYqZM2fi2LFjWLBgAcaNG4cVK1bYbpirSE1NBQBUr14dgHo7ivl4MGHCBKSlpWHRokXYs2cPduzYYXXVyaFDhzB06FCMHTsWY8aMwXXXXYeCggJ06tQJBw4cwH333YfWrVsjKysL69atw6lTpxAVFQVZltGvXz9s374dDz74IBo3box9+/bh9ddfx+HDh7FmzRqrMm7fvh2rV6/GI488grCwMLzxxhsYMGAATpw4gerVq+Ouu+7C4cOH8fnnn+P111+3nMk3fxFjj82bN+O2225DcnIyZs6cCYPBgI8++gi33nortm3bhrZt29q1nqFDh+KTTz7Byy+/DEmSkJWVhe+//x7Lly+3+qLAbOnSpQgNDcXkyZMRGhqKzZs3Y8aMGcjLy8PcuXOtlv3vv/9w22234e6778bw4cPLZcPsvffew0MPPYSnn37aco+5o/UbNGgQkpKS8NJLL1m+TB4wYAD+/fdfjB8/HvHx8cjMzMQPP/yAEydOuH2gSyKqBIWIyI1yc3MVAEr//v3tfk+9evWUPn362Hz99ddfVwAoa9eutcwDUOHPRx99dNXPeuyxxxQAyrZt2yzz8vPzlYSEBCU+Pl4xmUxWn/Hoo4/aXQ9FUZQ//vjDqhzFxcVKdHS00qxZM+XixYuW5f7v//5PAaDMmDHDMm/kyJFKSEiIoiiKsn37diU8PFzp06ePcunSJcsy999/vxITE6NkZWVZfe7dd9+tREREKBcuXFAURVG2bNmiAFAaN26sFBUVWZZbuHChAkDZt2+f1efWq1fPZp127Nih+Pv7K/fdd59l3gsvvKCEhIQohw8ftlr2qaeeUoxGo3LixAlFURRlzZo1CgDl1VdftSxTWlqqdOrUya72qlq1qnL99ddfdZmy6tWrpwBQfv75Z8u8zMxMJTAwUJkyZYpl3syZM5WK/ov86KOPFABKWlqaw+s0v/ePP/5Q0tPTlaZNmyr169dXjh07ds1ym9tr5cqVNpexd5unpaUpAJTq1asr2dnZluXWrl2rAFC++eYby7zmzZsrsbGxSn5+vmXe1q1bFQBWmTh37pwCQJk5c2a5co0cOVIBoDz//PNW81u1aqUkJydbfp84caISHh6ulJaWXn1jXGHlypUKAGX//v2KoijKunXrlMDAQKVfv37KkCFDLMu1aNFCufPOOy2/V9SWTZs2Vbp06VLuM8zLduvWTZFl2TJ/0qRJitFoVHJycq5aRnOeDh06pJw7d05JS0tT3n33XSUwMFCpWbOmUlhYqGzbtk0BoHz66adW7/3uu+/KzTdn7rvvvrNadsaMGQoAZfXq1eXKYC738uXLFYPBYHWMUxRFWbx4sQJA2bFjh2UeACUgIEA5cuSIZd5ff/2lAFDefPNNy7y5c+eW25Zlyzpy5EjL7+Ysb9myxVKupKQkpWfPnlbb9sKFC0pCQoLSvXv3cussy5znuXPnKv/884/V8futt95SQkNDlcLCQqvjZ9nPuNLYsWOV4OBgq+Nqly5dFADK4sWLK6yf+f+nhQsXKpIkKS+88ILldUfqZ87J0KFDrT7j/PnzljoSkWfi5eVE5FbmkXrDwsI0W6d5RNr8/Hyr+f3798cPP/xg9dOzZ8+rruvbb79F27ZtLZf0mdf/4IMP4tixY9i/f79m5QbUe7wzMzPxyCOPWA3006dPHzRq1Kjcve0AsGXLFvTs2RNdu3bF6tWrLWfuFUXBV199hb59+0JRFGRlZVl+evbsidzcXOzevdtqXaNHj0ZAQIDld/PgbkePHrWr/BkZGRg4cCBatmyJt99+2zJ/5cqV6NSpE6pWrWpVjm7dusFkMuHnn38GoG5vPz8/q/vxjUYjxo8fb9fn5+XlOZylJk2aWA1iV6NGDVx33XV219nZdZ46dQpdunRBSUkJfv75Z9SrV6/Sn1uWvdvcbMiQIahatarl9yvb/syZM9i3bx9GjBhhNepzly5d0Lx5c4fL99BDD1n93qlTJ6vtExkZicLCQvzwww8OrddcbnP9tm3bhhtuuAHdu3fHtm3bAKiXXP/zzz9WbVQZDz74oNVtB506dYLJZMLx48ftev91112HGjVqICEhAWPHjkWDBg2wfv16BAcHY+XKlYiIiED37t2t2i85ORmhoaHYsmWL1boSEhLKHc+++uorXH/99bjzzjvLfba53CtXrkTjxo3RqFEjq88xX+p+5ed069bN6iqMFi1aIDw83Kn9pay9e/ciJSUF99xzD/777z9LeQoLC9G1a1f8/PPPdg/k2LRpU7Ro0cJy5cNnn32G/v3727z3uuw98Pn5+cjKykKnTp1w4cIFHDx40GrZwMBAjB492uZnv/rqq5g4cSJeeeUVq1taKlO/K/eVKlWqICAgAFu3bi13qwEReQZeXk5EbhUeHg6gfAfZGeZ7ma/sfMXGxlqNHG6P48ePo127duXmmy9JPX78OJo1a1bJklb8eYD6x/iVGjVqhO3bt1vNu3TpEvr06YPk5GR8+eWX8PP732H83LlzyMnJwXvvvWc18ntZmZmZVr/XrVvX6ndzJ8yeP+xKS0sxePBgmEwmq84/AKSkpODvv/+2eYmpuRzHjx9HTExMuUf5VLQ9KhIeHu5wlq6sM6DW25k/Zh1Z57333gs/Pz8cOHBA00fY2bvNza7V9uZsNmjQoNy6GjRoUO4LnKsJCgoqV64rt88jjzyCL7/8Erfddhvq1KmDHj16YPDgwRU+iq+smjVrIikpCdu2bcPYsWOxbds23HLLLejcuTPGjx+Po0eP4sCBA5Bl2elOtzP7C6B2isPDw+Hv74/Y2FirzmxKSgpyc3Mtt2hc6cr2S0hIKLdMamoqBgwYcNUypKSk4MCBA5XOCeD8/nJleQBc9ZaS3Nxcqy+Iruaee+7BvHnzMGnSJPzyyy9Xvff633//xbPPPovNmzeXe3Rbbm6u1e916tSx+oKyrJ9++gnr16/Hk08+aXUfN1C5+l3ZtoGBgXjllVcwZcoU1KxZEzfeeCNuv/12jBgxQtNjCBG5DjvdRORW4eHhqF27Nv755x/N1mleV0WdA28TGBiI3r17Y+3atfjuu+9w++23W14zny0ZPny4zT/wyt5nC8Dm6M2KHYPSPf7449i5cyd+/PFHxMbGWr0myzK6d++OJ554osL3NmzY8Jrrt0ejRo2wd+9eFBcX2/yD+Er21NnWwHhXDrDkyDrN7rrrLixbtgwLFy7EnDlzrlVcuzm6zZ1pe0fZM0p4dHQ09u7di40bN2LDhg3YsGEDPvroI4wYMQIff/zxVd970003YdOmTbh48SJ27dqFGTNmoFmzZoiMjMS2bdtw4MABhIaGolWrVi6ph73brHPnzpZ7nq8kyzKio6Px6aefVvj6lZ3kyo5ULssymjdvXuFjFgEgLi7O6ndX58R83Jo7dy5atmxZ4TKOPF976NChmDZtGsaMGYPq1aujR48eFS6Xk5ODLl26IDw8HM8//zwSExMRFBSE3bt348knnyx39vlq27tp06bIycnB8uXLMXbsWKtOc2XqV9FnPfbYY+jbty/WrFmDjRs3Yvr06ZgzZw42b97sdK6JyPXY6SYit7v99tvx3nvvYefOnU49JgdQz3J//fXXiIuLszlAkiPq1atX4SjS5ksNtboUuOznAeqgSObLO80OHTpU7vMkScKnn36K/v37Y9CgQdiwYYNlhOUaNWogLCwMJpPJ4TP8jvriiy+wYMECLFiwAF26dCn3emJiIgoKCq5Zjnr16mHTpk0oKCiw+sPT3pG8+/bti507d+Krr77C0KFDHavEVZjPOuXk5FgNZGfvZcRXM378eDRo0AAzZsxAREQEnnrqKafXCdi/ze1lzt6Vo9lXNE+rZ0wHBASgb9++6Nu3L2RZxiOPPIJ3330X06dPv+qXap06dcJHH32EL774AiaTCR06dIDBYMBNN91k6XR36NDhmp1/PZ+VnZiYiB9//BEdO3asdIc6MTHxml9oJiYm4q+//kLXrl01q68z6zGf7Q8PD9cku3Xr1kXHjh2xdetWPPzww1ZXA5W1detW/Pfff1i9ejU6d+5smW8eSd4RUVFRWLVqFW666SZ07doV27dvR+3atQFoW7/ExERMmTIFU6ZMQUpKClq2bIl58+bhk08+cWq9ROR6vKebiNzuiSeeQEhICB544AGcPXu23OupqanXfEwQAFy8eBH33nsvsrOz8cwzz2jyB2Tv3r3x+++/Y+fOnZZ5hYWFeO+99xAfH48mTZo4/RlltWnTBtHR0Vi8eLHV43o2bNiAAwcOoE+fPuXeExAQgNWrV+OGG25A37598fvvvwNQz0gNGDAAX331VYV/eDvzaKOy/vnnHzzwwAMYPny4zVGlBw8ejJ07d2Ljxo3lXsvJyUFpaSkAdXuXlpbinXfesbxuMpnw5ptv2lWWhx56CDExMZgyZQoOHz5c7vXMzEzLCMKOMP+hXPY+6MLCwmuecbXX9OnTMXXqVEybNs2q7s6wd5vbq3bt2mjWrBmWLVtmuYUDUC+l3bdvn9Wy5ntm7X3UW0X+++8/q98NBoPlyowrH2V1JfNl46+88gpatGiBiIgIy/xNmzbhzz//tOvS8pCQEKfq4AzzrRovvPBCuddKS0vtKteAAQPw119/4euvvy73mvnM9ODBg3H69OkKn19/8eJFFBYWOlx283OkK7PtkpOTkZiYiNdee80qZ2aVOW69+OKLmDlz5lXHhjB/AVP2jH1xcbHV2BSOiI2NxY8//oiLFy+ie/fuljxrUb8LFy7g0qVLVvMSExMRFhZ2zX2DiMTAM91E5HaJiYn47LPPMGTIEDRu3BgjRoxAs2bNUFxcjF9++QUrV64s91zX06dPW77NLygowP79+7Fy5UpkZGRgypQpGDt2rCZle+qpp/D555/jtttuw4QJE1CtWjV8/PHHSEtLw1dffQWDQdvvKv39/fHKK69g9OjR6NKlC4YOHWp5ZFh8fDwmTZpU4fuqVKmC//u//8Ott96K2267DT/99BOaNWuGl19+GVu2bEG7du0wZswYNGnSBNnZ2di9ezd+/PFHZGdnO11m82BCnTt3LneGpUOHDqhfvz4ef/xxrFu3Drfffrvl0VmFhYXYt28fVq1ahWPHjiEqKgp9+/ZFx44d8dRTT+HYsWNo0qQJVq9eXe5+SluqVq2Kr7/+Gr1790bLli0xfPhwJCcnAwB2796Nzz//vFJXU/To0QN169bF/fffj8cffxxGoxEffvghatSogRMnTji8vorMnTsXubm5ePTRRxEWFobhw4df8z1fffVVuQGeAPV+UXu3uSNeeukl9O/fHx07dsTo0aNx/vx5LFq0CM2aNbPqQFSpUgVNmjTBihUr0LBhQ1SrVg3NmjVzaPyDBx54ANnZ2bj11lsRGxuL48eP480330TLli2veRVLgwYNUKtWLRw6dMiqo9W5c2c8+eSTAGBXpzs5ORnvvPMOXnzxRTRo0ADR0dHlrkBxlS5dumDs2LGYM2cO9u7dix49esDf3x8pKSlYuXIlFi5ceM1H4z3++ONYtWoVBg0ahPvuuw/JycnIzs7GunXrsHjxYlx//fW499578eWXX+Khhx7Cli1b0LFjR5hMJhw8eBBffvml5dnfjjDvc8888wzuvvtu+Pv7o2/fvpbO+NUYDAa8//77uO2229C0aVOMHj0aderUwenTp7FlyxaEh4fjm2++cag8Xbp0qfAKnLI6dOiAqlWrYuTIkZgwYQIkScLy5cudumy+QYMG+P7773HzzTejZ8+e2Lx5M8LDw52u3+HDh9G1a1cMHjwYTZo0gZ+fH77++mucPXsWd999d6XLS0RupNOo6UREyuHDh5UxY8Yo8fHxSkBAgBIWFqZ07NhRefPNN60e12J+PA4ARZIkJTw8XGnatKkyZswY5bfffqtw3ajE47zMUlNTlYEDByqRkZFKUFCQ0rZtW+X//u//NPmMKx8ZZrZixQqlVatWSmBgoFKtWjVl2LBhyqlTp6yWqeiRN1lZWUqTJk2UWrVqKSkpKYqiKMrZs2eVRx99VImLi1P8/f2VWrVqKV27dlXee+89y/tsPYLK/PidsuW78pFhZdvjyp+y78vPz1emTZumNGjQQAkICFCioqKUDh06KK+99ppSXFxsWe6///5T7r33XiU8PFyJiIhQ7r33XmXPnj12PTLM7MyZM8qkSZOUhg0bKkFBQUpwcLCSnJyszJ49W8nNzbUqe0WPn+vSpUu5R0Xt2rVLadeunRIQEKDUrVtXmT9/vs1HhtmzzrKPDDMzmUzK0KFDFT8/P2XNmjU262duL1s/5kck2bPNyz5i6Uqo4LFfX3zxhdKoUSMlMDBQadasmbJu3TplwIABSqNGjayW++WXX5Tk5GQlICDAaj0V5VZRyj+WbdWqVUqPHj2U6OhoyzYfO3askp6ebnO7lDVo0CAFgLJixQrLvOLiYiU4OFgJCAiweiSfolT8yLCMjAylT58+SlhYmALA0n4VtZ2ilH/8lS3mup47d+6a9XjvvfeU5ORkpUqVKkpYWJjSvHlz5YknnlDOnDljWeZqj1H877//lHHjxil16tRRAgIClNjYWGXkyJFWjxEsLi5WXnnlFaVp06ZKYGCgUrVqVSU5OVmZNWuW1f5i6xh35WPAFEV9ZF2dOnUUg8FgtV2v9cgwsz179ih33XWXUr16dSUwMFCpV6+eMnjwYGXTpk1X3V5Xy3NZFeVwx44dyo033qhUqVJFqV27tvLEE08oGzduLFe+Ll26KE2bNq1wvRW1xW+//aaEhYUpnTt3tjyWzJ762cpJVlaW8uijjyqNGjVSQkJClIiICKVdu3bKl19+edU6E5E4JEVxwYgpRERE5LVatmyJGjVqOPx4LyIiIl/Ee7qJiIioQiUlJeXuBd+6dSv++usvywB+REREdHU8001EREQVOnbsGLp164bhw4ejdu3aOHjwIBYvXoyIiAj8888/qF69ut5FJCIiEh4HUiMiIqIKVa1aFcnJyXj//fdx7tw5hISEoE+fPnj55ZfZ4SYiIrITz3QTERERERERuQjv6SYiIiIiIiJyEXa6iYiIiIiIiFzE5+7plmUZZ86cQVhYGCRJ0rs4RERERERE5IEURUF+fj5q164Ng8H2+Wyf63SfOXMGcXFxeheDiIiIiIiIvMDJkycRGxtr83Wf63SHhYUBUDdMeHi4zqWpmMlkQmpqKhITE2E0GvUuDvk4p/O4dy/QpQvw009Ay5ZaF4/MfGA7e8Sx0QfawevZ0YYekUVyD533eWaRNOdEpjXN44gRwNq1QP/+wLJlzq3LhfLy8hAXF2fpY9ric51u8yXl4eHhQne6Q0NDER4ezgMo6c7pPIaG/m8q6D7nFXxgO3vEsdEH2sHr2dGGHpFFcg+d93lmkTTnRKY1zaO///+mHvD/6bVuW+ZAakREREREREQuwk63oK52Iz6RuzmVR4MBCAtTp+Q6PrKdhT82+kg7eDU721D4LJJ7CLDPM4ukKSczrVkey57p9gKSoiiK3oVwp7y8PERERCA3N1fYy8uJiIiIiIhIbPb2LX3unm57mUwmlJSU6PLZiqLgwoULCA4O5mPNSBf+/v6We3EURUFhYSFCQkKYR9IVs0iiYBZJFMwiiYR5tI2d7isoioKMjAzk5OToWobS0lL4+fkxsKSbyMhI1KpVC7Is49SpU0hKSqrcoBj79wODBgErVwJNmmhfUFL5wHZ2Oovu4APt4PXsaEOPyCK5h877PLNImnMi05rm8cEHgSVLgDFjgPfec25dAmCn+wrmDnd0dLRuZ5oVRUFRURECAwPZ6Sa3M19pkZmZCQCIjo52boWXLqkH8EuXNCgd2cTtLAa2g+djG5IjmBfyNqJk+vx566mHY6e7DJPJZOlwV69eXbdymG+zDwoKYqebdFGlShUAQGZmpq77AhERERGRp+Nwh2WY7+EODg7WuSQciZL0Z94PSktLERAQwC+ASHeSJDGLJARmkUTBLJJImEfbeKa7AnoHRZIkBAYG6loGIvN+YDAYUL9+fZ1LQ8QskjiYRRIFs0giYR5t4+lUAZkHUvOxp7mRoBRFQU5OTuXzWL8+sHatOiXX8YHt7HQW3cEH2sHr2dGGHpFFcg+d93lmkTTnRKY1zeN99wGNGqlTL8BOt6D0elyZq61ZswYNGjSA0WjEY489pum6JUnCmjVrNF3ntdx8882a16Oytm7dCkmSNB95X5ZlZGRkQJblyq0gMhLo10+dkuv4wHZ2Oovu4APt4PXsaEOPyCK5h877PLNImnMi05rm8bbbgAMH1KkXYKfbi2RkZGD8+PGoX78+AgMDERcXh759+2LTpk2WZeLj4yFJEiRJQpUqVRAfH4/Bgwdj8+bNVus6duyYZbmyP8OHD3eqjGPHjsXAgQNx8uRJvPDCCxUuo0fn2dOI1Nm/powMYM4cdUquw+0sBraD52MbkiOYF/I2omR6yRIgOFidegF2ur3EsWPHkJycjM2bN2Pu3LnYt28fvvvuO9xyyy149NFHrZZ9/vnnkZ6ejkOHDmHZsmWIjIxEt27dMHv27HLr/fHHH5Genm75eeuttypdxoKCAmRmZqJnz56oXbs2wsLCKr0u8iBnzgBPP61OyXW4ncXAdvB8bENyBPNC3kaUTH//PXDxojr1Aux0C8rRB8o/8sgjkCQJv//+OwYMGICGDRuiadOmmDx5Mn799VerZcPCwlCrVi3UrVsXnTt3xnvvvYfp06djxowZOHTokNWy1atXR61atSw/ERERNstw/vx5jBgxAlWrVkVwcDBuu+02pKSkAFAvfTZ3sm+99VZIkoStW7eWW0d8fDwA4M4774QkSZbfAeCdd95BYmIiAgICcN1112H58uVX3SYzZ85ETEwM/v77bwDA9u3b0alTJ1SpUgVxcXGYMGECCgsLrT77pZdewn333YewsDDUrVsX77333lU/40pFRUWYOnUq6tSpg5CQELRr186qnkuXLkVkZCQ2btyIxo0bIzQ0FL169UJ6erplmdLSUkyYMAGRkZGoXr06nnzySYwcORJ33HEHAGDUqFH46aefsHDhQssVCMeOHbO8f9euXWjTpg2Cg4PRoUMHqzb966+/cMsttyAsLAzh4eFITk7Gn3/+edU6SZKEkJAQ3QcYJGIWSRTMIomCWSSRMI+2sdN9DYoCFBa69+fCBQklJQEA7AtsdnY2vvvuOzz66KMICQkp93qkHfdkTJw4EYqiYO3atQ5uof8ZNWoU/vzzT6xbtw47d+6Eoijo3bs3SkpKrDp/X331FdLT09GhQ4dy6/jjjz8AAB999BHS09Mtv3/99deYOHEipkyZgn/++Qdjx47F6NGjsWXLlnLrUBQF48ePx7Jly7Bt2za0aNECqamp6NWrFwYMGIC///4bK1aswPbt2zFu3Dir986bNw9t2rTBnj178Mgjj+Dhhx8u90XE1YwbNw47d+7EF198gb///huDBg1Cr169LF8+AMCFCxfw2muvYfny5fj5559x4sQJTJ061fL6K6+8gk8//RQfffQRduzYgby8PKvL7RcuXIj27dtjzJgxlisQ4uLiLK8/88wzmDdvHv7880/4+fnhvjIDUAwbNgyxsbH4448/sGvXLjz11FPw9/e/ap0MBgPi4uL4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gFi+fHmZ5bz11lvF8OHDS33t8ccfF08++aTTvLVr1wqr1SouXLgghBCibt264qGHHnK8rmmaqFmzppg9e7YQQojZs2eLGjVqOJYXQogPP/xQABBbt24VQgixatUqAUCcO3euRNk6derkNK9t27Zi9OjRZdaHiIhIL7ynm4iISCfZ2dk4fvw4Onbs6DS/Y8eO2LZtm9O8/v37o3bt2vj5558REhLimL9t2zasX7/e6cy2zWbDxYsXcf78eVSpUgUA0KJFC8froaGhqFatGk6ePFmhcm/btg3bt2/H/PnzHfOEENA0DQcOHMD1119f4v+0X1Jv/z/37NmDFi1aoHLlyo5l2rVr53IZir83AMTGxla4PkRERN7ETjcREZEB3HnnnZg3bx42btyIO+64wzE/NzcXEydORJ8+fUr8TfEObaVKlZxes1gs0DStQmXJzc3Fv/71LwwbNqzEa3Xq1PHK/3klb743ERGRJ7HTTUREpJNq1aohLi4O69evx6233uqYv379+hJnfQcPHoxmzZqhV69e+P777x3Lt2nTBnv27EGjRo18Vu42bdpg165dbv2fTZo0wbx583Dp0iUEBwcDADZv3uy0TFBQEAB55p6IiMio2OkmIiLS0fPPP4/x48ejYcOGaNWqFT799FOkpqY6Xbpt9/TTT8Nms+Guu+7C8uXL0alTJ4wbNw533XUX6tSpg3vvvRdWqxXbtm3Djh078Oqrr7pVtlOnTiE1NdVpXmxsLEaPHo2bb74ZQ4cOxT//+U+EhoZi165dSE5OxsyZM1167wcffBAvvPACnnzySYwZMwaHDx/Gm2++CUCetQaAunXrwmKxYNmyZbjzzjsREhLCx6sREZHhcPRyIiIiHQ0bNgwjR47Es88+i+bNm2PFihX47rvvkJCQUOryI0aMwMSJE3HnnXdiw4YN6NatG5YtW4Yff/wRbdu2xc0334z//ve/qFu3rttlW7BgAVq3bu308+GHH6JFixZYs2YN9u7di86dO6N169YYN24c4uLiXH7vatWqYenSpUhNTUWrVq3wwgsvYNy4cQCKLouvVasWJk6ciDFjxiA6OrrE6OhERERGYBFCCL0LQURERDR//nw89thjyMrKchosjoiIyMh4eTkRERHp4rPPPkODBg1Qq1YtbNu2DaNHj8Z9993HDjcREZkKO91ERESki4yMDIwbNw4ZGRmIjY1Fv379nB59RkREZAa8vJyIiIiIiIjISziQGhEREREREZGXsNNNRERERERE5CXsdBMRERERERF5CTvdRERERERERF7CTjcRERERERGRl7DTTUREREREROQl7HQTEREREREReQk73URERERERERewk43ERERERERkZf8P7D/wAXr26ljAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot_lengths(tokenised_ds_1[\"train\"][\"length\"])" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "66382a92", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/tmp/ipykernel_3421044/194231814.py:36: UserWarning: Creating legend with loc=\"best\" can be slow with large amounts of data.\n", + " plt.tight_layout()\n", + "/n/netscratch/albergo_lab/Lab/brianlck/interpretable-flow/.venv/lib64/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Creating legend with loc=\"best\" can be slow with large amounts of data.\n", + " fig.canvas.print_figure(bytes_io, **kw)\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA90AAAJOCAYAAACqS2TfAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjMsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvZiW1igAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzsnXd4FFXbxu/ZdFKBUEMPIFIEiaKAiAqKgohdsYCoCCoWsGJD3ldALBQroiKKFSzg+9mlqYCNooIIIQSBEEoIqZQkO/P9MdlNNtndzJk5M3Nm8vyuK9duJvuU+zlnn9mT3T0jKYqigCAIgiAIgiAIgiAI7njsToAgCIIgCIIgCIIg3AotugmCIAiCIAiCIAjCJGjRTRAEQRAEQRAEQRAmQYtugiAIgiAIgiAIgjAJWnQTBEEQBEEQBEEQhEnQopsgCIIgCIIgCIIgTIIW3QRBEARBEARBEARhErToJgiCIAiCIAiCIAiToEU3QRAEQRAEQRAEQZgELboJgiB0cuDAAVx55ZVo3LgxJEnCnDlzTI130003ISEhwdQYoeK2a9fO0pi7du2CJElYuHAhV7/t2rXDxRdfzNWnVhYuXAhJkvD777/bEt8u7Jq3deEbj127dml+bH0bOx60a9cON910k//3VatWQZIkrFq1yraceGJnTyEIwjnQopsgCNvIysrCuHHj0KFDB8TGxiIpKQn9+/fH3LlzcezYMf/j2rVrB0mSIEkSPB4PUlJS0KNHD9x222345Zdfgvr2Pb7mT/PmzbnlP3HiRHzzzTeYPHkyFi1ahAsvvLDWY2666aaQuVT/qf6ilGDnwIEDuP/++9GlSxc0aNAA8fHxyMjIwFNPPYWCggK70zOMb6Hy8ccf251KUI4ePYonn3zS8QupV155hfs/egDgySefDHi+N2jQAF27dsVjjz2GoqIi7vGsZu3atXjyySdtea75/kEnSRKeeuqpoI+5/vrrIUmSkP/8IQiifhBpdwIEQdRPvvjiC1x11VWIiYnBqFGj0L17d5SVleGnn37CAw88gC1btmD+/Pn+x/fq1Qv33XcfAKC4uBhbt27FkiVL8Prrr2PixImYNWtWrRjnn38+Ro0aFXAsLi6Om4YVK1ZgxIgRuP/++0M+Zty4cRg8eLD/9+zsbDzxxBO47bbbMGDAAP/x9PR0bnnx5vXXX4csy3anEZLffvsNQ4cORUlJCW644QZkZGQAAH7//Xc8/fTT+OGHH/Dtt9/anKW7OXr0KKZOnQoAOOecc+xNRiM33ngjrr32WsTExPiPvfLKK0hNTTXtn2CvvvoqEhISUFJSgm+//RbTpk3DihUrsGbNGkiSZEpMK1i7di2mTp2Km266CSkpKQF/27ZtGzwe89/jiY2NxQcffIDHHnss4HhpaSmWLVuG2NhY03MgCIIIBS26CYKwnOzsbFx77bVo27YtVqxYgRYtWvj/duedd2LHjh344osvAmzS0tJwww03BBybOXMmrrvuOsyePRudOnXC7bffHvD3zp0717LhycGDB2u9wKxJ37590bdvX//vv//+O5544gn07dvX1Nx4EhUVZXcKISkoKMBll12GiIgIbNy4EV26dAn4+7Rp0/D666/blB0hMhEREYiIiLA05pVXXonU1FQAwPjx43HFFVfg008/xc8//xzQJ1hRFAXHjx/n+k9FXlT/p4aZDB06FJ9++in++OMP9OzZ03982bJlKCsrw4UXXogVK1Zwi3f06FE0aNCAm79QlJaWIj4+3vQ4BEGYC328nCAIy3nmmWdQUlKCN998M2DB7aNjx46455576vQTFxeHRYsWoVGjRpg2bRoUReGS386dO3HVVVehUaNGaNCgAc4888yAfwL4vt+pKApefvll/0cbjbBkyRJkZGQgLi4OqampuOGGG5CTk1On3aZNm9CkSROcc845KCkpAQDk5OTg5ptvRrNmzRATE4Nu3bphwYIFAXa+jysvXrwY06ZNQ6tWrRAbG4tBgwZhx44dAY+t+Z3uc845J+TH5Kt/NLegoAD33nsvWrdujZiYGHTs2BEzZ86s9a55QUEBbrrpJiQnJyMlJQWjR4/W/DHV1157DTk5OZg1a1atBTcANGvWrNY7XwDw008/oU+fPoiNjUWHDh3wzjvvBPzd93HgmgT7HrDvO511+QzGkSNH0KdPH7Rq1Qrbtm3ToDg8Wmru+zjuc889h/nz5yM9PR0xMTE4/fTT8dtvv9XyuWTJEnTt2hWxsbHo3r07Pvvss4A5sWvXLjRp0gQAMHXqVP9cePLJJwP85OTk4NJLL0VCQgKaNGmC+++/H16vN+AxH374ITIyMpCYmIikpCT06NEDc+fODau5d+/euPzyywOO9ejRA5Ik4c8///Qf++ijjyBJErZu3Qqg9li2a9cOW7ZswerVq/0aar5rf+LECUyaNAlNmjRBfHw8LrvsMhw6dChsfuE477zzAKj/iAQAWZYxZ84cdOvWDbGxsWjWrBnGjRuHI0eOBNj55tw333yD0047DXFxcXjttdcAqHNg4sSJaNeuHWJiYtCqVSuMGjUKeXl5ATqmTJmCjh07IiYmBq1bt8aDDz6IEydOBMSRJAkTJkzA0qVL0b17d38/+frrr/2PefLJJ/HAAw8AANq3b++vXfW6avnkwC+//IILL7wQycnJaNCgAQYOHIg1a9ZormXfvn3Rvn17vP/++wHH33vvPVx44YVo1KhRLZtly5Zh2LBhaNmyJWJiYpCeno7//ve/teblOeecg+7du2P9+vU4++yz0aBBAzzyyCMhc3n77bcRGRnpr4tWfb6+8/fff+O6665Dw4YNcdZZZwEA9u/fjzFjxqBVq1aIiYlBixYtMGLECE17EhAEYT/0TjdBEJbzv//9Dx06dEC/fv0M+0pISMBll12GN998E3///Te6devm/9vx48cDXmgCQGJiYth3Xg4cOIB+/frh6NGjuPvuu9G4cWO8/fbbuOSSS/Dxxx/jsssuw9lnn41FixbhxhtvDPoRdlYWLlyIMWPG4PTTT8eMGTNw4MABzJ07F2vWrMHGjRtDvpv+22+/YciQITjttNOwbNkyxMXF4cCBAzjzzDP9L5abNGmCr776CrfccguKiopw7733Bvh4+umn4fF4cP/996OwsBDPPPMMrr/++pDflQeARx99FLfeemvAsXfffRfffPMNmjZtCkB9F2jgwIHIycnBuHHj0KZNG6xduxaTJ09Gbm6uf9M5RVEwYsQI/PTTTxg/fjxOPvlkfPbZZxg9erSm2n3++eeIi4vDlVdeqenxALBjxw5ceeWVuOWWWzB69GgsWLAAN910EzIyMgLmDwt6fObl5eH8889Hfn4+Vq9ebfgrBlpr7uP9999HcXExxo0bB0mS8Mwzz+Dyyy/Hzp07/Z9u+OKLL3DNNdegR48emDFjBo4cOYJbbrkFaWlpfj9NmjTBq6++ittvvx2XXXaZfwF8yimn+B/j9XoxZMgQnHHGGXjuuefw/fff4/nnn0d6err/EyrfffcdRo4ciUGDBmHmzJkAgK1bt2LNmjVh/wk3YMAAfPDBB/7f8/PzsWXLFng8Hvz444/+PH788Uc0adIEJ598clA/c+bMwV133YWEhAQ8+uijANR/2lTnrrvuQsOGDTFlyhTs2rULc+bMwYQJE/DRRx+FHpgwZGVlAQAaN24MQP06iq8f3H333cjOzsZLL72EjRs3Ys2aNQGfOtm2bRtGjhyJcePGYezYsTjppJNQUlKCAQMGYOvWrbj55pvRu3dv5OXl4fPPP8fevXuRmpoKWZZxySWX4KeffsJtt92Gk08+GX/99Rdmz56N7du3Y+nSpQE5/vTTT/j0009xxx13IDExES+88AKuuOIK7N69G40bN8bll1+O7du344MPPsDs2bP97+T7/hGjhRUrVuCiiy5CRkYGpkyZAo/Hg7feegvnnXcefvzxR/Tp00eTn5EjR+Ldd9/F008/DUmSkJeXh2+//RaLFi0K+EeBj4ULFyIhIQGTJk1CQkICVqxYgSeeeAJFRUV49tlnAx57+PBhXHTRRbj22mtxww031JobPubPn4/x48fjkUce8X/HnFXfVVddhU6dOmH69On+fyZfccUV2LJlC+666y60a9cOBw8exHfffYfdu3dbvtElQRA6UAiCICyksLBQAaCMGDFCs03btm2VYcOGhfz77NmzFQDKsmXL/McABP156623wsa69957FQDKjz/+6D9WXFystG/fXmnXrp3i9XoDYtx5552adSiKovz2228BeZSVlSlNmzZVunfvrhw7dsz/uP/7v/9TAChPPPGE/9jo0aOV+Ph4RVEU5aefflKSkpKUYcOGKcePH/c/5pZbblFatGih5OXlBcS99tprleTkZOXo0aOKoijKypUrFQDKySefrJw4ccL/uLlz5yoAlL/++isgbtu2bUNqWrNmjRIVFaXcfPPN/mP//e9/lfj4eGX79u0Bj3344YeViIgIZffu3YqiKMrSpUsVAMozzzzjf0xFRYUyYMAATePVsGFDpWfPnmEfU522bdsqAJQffvjBf+zgwYNKTEyMct999/mPTZkyRQl2inzrrbcUAEp2djazT5/tb7/9puTm5irdunVTOnTooOzatavOvH3jtWTJkpCP0Vrz7OxsBYDSuHFjJT8/3/+4ZcuWKQCU//3vf/5jPXr0UFq1aqUUFxf7j61atUoBEDAnDh06pABQpkyZUiuv0aNHKwCU//znPwHHTz31VCUjI8P/+z333KMkJSUpFRUV4YtRgyVLligAlL///ltRFEX5/PPPlZiYGOWSSy5RrrnmGv/jTjnlFOWyyy7z/x5sLLt166YMHDiwVgzfYwcPHqzIsuw/PnHiRCUiIkIpKCgIm6NvPm3btk05dOiQkp2drbz22mtKTEyM0qxZM6W0tFT58ccfFQDKe++9F2D79ddf1zrum3Nff/11wGOfeOIJBYDy6aef1srBl/eiRYsUj8cT0OMURVHmzZunAFDWrFnjPwZAiY6OVnbs2OE/9scffygAlBdffNF/7Nlnn61Vy+q5jh492v+7by6vXLnSn1enTp2UIUOGBNT26NGjSvv27ZXzzz+/ls/q+Obzs88+q2zevDmgf7/88stKQkKCUlpaGtA/q8eoybhx45QGDRoE9NWBAwcqAJR58+YF1ec7P82dO1eRJEn573//6/87iz7fPBk5cmRAjCNHjvg1EgThTOjj5QRBWIpvp97ExERuPn070hYXFwccHzFiBL777ruAnyFDhoT19eWXX6JPnz7+j/T5/N92223YtWsX/v77b255A+p3vA8ePIg77rgjYKOfYcOGoUuXLrW+2w4AK1euxJAhQzBo0CB8+umn/nfuFUXBJ598guHDh0NRFOTl5fl/hgwZgsLCQmzYsCHA15gxYxAdHe3/3be5286dOzXlv3//flx55ZXo1asXXnnlFf/xJUuWYMCAAWjYsGFAHoMHD4bX68UPP/wAQK13ZGRkwPfxIyIicNddd2mKX1RUxDyXunbtGrCJXZMmTXDSSSdp1mzU5969ezFw4ECUl5fjhx9+QNu2bXXHrY7Wmvu45ppr0LBhQ//vNcd+3759+OuvvzBq1KiAXZ8HDhyIHj16MOc3fvz4gN8HDBgQUJ+UlBSUlpbiu+++Y/Lry9un78cff8Tpp5+O888/Hz/++CMA9SPXmzdvDhgjPdx2220BXzsYMGAAvF4v/v33X032J510Epo0aYL27dtj3Lhx6NixI7744gs0aNAAS5YsQXJyMs4///yA8cvIyEBCQgJWrlwZ4Kt9+/a1+tknn3yCnj174rLLLqsV25f3kiVLcPLJJ6NLly4BcXwfda8ZZ/DgwQGfwjjllFOQlJRk6PlSnU2bNiEzMxPXXXcdDh8+7M+ntLQUgwYNwg8//KB5I8du3brhlFNO8X/y4f3338eIESNCfve6+nfgi4uLkZeXhwEDBuDo0aP4559/Ah4bExODMWPGhIz9zDPP4J577sHMmTMDvtKiR1/N50pcXByio6OxatWqWl81IAjCGdDHywmCsJSkpCQAtRfIRvB9l7nm4qtVq1YBO4dr4d9//8UZZ5xR67jvI6n//vsvunfvrjPT4PEA9cV4Tbp06YKffvop4Njx48cxbNgwZGRkYPHixYiMrGrjhw4dQkFBAebPnx+w83t1Dh48GPB7mzZtAn73LcK0vLCrqKjA1VdfDa/XG7D4B4DMzEz8+eefIT9i6svj33//RYsWLWpdyidYPYKRlJTEPJdqagZU3UZezLL4vPHGGxEZGYmtW7dyvYSd1pr7qGvsfXOzY8eOtXx17Nix1j9wwhEbG1srr5r1ueOOO7B48WJcdNFFSEtLwwUXXICrr7466KX4qtOsWTN06tQJP/74I8aNG4cff/wR5557Ls4++2zcdddd2LlzJ7Zu3QpZlg0vuo08XwB1UZyUlISoqCi0atUqYDGbmZmJwsJC/1c0alJz/Nq3b1/rMVlZWbjiiivC5pCZmYmtW7fqnieA8edLzXwAhP1KSWFhYcA/iMJx3XXX4fnnn8fEiROxdu3asN+93rJlCx577DGsWLGi1qXbCgsLA35PS0sL+AdldVavXo0vvvgCDz30UMD3uAF9+mqObUxMDGbOnIn77rsPzZo1w5lnnomLL74Yo0aN4tpDCIIwD1p0EwRhKUlJSWjZsiU2b97MzafPV7DFgduIiYnB0KFDsWzZMnz99de4+OKL/X/zvVtyww03hHyBV/17tgBC7t6saNiU7oEHHsC6devw/fffo1WrVgF/k2UZ559/Ph588MGgtp07d67Tvxa6dOmCTZs2oaysLOQL4ppo0RxqY7yaGyyx+PRx+eWX45133sHcuXMxY8aMutLVDGvNjYw9K1p2CW/atCk2bdqEb775Bl999RW++uorvPXWWxg1ahTefvvtsLZnnXUWli9fjmPHjmH9+vV44okn0L17d6SkpODHH3/E1q1bkZCQgFNPPdUUHVprdvbZZ/u/81wTWZbRtGlTvPfee0H/XnORrHenclmW0aNHj6CXWQSA1q1bB/xu9jzx9a1nn30WvXr1CvoYlutrjxw5EpMnT8bYsWPRuHFjXHDBBUEfV1BQgIEDByIpKQn/+c9/kJ6ejtjYWGzYsAEPPfRQrXefw9W7W7duKCgowKJFizBu3LiARbMefcFi3XvvvRg+fDiWLl2Kb775Bo8//jhmzJiBFStWGJ7XBEGYDy26CYKwnIsvvhjz58/HunXrDF0mB1Df5f7ss8/QunXrkBsksdC2bdugu0j7PmrI66PA1eMB6qZIvo93+ti2bVuteJIk4b333sOIESNw1VVX4auvvvLvsNykSRMkJibC6/Uyv8PPyocffog5c+Zgzpw5GDhwYK2/p6eno6SkpM482rZti+XLl6OkpCTghafWnbyHDx+OdevW4ZNPPsHIkSPZRITB965TQUFBwEZ2Wj9GHI677roLHTt2xBNPPIHk5GQ8/PDDhn0C2muuFd/cq7mbfbBjvK4xHR0djeHDh2P48OGQZRl33HEHXnvtNTz++ONh/6k2YMAAvPXWW/jwww/h9XrRr18/eDwenHXWWf5Fd79+/epc/Nt5rez09HR8//336N+/v+4FdXp6ep3/0ExPT8cff/yBQYMGcdNrxI/v3f6kpCQuc7dNmzbo378/Vq1ahdtvvz3g00DVWbVqFQ4fPoxPP/0UZ599tv+4byd5FlJTU/Hxxx/jrLPOwqBBg/DTTz+hZcuWAPjqS09Px3333Yf77rsPmZmZ6NWrF55//nm8++67hvwSBGE+9J1ugiAs58EHH0R8fDxuvfVWHDhwoNbfs7Ky6rxMEAAcO3YMN954I/Lz8/Hoo49yeQE5dOhQ/Prrr1i3bp3/WGlpKebPn4927dqha9euhmNU57TTTkPTpk0xb968gMv1fPXVV9i6dSuGDRtWyyY6OhqffvopTj/9dAwfPhy//vorAPUdqSuuuAKffPJJ0BfeRi5tVJ3Nmzfj1ltvxQ033BByV+mrr74a69atwzfffFPrbwUFBaioqACg1ruiogKvvvqq/+9erxcvvviiplzGjx+PFi1a4L777sP27dtr/f3gwYP+HYRZ8L1Qrv496NLS0jrfcdXK448/jvvvvx+TJ08O0G4ErTXXSsuWLdG9e3e88847/q9wAOpHaf/666+Ax/q+M6v1Um/BOHz4cMDvHo/H/8mMmpeyqonvY+MzZ87EKaecguTkZP/x5cuX4/fff9f00fL4+HhDGozg+6rGf//731p/q6io0JTXFVdcgT/++AOfffZZrb/53pm++uqrkZOTE/T69ceOHUNpaSlz7r7rSOupXUZGBtLT0/Hcc88FzDMfevrWU089hSlTpoTdG8L3D5jq79iXlZUF7E3BQqtWrfD999/j2LFjOP/88/3zmYe+o0eP4vjx4wHH0tPTkZiYWOdzgyAIMaB3ugmCsJz09HS8//77uOaaa3DyySdj1KhR6N69O8rKyrB27VosWbKk1nVdc3Jy/P/NLykpwd9//40lS5Zg//79uO+++zBu3DguuT388MP44IMPcNFFF+Huu+9Go0aN8PbbbyM7OxuffPIJPB6+/6uMiorCzJkzMWbMGAwcOBAjR470XzKsXbt2mDhxYlC7uLg4/N///R/OO+88XHTRRVi9ejW6d++Op59+GitXrsQZZ5yBsWPHomvXrsjPz8eGDRvw/fffIz8/33DOvs2Ezj777FrvsPTr1w8dOnTAAw88gM8//xwXX3yx/9JZpaWl+Ouvv/Dxxx9j165dSE1NxfDhw9G/f388/PDD2LVrF7p27YpPP/201vcpQ9GwYUN89tlnGDp0KHr16oUbbrgBGRkZAIANGzbggw8+0PVpigsuuABt2rTBLbfcggceeAARERFYsGABmjRpgt27dzP7C8azzz6LwsJC3HnnnUhMTMQNN9xQp80nn3xSa4MnQP2+qNaaszB9+nSMGDEC/fv3x5gxY3DkyBG89NJL6N69e8ACIi4uDl27dsVHH32Ezp07o1GjRujevTvT/ge33nor8vPzcd5556FVq1b4999/8eKLL6JXr151foqlY8eOaN68ObZt2xaw0Dr77LPx0EMPAYCmRXdGRgZeffVVPPXUU+jYsSOaNm1a6xMoZjFw4ECMGzcOM2bMwKZNm3DBBRcgKioKmZmZWLJkCebOnVvnpfEeeOABfPzxx7jqqqtw8803IyMjA/n5+fj8888xb9489OzZEzfeeCMWL16M8ePHY+XKlejfvz+8Xi/++ecfLF682H/tbxZ8z7lHH30U1157LaKiojB8+HD/YjwcHo8Hb7zxBi666CJ069YNY8aMQVpaGnJycrBy5UokJSXhf//7H1M+AwcODPoJnOr069cPDRs2xOjRo3H33XdDkiQsWrTI0MfmO3bsiG+//RbnnHMOhgwZghUrViApKcmwvu3bt2PQoEG4+uqr0bVrV0RGRuKzzz7DgQMHcO211+rOlyAIC7Fp13SCIAhl+/btytixY5V27dop0dHRSmJiotK/f3/lxRdfDLhci+/yOAAUSZKUpKQkpVu3bsrYsWOVX375Jahv6Licl4+srCzl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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot_lengths(tokenised_ds[\"train\"][\"length\"], log_scale=True)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/FlexMDM/pngs/len_trace_mdm.png b/FlexMDM/pngs/len_trace_mdm.png new file mode 100644 index 0000000000000000000000000000000000000000..7910f9608a43343af30802fc678ca9858d698a43 Binary files /dev/null and b/FlexMDM/pngs/len_trace_mdm.png differ diff --git a/FlexMDM/pngs/len_trace_vlmdm.png b/FlexMDM/pngs/len_trace_vlmdm.png new file mode 100644 index 0000000000000000000000000000000000000000..451ea5c004c8d9e583205ad7f030de8a4615f475 Binary files /dev/null and b/FlexMDM/pngs/len_trace_vlmdm.png differ diff --git a/FlexMDM/pngs/length_distribution_generated_samples_mdm.png b/FlexMDM/pngs/length_distribution_generated_samples_mdm.png new file mode 100644 index 0000000000000000000000000000000000000000..45ba1f007df97e89db6a766b9b3244e27cc0d65b Binary files /dev/null and b/FlexMDM/pngs/length_distribution_generated_samples_mdm.png differ diff --git a/FlexMDM/pngs/length_distribution_generated_samples_vlmdm_wikitext2.png b/FlexMDM/pngs/length_distribution_generated_samples_vlmdm_wikitext2.png new file mode 100644 index 0000000000000000000000000000000000000000..1c591c23b9e6c4ab3eab51e3f52660abf7f87a9b Binary files /dev/null and b/FlexMDM/pngs/length_distribution_generated_samples_vlmdm_wikitext2.png differ diff --git a/FlexMDM/pngs/length_distribution_wikitext2.png b/FlexMDM/pngs/length_distribution_wikitext2.png new file mode 100644 index 0000000000000000000000000000000000000000..590f0ef0d867b8623ce561141531b172e526a513 Binary files /dev/null and b/FlexMDM/pngs/length_distribution_wikitext2.png differ diff --git a/FlexMDM/pyproject.toml b/FlexMDM/pyproject.toml new file mode 100644 index 0000000000000000000000000000000000000000..93479df757b81edae49ee934237b91b64ead305e --- /dev/null +++ b/FlexMDM/pyproject.toml @@ -0,0 +1,36 @@ +[project] +name = "interpretable-flow" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.11" +dependencies = [ + "black>=25.1.0", + "datasets>=3.6.0", + "einops>=0.8.1", + "flash-attn>=2.7.4.post1", + "huggingface>=0.0.1", + "huggingface-hub[hf-transfer]>=0.30.2", + "hydra-core>=1.3.2", + "ipykernel>=6.29.5", + "matplotlib>=3.10.3", + "mauve-text>=0.4.0", + "omegaconf>=2.3.0", + "pre-commit>=4.2.0", + "pytorch-lightning>=2.5.1", + "ruff>=0.11.10", + "sentencepiece>=0.2.0", + "torch>=2.6.0", + "torchaudio>=2.6.0", + "torchvision>=0.21.0", + "transformers>=4.52.3", + "wandb>=0.20.1", + "zstandard>=0.23.0", + "zstd>=1.5.7.1", +] + +[dependency-groups] +dev = [ + "ipykernel>=6.29.5", + "jupyter>=1.1.1", +] diff --git a/FlexMDM/sampling.py b/FlexMDM/sampling.py new file mode 100644 index 0000000000000000000000000000000000000000..f4d347380bf7e44d1e63403e070a89ec0b5a7b49 --- /dev/null +++ b/FlexMDM/sampling.py @@ -0,0 +1,546 @@ +# TOOD: This is a very inflexible sampling algorithm -- Only works for semiautoregressive with one token addition at one time +# TODO: This code is quite bad, we'd like to refactor, can we use ein / einops? +import torch +from dataclasses import dataclass +from typing import Any, Literal, Optional + +from lightning_modules.mdm import MaskedDiffusionModule + + +@dataclass +class SamplingTraceDatapoint: + t: float + event_type: Literal["insertion", "change"] + position: int + token: Any + + +@dataclass +class SamplingResult: + samples: torch.Tensor + # Trace is supposed to be processed sequentially as updates are not commutative + trace: Optional[list[SamplingTraceDatapoint]] + + def __iter__(self): + yield from [self.samples, self.trace] + + +# Sample from categorical distribution for each position using the transition probabilities +def _sample_tokens(probs: torch.Tensor) -> torch.Tensor: + """Sample one token per position from probability distribution. + Args: + probs: [batch_size, seq_len, vocab_size] transition probabilities + Returns: + [batch_size, seq_len] sampled token indices + """ + batch_size, seq_len, vocab_size = probs.shape + flat_probs = probs.view(-1, vocab_size) + samples = torch.multinomial(flat_probs, num_samples=1) + return samples.view(batch_size, seq_len) + + +@torch.no_grad() +def mdm_euler_sampling( + model: MaskedDiffusionModule, + steps: int, + mask: int, + pad: int, + batch_size: int, + max_length: int, + return_trace: bool = False, +): + assert not return_trace, "Trace is not yet implemented in MDM Euler sampling" + device = model.device + xt = torch.full((batch_size, max_length), mask, dtype=torch.int64, device=device) + + dt = 1.0 / steps + t = torch.zeros(batch_size, device=device) + + for i in range(steps): + print("i-th sampling step") + # ——— predict and convert rates ——— + pred_rate = model(xt, t) + pred_rate = model.interpolant.to_actual_rate(xt, pred_rate, t) + unmask_rate = pred_rate.unmask_rate + + # ——— unmask step (Euler) ——— + mask_pos = (xt == mask).nonzero(as_tuple=True) + unmask_rate[xt != mask] = 0 + unmask_rate[*mask_pos, mask] = 0 + unmask_rate[*mask_pos, mask] = -unmask_rate[*mask_pos, :].sum(dim=1) + trans_prob = (unmask_rate * dt).clamp(0.0, 1.0) + + _xt = xt.clone() + trans_prob.scatter_add_( + 2, + _xt.unsqueeze(-1), + torch.ones_like(_xt.unsqueeze(-1), dtype=trans_prob.dtype), + ) + + if i == steps - 1: + print("Final step, removing mask token from sampling") + trans_prob[*mask_pos, mask] = 0.0 + print(trans_prob[*mask_pos, mask]) + + new_xt = _sample_tokens(trans_prob) + new_xt = torch.where(xt != mask, xt, new_xt) + + xt = new_xt + t = t + dt + + return xt, [] + + +@torch.no_grad() +def any_order_mask_insertion_euler_sampling( + model: torch.nn.Module, + steps: int, + mask: int, + pad: int, + batch_size: int, + max_length: int, + return_trace: bool = False, +) -> SamplingResult: + device = model.device + + # 1) Initialize all‑pad sequence and trace + xt = torch.full((batch_size, max_length), pad, dtype=torch.int64, device=device) + sampling_trace = [] + + dt = 1.0 / steps + t = torch.zeros(batch_size, device=device) + + # Precompute row indices for scatter + batch_idx_L = ( + torch.arange(batch_size, device=device) + .view(batch_size, 1) + .expand(batch_size, max_length) + ) + pos_idx_L = ( + torch.arange(max_length, device=device) + .view(1, max_length) + .expand(batch_size, max_length) + ) + sampling_trace = [[] for _ in range(batch_size)] if return_trace else None + + for i in range(steps): + # ——— predict and convert rates ——— + pred_rate = model(xt, t) + pred_rate = model.interpolant.to_actual_rate(xt, pred_rate, t) + unmask_rate = pred_rate.unmask_rate # (B, L, V) + len_rate = pred_rate.length_rate # (B, L+1) + + # ——— unmask step (Euler) ——— + mask_pos = (xt == mask).nonzero(as_tuple=True) + unmask_rate[xt != mask] = 0 + unmask_rate[*mask_pos, mask] = 0 + unmask_rate[*mask_pos, mask] = -unmask_rate[*mask_pos, :].sum(dim=1) + trans_prob = (unmask_rate * dt).clamp(0.0, 1.0) + + # add “stay” probability + _xt = xt.clone() + _xt[xt == pad] = mask + trans_prob.scatter_add_( + 2, + _xt.unsqueeze(-1), + torch.ones_like(_xt.unsqueeze(-1), dtype=trans_prob.dtype), + ) + + if i == steps - 1: + print("Final step, removing mask token from sampling") + trans_prob[*mask_pos, mask] = ( + 0.0 # remove mask token from sampling at the last step + ) + print(trans_prob[*mask_pos, mask]) + + new_xt = _sample_tokens(trans_prob) + new_xt[xt == pad] = pad + new_xt = torch.where((xt != mask) & (xt != pad), xt, new_xt) + + if i != steps - 1: + # ——— gap-wise insertion refactored — compute new length, fill masks, scatter tokens ——— + ext = torch.bernoulli((len_rate * dt).clamp(0.0, 1.0)).long() # (B, L+1) + xt_len = xt.ne(pad).sum(dim=1) # (B,) + gaps = torch.arange(max_length + 1, device=device).view(1, -1) + ext = ext * (gaps <= xt_len.view(batch_size, 1)).long() + total_ext = ext.sum(dim=1) + valid = xt_len + total_ext <= max_length + ext = ext * valid.view(batch_size, 1).long() + + ext_ex = ext.int().cumsum(dim=1) # (B, L+1) + new_len = xt_len + total_ext # (B,) + + xt_tmp = torch.full_like(xt, pad) + mask_fill = pos_idx_L < new_len.view(batch_size, 1) + xt_tmp[mask_fill] = mask + + new_pos_orig = pos_idx_L + ext_ex[:, :max_length] # (B, L) + orig_mask = pos_idx_L < xt_len.view(batch_size, 1) + flat_b = batch_idx_L[orig_mask] + flat_p = new_pos_orig[orig_mask] + xt_tmp[flat_b, flat_p] = new_xt[orig_mask] + else: + xt_tmp = new_xt + + if return_trace: + # Check if the token was changed + for i in range(batch_size): + for j in range(max_length): + if xt[i, j] != pad and xt[i, j] != new_xt[i, j]: + sampling_trace[i].append( + SamplingTraceDatapoint( + t=t[i].item(), + event_type="change", + position=j, + token=new_xt[i, j].item(), + ) + ) + + # Check if a new token was inserted + for j in range(max_length): + id = max_length - j - 1 + if ext[i, id]: + sampling_trace[i].append( + SamplingTraceDatapoint( + t=t[i].item(), + event_type="insertion", + position=id, + token=mask, + ) + ) + + xt = xt_tmp + t = t + dt + + return xt, sampling_trace + + +@torch.no_grad() +def mdm_tau_leaping_sampling( + model: MaskedDiffusionModule, + steps: int, + mask: int, + pad: int, + batch_size: int, + max_length: int, + return_trace: bool = False, +): + assert not return_trace, "Trace is not yet supported" + device = model.device + xt = torch.full((batch_size, max_length), mask, dtype=torch.int64, device=device) + dt = 1.0 / steps + t = torch.zeros(batch_size, device=device) + + for i in range(steps): + # ——— predict and convert rates ——— + pred = model(xt, t) + pred = model.interpolant.to_actual_rate(xt, pred, t) + unmask_rate = pred.unmask_rate # (B, L, V) + + if i == steps - 1: + # last step: deterministic unmask via argmax + mask_pos = xt == mask # (B, L) + new_token = unmask_rate.argmax(dim=2) # (B, L) + new_xt = xt.clone() + new_xt[mask_pos] = new_token[mask_pos] + new_xt = torch.where(xt != mask, xt, new_xt) + xt = new_xt + t = t + dt + continue + # tau-leaping via Poisson counts + counts = torch.poisson(unmask_rate * dt).long() + mask_pos = xt == mask # (B, L) + # zero out non-mask positions and mask→mask + counts[~mask_pos.unsqueeze(-1).expand_as(counts)] = 0 + counts[..., mask] = 0 + # only accept exactly one event + sum_c = counts.sum(dim=2) # (B, L) + one_event = sum_c == 1 + new_token = counts.argmax(dim=2) # (B, L) + + # build new xt + new_xt = xt.clone() + new_xt[one_event] = new_token[one_event] + # keep pads and already-unmasked tokens + new_xt = torch.where(xt != mask, xt, new_xt) + xt = new_xt + t = t + dt + + return xt, [] + +# Not used in production, for debugging purposes +lengths = {4: 0.1, 16: 0.4, 32: 0.4, 64: 0.1} + +def binomial_mass(k, n, p): + """ + Calculate the probability mass function (PMF) for a binomial distribution. + + Args: + k (int): Number of successes + n (int): Number of trials + p (float): Probability of success in a single trial + + Returns: + float: Probability mass P(X = k) + """ + import math + + # Calculate binomial coefficient (n choose k) + try: + binom_coef = math.factorial(n) / (math.factorial(k) * math.factorial(n - k)) + except ValueError: + # Handle cases where k > n or negative values + return 0.0 + + # Calculate probability mass + return binom_coef * (p ** k) * ((1 - p) ** (n - k)) + +def calculate_rate_batch(alpha_t, len_t): + """ + Calculate rate for a batch of alpha_t and len_t values. + + Args: + alpha_t (torch.Tensor): Tensor of shape (batch_size,) + len_t (torch.Tensor): Tensor of shape (batch_size,) + + Returns: + torch.Tensor: Tensor of shape (batch_size,) containing calculated rates + """ + batch_size = alpha_t.shape[0] + device = alpha_t.device + + # Initialize tensors for numerator and denominator + nom = torch.zeros(batch_size, device=device) + denom = torch.zeros(batch_size, device=device) + + for length, probability in lengths.items(): + # Create mask for valid entries where len_t <= length + valid_mask = (len_t <= length) & (len_t >= 0) + + if not valid_mask.any(): + continue + + valid_indices = valid_mask.nonzero(as_tuple=True)[0] + valid_len_t = len_t[valid_indices] + valid_alpha_t = alpha_t[valid_indices] + + # Calculate binomial probabilities efficiently using torch distribution + binom_dist = torch.distributions.Binomial(total_count=length, probs=valid_alpha_t) + binom_probs = binom_dist.log_prob(valid_len_t).exp() + + # Update numerator and denominator for valid indices + nom[valid_indices] += (length - valid_len_t) * probability * binom_probs + denom[valid_indices] += probability * binom_probs + + # Handle division by zero in a vectorized way + result = torch.zeros_like(nom) + div_mask = denom > 0 + result[div_mask] = nom[div_mask] / (denom[div_mask]) + + return result + +# Keep the original function for backward compatibility +def calculate_rate(alpha_t, len_t): + """Legacy scalar version of calculate_rate""" + if isinstance(alpha_t, torch.Tensor) and alpha_t.ndim > 0: + return calculate_rate_batch(alpha_t, len_t) + + nom, denom = 0, 0 + for length, probability in lengths.items(): + if length >= len_t: + nom += (length - len_t) * probability * binomial_mass(len_t, length, alpha_t) + denom += probability * binomial_mass(len_t, length, alpha_t) + + if denom == 0: + return 0.0 + + return nom /denom + + +@torch.no_grad() +@torch.compile(mode="reduce-overhead") +def any_order_mask_insertion_tau_leaping_sampling( + model: torch.nn.Module, + steps: int, + mask: int, + pad: int, + batch_size: int, + max_length: int, + return_trace: bool = False, + confidence_based_sampling: bool = True, # whether to use confidence-based decoding + alpha: float = 5.0, # hyperparameter for window size calculation + max_window: int = 32, # Maximum window size for sliding window + confidence_method: str = "prob_diff", # "position", "top_prob", "prob_diff", "entropy" + use_sliding_window: bool = False, # whether to use sliding window for position selection +) -> SamplingResult: + device = model.device + xt = torch.full((batch_size, max_length), pad, dtype=torch.int64, device=device) + sampling_trace = [] + dt = 1.0 / steps + t = torch.zeros(batch_size, device=device) + + # Precompute row indices for scatter + batch_idx_L = ( + torch.arange(batch_size, device=device) + .view(batch_size, 1) + .expand(batch_size, max_length) + ) + pos_idx_L = ( + torch.arange(max_length, device=device) + .view(1, max_length) + .expand(batch_size, max_length) + ) + + for i in range(steps): + # --- predict rates --- + pred = model(xt, t) + xt_len = (xt != pad).sum(dim=1) + pred = model.interpolant.to_actual_rate(xt, pred, t) + unmask_rate = pred.unmask_rate # (B, L, V) + len_rate = pred.length_rate # (B, L+1) + + if i == steps - 1: + # last step: deterministic unmask via argmax + mask_pos = xt == mask + new_token = unmask_rate.argmax(dim=2) + new_xt = xt.clone() + new_xt[mask_pos] = new_token[mask_pos] + new_xt = torch.where(xt == pad, pad, new_xt) + new_xt = torch.where((xt != mask) & (xt != pad), xt, new_xt) + xt = new_xt + t = t + dt + continue + + # --- confidence-based decoding --- + if confidence_based_sampling > 0.0: + # Confidence-based unmasking (vectorized) + mask_positions = (xt == mask) # (B, L) + num_mask_positions = mask_positions.sum(dim=1) # (B,) + + # 1. Determine number of tokens to unmask using Poisson + unmask_counts = torch.poisson(num_mask_positions.float() * dt).long() # (B,) + + # 2. Calculate confidence based on selected method + if confidence_method == "position": + # Position-based confidence: position i / len(xt) + xt_len = (xt != pad).sum(dim=1) # (B,) - current sequence lengths + position_indices = torch.arange(max_length, device=device).unsqueeze(0).expand(batch_size, -1) # (B, L) + confidence = 1.0 - (position_indices.float() / xt_len.unsqueeze(1).float().clamp(min=1)) # (B, L) + + elif confidence_method == "top_prob": + # Top probability confidence + import torch.nn.functional as F + token_logits = unmask_rate # (B, L, V) - use the unmask_rate as logits + unmask_probs = F.softmax(token_logits, dim=-1) # (B, L, V) + confidence = unmask_probs.max(dim=-1)[0] # (B, L) + + elif confidence_method == "prob_diff": + # Probability difference confidence (top - second top) + import torch.nn.functional as F + token_logits = unmask_rate # (B, L, V) + unmask_probs = F.softmax(token_logits, dim=-1) # (B, L, V) + top2_probs, _ = torch.topk(unmask_probs, k=2, dim=-1) # (B, L, 2) + confidence = top2_probs[:, :, 0] - top2_probs[:, :, 1] # (B, L) + + elif confidence_method == "entropy": + # Entropy-based confidence (lower entropy = higher confidence) + import torch.nn.functional as F + token_logits = unmask_rate # (B, L, V) + unmask_probs = F.softmax(token_logits, dim=-1) # (B, L, V) + entropy = -torch.sum(unmask_probs * torch.log(unmask_probs + 1e-10), dim=-1) # (B, L) + confidence = -entropy # (B, L) - negative entropy so lower entropy gives higher confidence + + else: + raise ValueError(f"Unknown confidence_method: {confidence_method}") + + # 3. Apply window constraint if enabled + if use_sliding_window: + # Calculate dynamic k for each batch + k_values = torch.minimum( + torch.minimum( + (alpha * unmask_counts).long(), + torch.tensor(max_window, device=device) + ), num_mask_positions) # (B,) + + # Get cumulative count of mask positions + mask_cumsum = mask_positions.cumsum(dim=1) # (B, L) + + # Create window mask: position is eligible if it's a mask and within first k masks + is_within_window = mask_cumsum <= k_values.unsqueeze(1) # (B, L) + window_mask = mask_positions & is_within_window # (B, L) + + # Set confidence to -inf for positions outside the window or non-mask positions + confidence = torch.where(window_mask, confidence, torch.tensor(-float('inf'), device=device)) + else: + # No window constraint - only mask positions are eligible + confidence = torch.where(mask_positions, confidence, torch.tensor(-float('inf'), device=device)) + + new_xt = xt.clone() + + # Vectorized unmasking + max_unmask = unmask_counts.max().item() + if max_unmask > 0: + # Get top-k indices for all batches + _, all_top_indices = torch.topk(confidence, k=max_unmask, dim=1, largest=True) # (B, max_unmask) + + # Create mask for valid unmask operations + unmask_mask = torch.arange(max_unmask, device=device).unsqueeze(0) < unmask_counts.unsqueeze(1) # (B, max_unmask) + + # Get most likely tokens + most_likely_tokens = unmask_rate.argmax(dim=-1) # (B, L) + + # Gather the tokens to place at selected positions + selected_positions = all_top_indices[unmask_mask] # Flattened valid positions + batch_indices = torch.arange(batch_size, device=device).unsqueeze(1).expand(-1, max_unmask)[unmask_mask] # Corresponding batch indices + + # Apply unmasking with sampled tokens + new_xt[batch_indices, selected_positions] = most_likely_tokens[batch_indices, selected_positions] + else: + # --- tau-leaping unmask via Poisson --- + counts = torch.poisson(unmask_rate * dt).long() + mask_pos = xt == mask + counts[~mask_pos.unsqueeze(-1).expand_as(counts)] = 0 + counts[..., mask] = 0 + sum_c = counts.sum(dim=2) + one_event = sum_c == 1 + new_token = counts.argmax(dim=2) + new_xt = xt.clone() + new_xt[one_event] = new_token[one_event] + new_xt = torch.where(xt == pad, pad, new_xt) + new_xt = torch.where((xt != mask) & (xt != pad), xt, new_xt) + + # insertion only on non-last + if i != steps - 1: + # --- Poisson insertion, compute new lengths and fill masks --- + ext = torch.poisson(len_rate * dt).long() # (B, L+1) + xt_len = xt.ne(pad).sum(dim=1) # (B,) + gaps = torch.arange(max_length + 1, device=device).view(1, -1) + ext = ext * (gaps <= xt_len.view(batch_size, 1)).long() + total_ext = ext.sum(dim=1) + valid = xt_len + total_ext <= max_length + ext = ext * valid.view(batch_size, 1).long() + + # compute prefix sums of insertions + ext_ex = ext.int().cumsum(dim=1) # (B, L+1) + new_len = xt_len + total_ext # (B,) + + # initialize with pads, then fill mask up to new_len + xt_tmp = torch.full_like(xt, pad) + mask_pos = pos_idx_L < new_len.view(batch_size, 1) + xt_tmp[mask_pos] = mask + + # shift and scatter original tokens + new_pos_orig = pos_idx_L + ext_ex[:, :max_length] # (B, L) + orig_mask = pos_idx_L < xt_len.view(batch_size, 1) + flat_b = batch_idx_L[orig_mask] + flat_p = new_pos_orig[orig_mask] + xt_tmp[flat_b, flat_p] = new_xt[orig_mask] + else: + xt_tmp = new_xt + + xt = xt_tmp + t = t + dt + if return_trace: + sampling_trace.append(xt) + + return xt, sampling_trace diff --git a/FlexMDM/scaling_flexmdm/.gitignore b/FlexMDM/scaling_flexmdm/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..b5c002906f75b6b1d251977f4528f632e6683798 --- /dev/null +++ b/FlexMDM/scaling_flexmdm/.gitignore @@ -0,0 +1,2 @@ +SFT/human_eval +SFT/human-eval-infilling \ No newline at end of file diff --git a/FlexMDM/scaling_flexmdm/README.md b/FlexMDM/scaling_flexmdm/README.md new file mode 100644 index 0000000000000000000000000000000000000000..1639cb12fe5a55d4ad71e1d0f14f73308b5767c7 --- /dev/null +++ b/FlexMDM/scaling_flexmdm/README.md @@ -0,0 +1,26 @@ +
+

Scaling FlexMDM from LLaDA weight initialization

+

Scaling FlexMDM from LLaDA-8B-Base, an open-sourced pretrained MDM. Scaled FlexMDM outperforms LLaDA, on math (GSM8K) and code infilling (HumanEval-infill).

+
+ + +
+
+
+ + + +## Overview + +The code consists of three main parts: (1) Transfer learning from LLaDA-8B-Base, (2) Instruction-fine tuning the baseline models, and (3) Inference for both models and evaluation. Experimental details can be found in Appendix E of our paper. Our code is built from LLaDA-d1 [codebase](https://github.com/dllm-reasoning/d1). + +## Transfer learning + +We first take LLaDA-8B-Base and add a time-embedding / scalar insertion head (`llada_dit.py` and `llada_utils.py`). Next, we pre-process the transfer learning data with `flexmdm_transfer_preprocess.py`. The training code is given in `flexmdm_transfer_openwebtext.py`, for which the slurm script is `scripts/flexmdm_transfer.sh`. As we mentioned, our FlexMDM baseline is fine-tuned with 16 H100 GPUs for approximately three days. The FlexMDM interpolant is given in `flexmdm_interpolant.py`. `flexmdm_trainer.py` file includes the data collector as well as the loss function calculation. + +## Instruction-Fine Tuning + +We next instruction-fine-tune (IFT) both the FlexMDM (which we acquired from the transfer learning) and LLaDA-8B-Base. For the IFT with GSM8K, the data processing code is given in `preprocess_math.py`. For the IFT with the opc-sft dataset, it is given in `preprocess_code_infilling.py`. Both IFT shares the training code in `instruction_finetuning.py` and the training scripts are respectively given by `scripts/IFT_math.sh` and `scripts/IFT_code.sh`. `llada_trainer.py` file includes the data collector as well as the loss function calculation. + +## Evaluation +Each model's inference implementation is given in `flexmdm_inference.py` and `llada_inference.py`, respectively. The evaluation code for GSM8K and HumanEval-infill is given in `eval_gsm8k.py` and `eval_humaneval_infill.py`, respectively. The slurm scripts are provided in `scripts/inference_math.sh` and `scripts/inference_code.sh`. \ No newline at end of file diff --git a/FlexMDM/scaling_flexmdm/env.yml b/FlexMDM/scaling_flexmdm/env.yml new file mode 100644 index 0000000000000000000000000000000000000000..87d86f5ef5aabe8e137535e33bbff6d89484ce24 --- /dev/null +++ b/FlexMDM/scaling_flexmdm/env.yml @@ -0,0 +1,33 @@ +name: d1 +channels: + - defaults +dependencies: + - python=3.10 + - pip + - setuptools + - wheel + - numpy==1.25.0 + - pandas + - _libgcc_mutex=0.1=main + - libgcc-ng>=11.2.0 + - openssl>=3.0 + - pip: + - torch==2.6.0 + - torchvision + - torchaudio + - transformers==4.49.0 + - accelerate==1.4.0 + - bitsandbytes==0.45.3 + - peft==0.15.1 + - "git+https://github.com/huggingface/trl.git@0f88c179e30b3439467942a08c3190f624d5c423" + - deepspeed==0.16.4 + - datasets==3.3.2 + - tiktoken==0.9.0 + - wandb==0.15.3 + - sentencepiece + - evaluate + - scipy + - tqdm + - regex + - scikit-learn + \ No newline at end of file diff --git a/FlexMDM/scaling_flexmdm/eval_gsm8k.py b/FlexMDM/scaling_flexmdm/eval_gsm8k.py new file mode 100644 index 0000000000000000000000000000000000000000..198e7ce07350ef0362b417adde13c42f16864f4c --- /dev/null +++ b/FlexMDM/scaling_flexmdm/eval_gsm8k.py @@ -0,0 +1,493 @@ +import argparse +import json +import math +import os +import random +import time +import pickle +from datetime import datetime + +import numpy as np +import torch +import torch.distributed as dist +from torch.utils.data import DataLoader, DistributedSampler +from tqdm import tqdm +from transformers import AutoTokenizer, AutoModel +from peft import PeftModel +from safetensors.torch import load_file +from peft import LoraConfig, TaskType, get_peft_model +from pathlib import Path + +from llada_inference import generate_mdm +from flexmdm_inference import generate_flexmdm +from preprocess_math import GSM8KDataset + +from instruction_finetuning import get_reasoning_tokenizer +from llada_dit import LLaDA_DIT + + +DATASET_MAP = { + "gsm8k": GSM8KDataset, +} + +def parse_gsm_answers(json_path=None, json_data=None): + if json_path: + with open(json_path, "r") as file: + data = json.load(file) + else: + data = json_data + + total_correct = 0 + total_processed = 0 + total_effective_tokens = 0 + processed_items = [] + + for item in data.get("generations", []): + total_processed += 1 + ground_truth = item.get("ground_truth") + raw_generation = item.get("generations", "") + question = item.get("question", "") + + # Count effective tokens + effective_tokens = count_effective_tokens(raw_generation) + total_effective_tokens += effective_tokens + + parsed_answer = None + + boxed_matches = re.findall(r"\\boxed{(.*?)}", raw_generation) + if boxed_matches: + for boxed_content in boxed_matches: + boxed_content = boxed_content.strip() + if boxed_content and boxed_content != "..." and not re.match(r"^\.+$", boxed_content): + try: + parsed_answer = float(boxed_content) + break + except ValueError: + numbers = re.findall(r"-?\d+\.?\d*", boxed_content) + if numbers: + try: + parsed_answer = float(numbers[0]) + break + except ValueError: + pass + + if parsed_answer is None: + answer_match = re.search(r"(.*?)", raw_generation, re.DOTALL) + if answer_match: + answer_text = answer_match.group(1).strip() + if answer_text: + try: + parsed_answer = float(answer_text) + except ValueError: + numbers = re.findall(r"-?\d+\.?\d*", answer_text) + if numbers: + try: + parsed_answer = float(numbers[-1]) + except ValueError: + pass + + is_correct = parsed_answer is not None and parsed_answer == ground_truth + if is_correct: + total_correct += 1 + + processed_items.append( + { + "question": question, + "raw_generation": raw_generation, + "extracted_answer": parsed_answer, + "ground_truth": ground_truth, + "is_correct": is_correct, + "effective_tokens": effective_tokens, + } + ) + + return ( + total_correct, + total_processed, + processed_items, + total_effective_tokens, + ) + + +def init_seed(seed): + random.seed(seed) + os.environ["PYTHONHASHSEED"] = str(seed) + np.random.seed(seed) + torch.manual_seed(seed) + torch.cuda.manual_seed(seed) + torch.backends.cudnn.deterministic = True + + +def setup_ddp(): + dist.init_process_group("nccl") + local_rank = int(os.environ["LOCAL_RANK"]) + torch.cuda.set_device(local_rank) + return local_rank + + +def cleanup_ddp(): + dist.destroy_process_group() + + +def save_progress(filename, all_generations, processed_count): + """Save progress to allow resumption after preemption""" + progress_file = filename.replace('.json', '_progress.pkl') + progress_data = { + 'all_generations': all_generations, + 'processed_count': processed_count, + 'timestamp': datetime.now().isoformat() + } + with open(progress_file, 'wb') as f: + pickle.dump(progress_data, f) + if dist.get_rank() == 0: + print(f"Progress saved: {processed_count} samples processed") + +def load_progress(filename): + """Load previous progress if available""" + progress_file = filename.replace('.json', '_progress.pkl') + if os.path.exists(progress_file): + try: + with open(progress_file, 'rb') as f: + progress_data = pickle.load(f) + if dist.get_rank() == 0: + print(f"Resuming from progress: {progress_data['processed_count']} samples already processed") + print(f"Previous run timestamp: {progress_data['timestamp']}") + return progress_data['all_generations'], progress_data['processed_count'] + except Exception as e: + if dist.get_rank() == 0: + print(f"Error loading progress file: {e}") + return [], 0 + return [], 0 + +def evaluate( + model, + tokenizer, + dataloader, + args, + filename, +): + model.eval() + total_processed = torch.tensor(0, device=model.device) + wall_times = [] + device = model.device + + # Load previous progress + all_generations, processed_count = load_progress(filename) + + # Skip already processed samples + samples_to_skip = processed_count + current_sample = 0 + + save_interval = 10 # Save progress every 10 batches + batch_count = 0 + + for batch in tqdm(dataloader, disable=False, desc=f"Rank {dist.get_rank()}", position=dist.get_rank()): + # Skip already processed batches + batch_size = len(batch["answers"]) + if current_sample + batch_size <= samples_to_skip: + current_sample += batch_size + continue + + start_time = time.time() + input_ids = batch["input_ids"].to(device) + gt_answers = batch["answers"] + questions = batch["questions"] + prompts = batch["prompts"] + + if dist.get_rank() == 0: + print(f"Processing batch {batch_count + 1}, samples {current_sample}-{current_sample + batch_size}") + + tokenised_prompts = tokenizer(prompts, padding="max_length", max_length=args.gen_length, return_tensors="pt").input_ids.to(device) + + if args.variable_length: + out, trace = generate_flexmdm( + model, + tokenised_prompts, + tokenizer, + steps = args.diffusion_steps, + model_type = "variable", + temperature = args.temperature, + remasking = args.remasking, + confidence_method=args.confidence_method, + use_sliding_window=args.use_sliding_window, + alpha=args.alpha, + max_window=args.max_window, + ) + generated_texts = tokenizer.batch_decode(out[:, :], skip_special_tokens=True) + else: + out = generate_mdm( + model, + tokenised_prompts, + tokenizer, + steps = 256, + model_type = "mdm", + temperature = args.temperature, + remasking = args.remasking, + ) + generated_texts = tokenizer.batch_decode(out[:, :], skip_special_tokens=False) + + example_result = [ + { + "question": questions[j], + "prompt_input": prompts[j], + "generations": generated_texts[j], + "ground_truth": gt_answers[j], + } + for j in range(len(gt_answers)) + ] + all_generations.extend(example_result) + total_processed += len(generated_texts) + current_sample += batch_size + wall_times.append(time.time() - start_time) + batch_count += 1 + + # Periodic progress saving + if batch_count % save_interval == 0: + save_progress(filename, all_generations, current_sample) + + # Print individual results + if dist.get_rank() == 0: + idx = random.randint(0, len(questions) - 1) + print(f"Question: {questions[idx]}") + print("-" * 50) + print("Generation:") + print(generated_texts[idx]) + print("-" * 50) + print(f"Ground truth: {gt_answers[idx]}") + + # Final progress save + save_progress(filename, all_generations, current_sample) + + avg_wall_time = sum(wall_times) / len(wall_times) if wall_times else 0 + metrics = { + "wall_time": avg_wall_time, + "generations": all_generations, + "total_processed": total_processed.item(), + } + return metrics + + +class CustomDistributedSampler(DistributedSampler): + """ + From torch docs: + drop_last (bool, optional): if ``True``, then the sampler will drop the + tail of the data to make it evenly divisible across the number of + replicas. If ``False``, the sampler will add extra indices to make + the data evenly divisible across the replicas + + We want drop_last = False, but don't want to have extra padding indices. Hence using a custom sampler. + """ + + def __init__( + self, + dataset, + num_replicas=None, + rank=None, + shuffle=True, + seed=0, + drop_last=False, + ) -> None: + if num_replicas is None: + if not dist.is_available(): + raise RuntimeError("Requires distributed package to be available") + num_replicas = dist.get_world_size() + if rank is None: + if not dist.is_available(): + raise RuntimeError("Requires distributed package to be available") + rank = dist.get_rank() + if rank >= num_replicas or rank < 0: + raise ValueError(f"Invalid rank {rank}, rank should be in the interval [0, {num_replicas - 1}]") + + self.dataset = dataset + self.num_replicas = num_replicas + self.rank = rank + self.epoch = 0 + self.drop_last = drop_last + + if self.drop_last and len(self.dataset) % self.num_replicas != 0: + self.num_samples = math.ceil((len(self.dataset) - self.num_replicas) / self.num_replicas) + self.total_size = self.num_samples * self.num_replicas + else: + # If we don't drop the last batch, we need to calculate the number of samples per rank. + self.total_size = len(self.dataset) + self.num_samples = len(self.dataset) // self.num_replicas + int( + rank < (self.total_size % self.num_replicas) + ) + + self.shuffle = shuffle + self.seed = seed + + def __iter__(self): + if self.shuffle: + # deterministically shuffle based on epoch and seed + g = torch.Generator() + g.manual_seed(self.seed + self.epoch) + indices = torch.randperm(len(self.dataset), generator=g).tolist() + else: + indices = list(range(len(self.dataset))) + + if not self.drop_last: + # add extra samples to make it evenly divisible + padding_size = self.total_size - len(indices) + if padding_size <= len(indices): + indices += indices[:padding_size] + else: + indices += (indices * math.ceil(padding_size / len(indices)))[:padding_size] + else: + # remove tail of data to make it evenly divisible. + indices = indices[:self.total_size] + assert len(indices) == self.total_size + + # subsample + indices = indices[self.rank:self.total_size:self.num_replicas] + assert len(indices) == self.num_samples + + return iter(indices) + + def __len__(self): + return self.num_samples + + def set_epoch(self, epoch): + self.epoch = epoch + + +if __name__ == "__main__": + init_seed(42) + + # Note: This evaluation script saves only model generations. A separate parser is used later to extract + # predictions and calculate metrics. + + local_rank = setup_ddp() + + parser = argparse.ArgumentParser() + parser.add_argument("--variable_length", action="store_true", help="Whether to employ FlexMDM or MDM") + parser.add_argument("--few_shot", type=int, default=0, help="Number of few-shot examples--we all use 0") + parser.add_argument("--batch_size", type=int, default=1) + parser.add_argument("--dataset", type=str, choices=["gsm8k"], default="gsm8k") + parser.add_argument("--suffix", type=str, default="") + # model path: for the test run + parser.add_argument("--checkpoint_path", type=str, default="/n/netscratch/albergo_lab/Lab/sft-checkpoints/llada-sft-openwebtext/checkpoint-40000") + + # FlexMDM sampling configs-only valid for FlexMDM + parser.add_argument("--confidence_method", type=str, default="prob_diff", choices=["position", "prob_diff", "top_prob", "entropy"]) + parser.add_argument("--use_sliding_window", action="store_true", help="Use sliding window for confidence calculation", help="valid for FlexMDM only") + parser.add_argument("--alpha", type=float, default=1.0, help="Alpha value for the slide") + parser.add_argument("--max_window", type=int, default=10, help="Maximum window size for the slide") + + # MDM (LLaDA) sampling configs-only valid for MDM + parser.add_argument("--gen_length", type=int, default=1024) + parser.add_argument("--block_length", type=int, default=1024) + parser.add_argument("--diffusion_steps", type=int, default=1024) + parser.add_argument("--temperature", type=float, default=0.0) + parser.add_argument("--remasking", type=str, default="random") + + parser.add_argument("--add_reasoning", action="store_true") + parser.add_argument("--dont_save", action="store_true") + parser.add_argument("--output_dir", type=str, default="results/mdm_test") + parser.add_argument("--dont_use_box", action="store_true") + args = parser.parse_args() + + # Log all arguments + if dist.get_rank() == 0: + print("=" * 50) + print("EVALUATION ARGUMENTS:") + print("=" * 50) + for arg_name, arg_value in vars(args).items(): + print(f"{arg_name}: {arg_value}") + print("=" * 50) + + num_evals = {"gsm8k": -1} + + # backbone model load + backbone = AutoModel.from_pretrained("GSAI-ML/LLaDA-8B-Base", trust_remote_code=True, torch_dtype=torch.bfloat16).to(local_rank) + tokenizer = AutoTokenizer.from_pretrained("GSAI-ML/LLaDA-8B-Base", padding_side="right", trust_remote_code=True, use_fast=True) + print("Tokenizer and backbone model loaded") + + + if args.variable_length: + # instruction finetuned FlexMDM load--same configurations as in the training script + lora_config = LoraConfig( + r=128, + lora_alpha=128, + target_modules=["q_proj", "k_proj", "v_proj", "transformer.ff_out"], + lora_dropout=0.1, + bias="none", + task_type=TaskType.CAUSAL_LM) + + backbone = get_peft_model(backbone, lora_config) + model = LLaDA_DIT(backbone, pad_token_id = tokenizer.pad_token_id, d_model = 4096) + + ckpt_dir = Path(args.checkpoint_path) + state = torch.load(ckpt_dir/ "pytorch_model.bin", map_location="cpu") + missing, unexpected = model.load_state_dict(state, strict=False) + print(f"Missing keys: {missing}") + print(f"Unexpected keys: {unexpected}") + model = model.to(device = local_rank, dtype = torch.bfloat16).to(local_rank) + print("FlexMDM loaded") + else: + # fine-tuned LLaDA model load--same configurations as in the training script + if args.checkpoint_path: + model = PeftModel.from_pretrained(backbone, args.checkpoint_path, torch_dtype=torch.bfloat16).to( + local_rank + ) + if dist.get_world_size() > 1: + dist.barrier() # Make sure all processes are ready + for param in model.parameters(): + dist.broadcast(param.data, src=0) + print(f"Rank {local_rank}: Parameters synchronized") + else: + model = backbone.to(device = local_rank, dtype = torch.bfloat16).to(local_rank) + print("LLaDA model loaded") + + else: + dataset = DATASET_MAP[args.dataset]( + tokenizer, + subsample=num_evals[args.dataset], + num_examples=args.few_shot, + add_reasoning=True, # prefill for all models + ) + + dataloader = DataLoader( + dataset, + batch_size=args.batch_size, + sampler=CustomDistributedSampler(dataset, shuffle=False), + collate_fn=dataset.collate_fn, + ) + + os.makedirs(args.output_dir, exist_ok=True) + if args.variable_length: + filename = f"{args.output_dir}/{args.diffusion_steps}_alpha_{args.alpha}_max_window_{args.max_window}_ours_{args.remasking}_{dist.get_rank()}_generations.json" + else: + filename = f"{args.output_dir}/mdm_{args.remasking}_{args.gen_length}_{args.block_length}_{dist.get_rank()}_generations.json" + print(f"Saving generations to {filename}") + + metrics = evaluate(model, tokenizer, dataloader, args, filename) + + if not args.dont_save: + with open(filename, "w") as f: + json.dump( + { + "generations": metrics["generations"], + "metrics": { + "wall_time": metrics["wall_time"], + "total_processed": metrics["total_processed"], + }, + "checkpoint_path": args.checkpoint_path, + "gen_length": args.gen_length, + "diffusion_steps": args.diffusion_steps, + "block_length": args.block_length, + "confidence_method": args.confidence_method, + "use_sliding_window": args.use_sliding_window, + }, + f, + indent=2, + ) + + # Clean up progress file after successful completion + progress_file = filename.replace('.json', '_progress.pkl') + if os.path.exists(progress_file): + os.remove(progress_file) + if dist.get_rank() == 0: + print(f"Cleaned up progress file: {progress_file}") + + cleanup_ddp() diff --git a/FlexMDM/scaling_flexmdm/eval_gsm8k/parse_and_get_acc.py b/FlexMDM/scaling_flexmdm/eval_gsm8k/parse_and_get_acc.py new file mode 100644 index 0000000000000000000000000000000000000000..0c7d51de2fdcf16a49c464fb04e33fd11a793d90 --- /dev/null +++ b/FlexMDM/scaling_flexmdm/eval_gsm8k/parse_and_get_acc.py @@ -0,0 +1,441 @@ +import json +import re +import os +import glob +import tiktoken +from collections import defaultdict +from parser_helper import is_equiv, remove_boxed, last_boxed_only_string + + +def count_effective_tokens(text): + if not text: + return 0 + text = text.replace("<|endoftext|>", "") + enc = tiktoken.get_encoding("cl100k_base") + tokens = enc.encode(text) + return len(tokens) + + +def parse_gsm_answers(json_path=None, json_data=None): + if json_path: + with open(json_path, "r") as file: + data = json.load(file) + else: + data = json_data + + total_correct = 0 + total_processed = 0 + total_effective_tokens = 0 + processed_items = [] + + for item in data.get("generations", []): + total_processed += 1 + ground_truth = item.get("ground_truth") + raw_generation = item.get("generations", "") + question = item.get("question", "") + + # Count effective tokens + effective_tokens = count_effective_tokens(raw_generation) + total_effective_tokens += effective_tokens + + parsed_answer = None + + boxed_matches = re.findall(r"\\boxed{(.*?)}", raw_generation) + if boxed_matches: + for boxed_content in boxed_matches: + boxed_content = boxed_content.strip() + if boxed_content and boxed_content != "..." and not re.match(r"^\.+$", boxed_content): + try: + parsed_answer = float(boxed_content) + break + except ValueError: + numbers = re.findall(r"-?\d+\.?\d*", boxed_content) + if numbers: + try: + parsed_answer = float(numbers[0]) + break + except ValueError: + pass + + if parsed_answer is None: + answer_match = re.search(r"(.*?)", raw_generation, re.DOTALL) + if answer_match: + answer_text = answer_match.group(1).strip() + if answer_text: + try: + parsed_answer = float(answer_text) + except ValueError: + numbers = re.findall(r"-?\d+\.?\d*", answer_text) + if numbers: + try: + parsed_answer = float(numbers[-1]) + except ValueError: + pass + + is_correct = parsed_answer is not None and parsed_answer == ground_truth + if is_correct: + total_correct += 1 + + processed_items.append( + { + "question": question, + "raw_generation": raw_generation, + "extracted_answer": parsed_answer, + "ground_truth": ground_truth, + "is_correct": is_correct, + "effective_tokens": effective_tokens, + } + ) + + return ( + total_correct, + total_processed, + processed_items, + total_effective_tokens, + ) + + +def parse_math_answers(json_path=None, json_data=None): + if json_path: + with open(json_path, "r") as file: + data = json.load(file) + else: + data = json_data + + total_correct = 0 + total_processed = 0 + total_effective_tokens = 0 + processed_items = [] + + for item in data.get("generations", []): + total_processed += 1 + question = item.get("question", "") + ground_truth = item.get("ground_truth", "") + raw_generation = item.get("generations", "") + + # Count effective tokens + effective_tokens = count_effective_tokens(raw_generation) + total_effective_tokens += effective_tokens + + parsed_answer = None + + try: + parsed_answer = remove_boxed(last_boxed_only_string(raw_generation)) + except: + parsed_answer = None + + if not parsed_answer: + answer_match = re.search(r"(.*?)", raw_generation, re.DOTALL) + if answer_match: + parsed_answer = answer_match.group(1).strip() + is_correct = False + if parsed_answer is not None: + is_correct = is_equiv(parsed_answer, ground_truth) + + if is_correct: + total_correct += 1 + + processed_items.append( + { + "question": question, + "raw_generation": raw_generation, + "extracted_answer": parsed_answer, + "ground_truth": ground_truth, + "is_correct": is_correct, + "effective_tokens": effective_tokens, + } + ) + + return ( + total_correct, + total_processed, + processed_items, + total_effective_tokens, + ) + + +def parse_countdown_answers(json_path=None, json_data=None): + if json_path: + with open(json_path, "r") as file: + data = json.load(file) + else: + data = json_data + + total_correct = 0 + total_processed = 0 + total_effective_tokens = 0 + + processed_items = [] + + def validate_equation(equation_str, available_numbers): + """Validate that equation only uses available numbers and each number once.""" + try: + numbers_in_eq = [int(n) for n in re.findall(r"\d+", equation_str)] + available_numbers = sorted(available_numbers) + numbers_in_eq = sorted(numbers_in_eq) + return numbers_in_eq == available_numbers + except: + return False + + def evaluate_equation(equation_str): + """Safely evaluate the arithmetic equation.""" + try: + allowed_pattern = r"^[\d+\-*/().\s]+$" + if not re.match(allowed_pattern, equation_str): + raise ValueError("Invalid characters in equation.") + result = eval(equation_str.strip(), {"__builtins__": None}, {}) + return result + except Exception: + return float("Inf") + + for item in data.get("generations", []): + total_processed += 1 + question = item.get("question", "") + ground_truth = item.get("ground_truth", []) + generated_text = item.get("generations", "") + + # Count effective tokens + effective_tokens = count_effective_tokens(generated_text) + total_effective_tokens += effective_tokens + + # Extract available numbers and target from ground_truth + numbers = [] + target = None + + if isinstance(ground_truth, list) and len(ground_truth) == 2: + numbers = ground_truth[0] + target = ground_truth[1] + else: + # Fallback to parsing from question if ground_truth is not in expected format + numbers_match = re.search(r"Numbers: \[([\d, ]+)\]", question, re.IGNORECASE) + if numbers_match: + numbers_str = numbers_match.group(1) + numbers = [int(num.strip()) for num in numbers_str.split(",")] + + target_match = re.search(r"Target: (\d+)", question, re.IGNORECASE) + if target_match: + target = int(target_match.group(1)) + + equation = "" + try: + equation = remove_boxed(last_boxed_only_string(generated_text)) + except: + # Try to extract from answer tags + answer_match = re.search(r"(.*?)", generated_text, re.DOTALL) + if answer_match: + equation = answer_match.group(1).strip() + else: + equation = generated_text + + # Replace LaTeX operators with Python operators + equation = equation.replace(r"\div", "/").replace(r"\times", "*").replace(r"\cdot", "*") + + # Check for equation with equals sign and extract only the expression part + equation_match = re.search(r"([0-9+\-*/() ]+)=[0-9. ]+", equation) + if equation_match: + equation = equation_match.group(1).strip() + + is_correct = False + result = None + + # Validate and evaluate the equation + is_valid = validate_equation(equation, numbers) + if is_valid: + result = evaluate_equation(equation) + if target is not None and abs(result - target) < 1e-5: + is_correct = True + total_correct += 1 + + processed_items.append( + { + "question": question, + "extracted_answer": equation, + "evaluation_result": result, + "ground_truth": ground_truth, + "is_correct": is_correct, + "effective_tokens": effective_tokens, + } + ) + + return ( + total_correct, + total_processed, + processed_items, + total_effective_tokens, + ) + + +def parse_sudoku_answers(json_path=None, json_data=None): + if json_path: + with open(json_path, "r") as file: + data = json.load(file) + else: + data = json_data + + total_correct_cells = total_empty_cells = total_processed = 0 + total_effective_tokens = 0 + processed_items = [] + + for item in data.get("generations", []): + total_processed += 1 + question = item.get("question", "") + ground_truth = item.get("ground_truth", "") + raw_generation = item.get("generations", "") + + # Count effective tokens + effective_tokens = count_effective_tokens(raw_generation) + total_effective_tokens += effective_tokens + + # Extract puzzle + puzzle_str = "" + if len(question) >= 16 and all(c.isdigit() or c == "0" for c in question[:16]): + puzzle_str = question[:16] + else: + match = re.search(r"Sudoku puzzle: ([0-9]{16})", question) + if match: + puzzle_str = match.group(1) + assert len(puzzle_str) == 16, f"Invalid puzzle string: {puzzle_str}" + + empty_indices = [i for i in range(16) if puzzle_str[i] == "0"] + empty_cells = len(empty_indices) + + # Extract solution using regex patterns + solution_str = "" + patterns = [ + r".*?```\s*([\d\s]+)```", + r"(.*?)(?:<\|eot_id\|>|<\|endoftext\|>|)", + r"\s*(.*?)(?:<\|eot_id\|>|<\|endoftext\|>|$)", + r".*?(\d{16})\s*", + r"\b(\d{16})\b", + ] + + for pattern in patterns: + if solution_str: + break + match = re.search(pattern, raw_generation, re.DOTALL) + if match and match.group(1).strip(): + solution_str = match.group(1).strip() + + solution_str = re.sub(r"\s", "", solution_str) + + # Handle solution length + if not solution_str: + correct_cells = 0 + else: + if len(solution_str) < 16: + solution_str = solution_str + "0" * (16 - len(solution_str)) + elif len(solution_str) > 16: + solution_str = solution_str[:16] + correct_cells = sum(1 for i in empty_indices if solution_str[i] == ground_truth[i]) + + accuracy = correct_cells / empty_cells if empty_cells > 0 else 0.0 + total_correct_cells += correct_cells + total_empty_cells += empty_cells + + processed_items.append( + { + "question": question, + "raw_generation": raw_generation, + "extracted_answer": solution_str, + "ground_truth": ground_truth, + "empty_cells": empty_cells, + "correct_cells": correct_cells, + "accuracy": accuracy, + "effective_tokens": effective_tokens, + } + ) + return ( + total_correct_cells, + total_empty_cells, + processed_items, + total_effective_tokens * 8, + ) + + +def extract_setup_name(filename): + """Extract the setup name from the filename.""" + match = re.match(r"(.+)_\d+_generations\.json$", filename) + if match: + return match.group(1) + return None + + +def aggregate_results(directory="."): + """Aggregate results from all JSON files and save detailed results.""" + # Find all JSON files matching the pattern + json_files = glob.glob(os.path.join(directory, "*_generations.json")) + + # Dictionary to store aggregated results by setup + setups = defaultdict( + lambda: { + "correct": 0, + "processed": 0, + "accuracy": 0.0, + "questions": [], + "total_effective_tokens": 0, + } + ) + + for json_file in json_files: + filename = os.path.basename(json_file) + setup_name = extract_setup_name(filename) + "_gsm" + + if setup_name: + # print(f"Processing {filename}...") + if "gsm" in setup_name: + ( + correct, + processed, + detailed_results, + total_effective_tokens, + ) = parse_gsm_answers(json_path=json_file) + elif "math" in setup_name: + ( + correct, + processed, + detailed_results, + total_effective_tokens, + ) = parse_math_answers(json_path=json_file) + elif "countdown" in setup_name: + ( + correct, + processed, + detailed_results, + total_effective_tokens, + ) = parse_countdown_answers(json_path=json_file) + elif "sudoku" in setup_name: + ( + correct, + processed, + detailed_results, + total_effective_tokens, + ) = parse_sudoku_answers(json_path=json_file) + + setups[setup_name]["correct"] += correct + setups[setup_name]["processed"] += processed + setups[setup_name]["total_effective_tokens"] += total_effective_tokens + setups[setup_name]["questions"].extend(detailed_results) + + # Calculate final accuracy and save results + for setup, results in sorted(setups.items()): + results["accuracy"] = ( + results["correct"] / results["processed"] * 100 if results["processed"] > 0 else 0 + ) + results["avg_effective_tokens"] = ( + results["total_effective_tokens"] / results["processed"] if len(results["questions"]) > 0 else 0 + ) + # Header + header_format = "{:<40} {:>12} {:>25}" + print(header_format.format("Setup (task_model_seqlen_diffusteps)", "Accuracy", "Avg Effective Tokens")) + print("-" * 80) + + # Data rows + row_format = "{:<40} {:>11.2f}% {:>25.2f}" + for setup, results in sorted(setups.items()): + print(row_format.format(setup, results["accuracy"], results["avg_effective_tokens"])) + + print("=" * 80) + + +if __name__ == "__main__": + aggregate_results(directory="results/mdm_test") diff --git a/FlexMDM/scaling_flexmdm/eval_gsm8k/parser_helper.py b/FlexMDM/scaling_flexmdm/eval_gsm8k/parser_helper.py new file mode 100644 index 0000000000000000000000000000000000000000..d3f92ccb6dbb931570a9087c7024f43e7a65ccc4 --- /dev/null +++ b/FlexMDM/scaling_flexmdm/eval_gsm8k/parser_helper.py @@ -0,0 +1,211 @@ +import json +import re +import os +import glob +from collections import defaultdict + + +def last_boxed_only_string(string): + idx = string.rfind("\\boxed") + if "\\boxed " in string: + return "\\boxed " + string.split("\\boxed ")[-1].split("$")[0] + if idx < 0: + idx = string.rfind("\\fbox") + if idx < 0: + return string + + i = idx + right_brace_idx = None + num_left_braces_open = 0 + while i < len(string): + if string[i] == "{": + num_left_braces_open += 1 + if string[i] == "}": + num_left_braces_open -= 1 + if num_left_braces_open == 0: + right_brace_idx = i + break + i += 1 + + if right_brace_idx is None: + retval = None + else: + retval = string[idx : right_brace_idx + 1] + + return retval + + +def remove_boxed(s): + if "\\boxed " in s: + left = "\\boxed " + assert s[: len(left)] == left + return s[len(left) :] + + left = "\\boxed{" + + try: + assert s[: len(left)] == left + assert s[-1] == "}" + + return s[len(left) : -1] + except: + return s + + +def fix_fracs(string): + substrs = string.split("\\frac") + new_str = substrs[0] + if len(substrs) > 1: + substrs = substrs[1:] + for substr in substrs: + new_str += "\\frac" + if substr[0] == "{": + new_str += substr + else: + try: + assert len(substr) >= 2 + except AssertionError: + return string + a = substr[0] + b = substr[1] + if b != "{": + if len(substr) > 2: + post_substr = substr[2:] + new_str += "{" + a + "}{" + b + "}" + post_substr + else: + new_str += "{" + a + "}{" + b + "}" + else: + if len(substr) > 2: + post_substr = substr[2:] + new_str += "{" + a + "}" + b + post_substr + else: + new_str += "{" + a + "}" + b + string = new_str + return string + + +def remove_right_units(string): + # "\\text{ " only ever occurs (at least in the val set) when describing units + if "\\text{ " in string: + splits = string.split("\\text{ ") + assert len(splits) == 2 + return splits[0] + else: + return string + + +def fix_sqrt(string): + if "\\sqrt" not in string: + return string + splits = string.split("\\sqrt") + new_string = splits[0] + for split in splits[1:]: + if split[0] != "{": + a = split[0] + new_substr = "\\sqrt{" + a + "}" + split[1:] + else: + new_substr = "\\sqrt" + split + new_string += new_substr + return new_string + + +def strip_string(string): + # linebreaks + string = string.replace("\n", "") + + # remove inverse spaces + string = string.replace("\\!", "") + + # replace \\ with \ + string = string.replace("\\\\", "\\") + + # replace tfrac and dfrac with frac + string = string.replace("tfrac", "frac") + string = string.replace("dfrac", "frac") + + # remove \left and \right + string = string.replace("\\left", "") + string = string.replace("\\right", "") + + # Remove circ (degrees) + string = string.replace("^{\\circ}", "") + string = string.replace("^\\circ", "") + + # remove dollar signs + string = string.replace("\\$", "") + + # remove units (on the right) + string = remove_right_units(string) + + # remove percentage + string = string.replace("\\%", "") + string = string.replace("\%", "") # noqa: W605 + + # " 0." equivalent to " ." and "{0." equivalent to "{." Alternatively, add "0" if "." is the start of the string + string = string.replace(" .", " 0.") + string = string.replace("{.", "{0.") + # if empty, return empty string + if len(string) == 0: + return string + if string[0] == ".": + string = "0" + string + + # to consider: get rid of e.g. "k = " or "q = " at beginning + if len(string.split("=")) == 2: + if len(string.split("=")[0]) <= 2: + string = string.split("=")[1] + + # fix sqrt3 --> sqrt{3} + string = fix_sqrt(string) + + # remove spaces + string = string.replace(" ", "") + + # \frac1b or \frac12 --> \frac{1}{b} and \frac{1}{2}, etc. Even works with \frac1{72} (but not \frac{72}1). Also does a/b --> \\frac{a}{b} + string = fix_fracs(string) + + # manually change 0.5 --> \frac{1}{2} + if string == "0.5": + string = "\\frac{1}{2}" + + # NOTE: X/Y changed to \frac{X}{Y} in dataset, but in simple cases fix in case the model output is X/Y + string = fix_a_slash_b(string) + + return string + + +def fix_a_slash_b(string): + if len(string.split("/")) != 2: + return string + a = string.split("/")[0] + b = string.split("/")[1] + try: + a = int(a) + b = int(b) + assert string == "{}/{}".format(a, b) + new_string = "\\frac{" + str(a) + "}{" + str(b) + "}" + return new_string + except AssertionError: + return string + + +def is_equiv(str1, str2, verbose=False): + if type(str1) == float or type(str2) == float: + try: + return abs(float(str1) - float(str2)) < 1e-6 + except: + return False + if str1 is None and str2 is None: + print("WARNING: Both None") + return True + if str1 is None or str2 is None: + return False + + try: + ss1 = strip_string(str1) + ss2 = strip_string(str2) + if verbose: + print(ss1, ss2) + return ss1 == ss2 + except Exception: + return str1 == str2 diff --git a/FlexMDM/scaling_flexmdm/eval_humaneval_infill.py b/FlexMDM/scaling_flexmdm/eval_humaneval_infill.py new file mode 100644 index 0000000000000000000000000000000000000000..796b71577ce485adb0cc4d6a94f28e4335ec111b --- /dev/null +++ b/FlexMDM/scaling_flexmdm/eval_humaneval_infill.py @@ -0,0 +1,559 @@ +import argparse +import json +import math +import os +import random +import time +import pickle +from datetime import datetime +from typing import List, Dict, Any + +import numpy as np +import torch +import torch.distributed as dist +from torch.utils.data import DataLoader, DistributedSampler, Dataset +from tqdm import tqdm +from transformers import AutoTokenizer, AutoModel +from peft import LoraConfig, TaskType, get_peft_model, PeftModel +from pathlib import Path + +from flexmdm_inference import generate_flexmdm_infilling +from llada_inference import generate_mdm +from instruction_finetuning import get_reasoning_tokenizer +from llada_dit import LLaDA_DIT +from human_eval_infilling.data import write_jsonl, read_problems + + +class HumanEvalInfillingDataset(Dataset): + """Dataset for HumanEval infilling tasks with prefix and suffix.""" + + def __init__(self, tokenizer, variable_length: bool = True, data_path: str = None, subsample: int = -1): + self.tokenizer = tokenizer + self.data = [] + + problems = read_problems(benchmark_name="single-line") + + if variable_length: + for task_id in problems: + prompt = problems[task_id]['prompt'] + "" + suffix = "" + problems[task_id]['suffix'] + self.data.append({ + 'task_id': task_id, + 'prompt': prompt, + 'suffix': suffix, + 'ground_truth': problems[task_id]['canonical_solution'] + }) + else: + for task_id in problems: + instruction = "You are instructed to perform an infill task. Please fill in the missing parts of the code. The prefix and suffix are provided and delimited by and tokens." + prompt = instruction + "" + problems[task_id]['prompt'] + "" + problems[task_id]['suffix'] + "" + self.data.append({ + 'task_id': task_id, + 'prompt': prompt, + 'suffix': "", + 'ground_truth': problems[task_id]['canonical_solution'] + }) + if subsample > 0: + self.data = self.data[:subsample] + + def __len__(self): + return len(self.data) + + def __getitem__(self, idx): + return self.data[idx] + + def collate_fn(self, batch): + task_ids = [item['task_id'] for item in batch] + prompts = [item['prompt'] for item in batch] + suffixes = [item['suffix'] for item in batch] + ground_truths = [item['ground_truth'] for item in batch] + + return { + 'task_ids': task_ids, + 'prompts': prompts, + 'suffixes': suffixes, + 'ground_truths': ground_truths + } + + +def init_seed(seed): + random.seed(seed) + os.environ["PYTHONHASHSEED"] = str(seed) + np.random.seed(seed) + torch.manual_seed(seed) + torch.cuda.manual_seed(seed) + torch.backends.cudnn.deterministic = True + + +def setup_ddp(): + dist.init_process_group("nccl") + local_rank = int(os.environ["LOCAL_RANK"]) + torch.cuda.set_device(local_rank) + return local_rank + + +def cleanup_ddp(): + dist.destroy_process_group() + + +def save_progress(filename, all_generations, processed_count): + """Save progress to allow resumption after preemption""" + progress_file = filename.replace('.json', '_progress.pkl') + progress_data = { + 'all_generations': all_generations, + 'processed_count': processed_count, + 'timestamp': datetime.now().isoformat() + } + with open(progress_file, 'wb') as f: + pickle.dump(progress_data, f) + if dist.get_rank() == 0: + print(f"Progress saved: {processed_count} samples processed") + + +def load_progress(filename): + """Load previous progress if available""" + progress_file = filename.replace('.json', '_progress.pkl') + if os.path.exists(progress_file): + try: + with open(progress_file, 'rb') as f: + progress_data = pickle.load(f) + if dist.get_rank() == 0: + print(f"Resuming from progress: {progress_data['processed_count']} samples already processed") + print(f"Previous run timestamp: {progress_data['timestamp']}") + return progress_data['all_generations'], progress_data['processed_count'] + except Exception as e: + if dist.get_rank() == 0: + print(f"Error loading progress file: {e}") + return [], 0 + return [], 0 + + +def evaluate_infilling_mdm( + model, + tokenizer, + dataloader, + args, + filename, +): + model.eval() + total_processed = torch.tensor(0, device=model.device) + wall_times = [] + device = model.device + + # Load previous progress + all_generations, processed_count = load_progress(filename) + + # Skip already processed samples + samples_to_skip = processed_count + current_sample = 0 + + save_interval = 10 # Save progress every 10 batches + batch_count = 0 + + for batch in tqdm(dataloader, disable=False, desc=f"Rank {dist.get_rank()}", position=dist.get_rank()): + # Skip already processed batches + batch_size = len(batch["task_ids"]) + if current_sample + batch_size <= samples_to_skip: + current_sample += batch_size + continue + + start_time = time.time() + task_ids = batch["task_ids"] + prompts = batch["prompts"] + ground_truths = batch["ground_truths"] + + if dist.get_rank() == 0: + print(f"Processing batch {batch_count + 1}, samples {current_sample}-{current_sample + batch_size}") + + # For MDM: prompts are already formatted with instruction and delimiters + tokenized_prompts = tokenizer( + prompts, + max_length=args.gen_length, + return_tensors="pt", + truncation=True + ).input_ids.to(device) + + print(tokenized_prompts.shape) + + out = generate_mdm( + model, + tokenized_prompts, + tokenizer, + args.diffusion_steps, + args.gen_length, + args.gen_length, + args.temperature, + cfg_scale=0.0, # No classifier-free guidance for MDM + remasking=args.remasking + ) + + generated_texts = tokenizer.batch_decode(out[:, :], skip_special_tokens=True) + + example_result = [ + { + "task_id": task_ids[j], + "prompt": prompts[j], + "generated_infill": generated_texts[j], + "ground_truth": ground_truths[j], + } + for j in range(len(task_ids)) + ] + all_generations.extend(example_result) + total_processed += len(generated_texts) + current_sample += batch_size + wall_times.append(time.time() - start_time) + batch_count += 1 + + # Periodic progress saving + if batch_count % save_interval == 0: + save_progress(filename, all_generations, current_sample) + + # Print individual results + if dist.get_rank() == 0: + idx = random.randint(0, len(task_ids) - 1) + print(f"Task ID: {task_ids[idx]}") + print(f"Full prompt: {prompts[idx]}") + print("-" * 30) + print("Generated infill:") + print(generated_texts[idx]) + print("-" * 30) + print(f"Ground truth: {ground_truths[idx]}") + print("=" * 50) + + # Final progress save + save_progress(filename, all_generations, current_sample) + + avg_wall_time = sum(wall_times) / len(wall_times) if wall_times else 0 + metrics = { + "wall_time": avg_wall_time, + "generations": all_generations, + "total_processed": total_processed.item(), + } + return metrics + + +def evaluate_infilling_flexmdm( + model, + tokenizer, + dataloader, + args, + filename, +): + model.eval() + total_processed = torch.tensor(0, device=model.device) + wall_times = [] + device = model.device + + # Load previous progress + all_generations, processed_count = load_progress(filename) + + # Skip already processed samples + samples_to_skip = processed_count + current_sample = 0 + + save_interval = 10 # Save progress every 10 batches + batch_count = 0 + + for batch in tqdm(dataloader, disable=False, desc=f"Rank {dist.get_rank()}", position=dist.get_rank()): + # Skip already processed batches + batch_size = len(batch["task_ids"]) + if current_sample + batch_size <= samples_to_skip: + current_sample += batch_size + continue + + start_time = time.time() + task_ids = batch["task_ids"] + prompts = batch["prompts"] + suffixes = batch["suffixes"] + ground_truths = batch["ground_truths"] + + if dist.get_rank() == 0: + print(f"Processing batch {batch_count + 1}, samples {current_sample}-{current_sample + batch_size}") + + # Tokenize prefixes and suffixes + tokenized_prefixes = tokenizer( + prompts, + padding="max_length", + max_length=args.gen_length, + return_tensors="pt", + truncation=True + ).input_ids.to(device) + + tokenized_suffixes = tokenizer( + suffixes, + padding="max_length", + max_length=args.gen_length, + return_tensors="pt", + truncation=True + ).input_ids.to(device) + + # Generate infilled content + out, trace = generate_flexmdm_infilling( + model, + tokenized_prefixes, + tokenized_suffixes, + tokenizer, + steps=args.diffusion_steps, + model_type="variable", + temperature=args.temperature, + remasking=args.remasking, + alpha=args.alpha, + max_window=args.max_window, + confidence_method=args.confidence_method, + use_sliding_window=args.use_sliding_window, + ) + + generated_texts = tokenizer.batch_decode(out[:, :], skip_special_tokens=True) + + example_result = [ + { + "task_id": task_ids[j], + "prompt": prompts[j], + "suffix": suffixes[j], + "generated_infill": generated_texts[j], + "ground_truth": ground_truths[j], + } + for j in range(len(task_ids)) + ] + all_generations.extend(example_result) + total_processed += len(generated_texts) + current_sample += batch_size + wall_times.append(time.time() - start_time) + batch_count += 1 + + # Periodic progress saving + if batch_count % save_interval == 0: + save_progress(filename, all_generations, current_sample) + + # Print individual results + if dist.get_rank() == 0: + idx = random.randint(0, len(task_ids) - 1) + print(f"Task ID: {task_ids[idx]}") + print("-" * 30) + print("Generated infill:") + print(generated_texts[idx]) + print("-" * 30) + print(f"Suffix: {suffixes[idx]}") + print("-" * 30) + print(f"Ground truth: {ground_truths[idx]}") + print("=" * 50) + + # Final progress save + save_progress(filename, all_generations, current_sample) + + avg_wall_time = sum(wall_times) / len(wall_times) if wall_times else 0 + metrics = { + "wall_time": avg_wall_time, + "generations": all_generations, + "total_processed": total_processed.item(), + } + return metrics + + +class CustomDistributedSampler(DistributedSampler): + """Custom distributed sampler that doesn't add padding indices.""" + + def __init__( + self, + dataset, + num_replicas=None, + rank=None, + shuffle=True, + seed=0, + drop_last=False, + ) -> None: + if num_replicas is None: + if not dist.is_available(): + raise RuntimeError("Requires distributed package to be available") + num_replicas = dist.get_world_size() + if rank is None: + if not dist.is_available(): + raise RuntimeError("Requires distributed package to be available") + rank = dist.get_rank() + if rank >= num_replicas or rank < 0: + raise ValueError(f"Invalid rank {rank}, rank should be in the interval [0, {num_replicas - 1}]") + + self.dataset = dataset + self.num_replicas = num_replicas + self.rank = rank + self.epoch = 0 + self.drop_last = drop_last + + if self.drop_last and len(self.dataset) % self.num_replicas != 0: + self.num_samples = math.ceil((len(self.dataset) - self.num_replicas) / self.num_replicas) + self.total_size = self.num_samples * self.num_replicas + else: + self.total_size = len(self.dataset) + self.num_samples = len(self.dataset) // self.num_replicas + int( + rank < (self.total_size % self.num_replicas) + ) + + self.shuffle = shuffle + self.seed = seed + + def __iter__(self): + if self.shuffle: + g = torch.Generator() + g.manual_seed(self.seed + self.epoch) + indices = torch.randperm(len(self.dataset), generator=g).tolist() + else: + indices = list(range(len(self.dataset))) + + if not self.drop_last: + padding_size = self.total_size - len(indices) + if padding_size <= len(indices): + indices += indices[:padding_size] + else: + indices += (indices * math.ceil(padding_size / len(indices)))[:padding_size] + else: + indices = indices[:self.total_size] + assert len(indices) == self.total_size + + indices = indices[self.rank:self.total_size:self.num_replicas] + assert len(indices) == self.num_samples + + return iter(indices) + + def __len__(self): + return self.num_samples + + def set_epoch(self, epoch): + self.epoch = epoch + + +if __name__ == "__main__": + init_seed(42) + + local_rank = setup_ddp() + + parser = argparse.ArgumentParser() + parser.add_argument("--batch_size", type=int, default=1) + parser.add_argument("--data_path", type=str, default=None, help="Path to HumanEval infilling dataset") + parser.add_argument("--subsample", type=int, default=-1, help="Number of samples to evaluate (-1 for all)") + + # Model path + parser.add_argument("--checkpoint_path", type=str, default="/n/netscratch/albergo_lab/Lab/sft-checkpoints/llada-sft-openwebtext/checkpoint-40000") + + # Model type + parser.add_argument("--variable_length", action="store_true", help="whether to use FlexMDM or MDM") + parser.add_argument("--temperature", type=float, default=0.0, help="Temperature for the inference") + + # FlexMDM sampling configs-only valid for FlexMDM + parser.add_argument("--confidence_method", type=str, default="prob_diff", choices=["position", "prob_diff", "top_prob", "entropy"]) + parser.add_argument("--use_sliding_window", action="store_true", help="Use sliding window for confidence calculation") + parser.add_argument("--alpha", type=float, default=1.0, help="Alpha value for the sliding window") + parser.add_argument("--max_window", type=int, default=10, help="Maximum window size for the sliding window") + + # MDM sampling configs-only valid for MDM + parser.add_argument("--gen_length", type=int, default=1024) + parser.add_argument("--diffusion_steps", type=int, default=1024) + parser.add_argument("--remasking", type=str, default="random") + + parser.add_argument("--dont_save", action="store_true") + parser.add_argument("--output_dir", type=str, default="results/human_eval_infilling") + args = parser.parse_args() + + + # Log all arguments + if dist.get_rank() == 0: + print("=" * 50) + print("HUMAN EVAL INFILLING ARGUMENTS:") + print("=" * 50) + for arg_name, arg_value in vars(args).items(): + print(f"{arg_name}: {arg_value}") + print("=" * 50) + + # Load backbone model + backbone = AutoModel.from_pretrained("GSAI-ML/LLaDA-8B-Base", trust_remote_code=True, torch_dtype=torch.bfloat16).to(local_rank) + tokenizer = AutoTokenizer.from_pretrained("GSAI-ML/LLaDA-8B-Base", padding_side="right", trust_remote_code=True, use_fast=True) + print("Tokenizer and backbone model loaded") + + if args.variable_length: + # instruction finetuned FlexMDM load--same configurations as in the training script + lora_config = LoraConfig( + r=128, + lora_alpha=128, + target_modules=["q_proj", "k_proj", "v_proj", "transformer.ff_out"], + lora_dropout=0.1, + bias="none", + task_type=TaskType.CAUSAL_LM + ) + model = get_peft_model(backbone, lora_config) + model = LLaDA_DIT(model, pad_token_id=tokenizer.pad_token_id, d_model=4096) + + ckpt_dir = Path(args.checkpoint_path) + state = torch.load(ckpt_dir / "pytorch_model.bin", map_location="cpu") + missing, unexpected = model.load_state_dict(state, strict=False) + print(f"Missing keys: {missing}") + print(f"Unexpected keys: {unexpected}") + model = model.to(device=local_rank, dtype=torch.bfloat16).to(local_rank) + print("FlexMDM loaded") + else: + # fine-tuned LLaDA model load--same configurations as in the training script + model = PeftModel.from_pretrained(backbone, args.checkpoint_path, torch_dtype=torch.bfloat16).to( + local_rank + ) + if dist.get_world_size() > 1: + dist.barrier() # Make sure all processes are ready + for param in model.parameters(): + dist.broadcast(param.data, src=0) + print(f"Rank {local_rank}: Parameters synchronized") + else: + model = backbone.to(device = local_rank, dtype = torch.bfloat16).to(local_rank) + print("LLaDA model loaded") + + + # Create dataset and dataloader + dataset = HumanEvalInfillingDataset( + tokenizer, + variable_length=args.variable_length, + data_path=args.data_path, + subsample=args.subsample + ) + + dataloader = DataLoader( + dataset, + batch_size=args.batch_size, + sampler=CustomDistributedSampler(dataset, shuffle=False), + collate_fn=dataset.collate_fn, + ) + + os.makedirs(args.output_dir, exist_ok=True) + filename = f"{args.output_dir}/infill_{args.diffusion_steps}_alpha_{args.alpha}_max_window_{args.max_window}_{args.confidence_method}_{dist.get_rank()}_generations.json" + print(f"Saving generations to {filename}") + + if args.variable_length: + metrics = evaluate_infilling_flexmdm(model, tokenizer, dataloader, args, filename) + else: + metrics = evaluate_infilling_mdm(model, tokenizer, dataloader, args, filename) + + + if not args.dont_save: + with open(filename, "w") as f: + json.dump( + { + "generations": metrics["generations"], + "metrics": { + "wall_time": metrics["wall_time"], + "total_processed": metrics["total_processed"], + }, + "checkpoint_path": args.checkpoint_path, + "gen_length": args.gen_length, + "diffusion_steps": args.diffusion_steps, + "confidence_method": args.confidence_method, + "use_sliding_window": args.use_sliding_window, + "alpha": args.alpha, + "max_window": args.max_window, + }, + f, + indent=2, + ) + + # Clean up progress file after successful completion + progress_file = filename.replace('.json', '_progress.pkl') + if os.path.exists(progress_file): + os.remove(progress_file) + if dist.get_rank() == 0: + print(f"Cleaned up progress file: {progress_file}") + + cleanup_ddp() diff --git a/FlexMDM/scaling_flexmdm/flexmdm_inference.py b/FlexMDM/scaling_flexmdm/flexmdm_inference.py new file mode 100644 index 0000000000000000000000000000000000000000..d44064ed925f92d00970d0f69c2f14a828e8c6ea --- /dev/null +++ b/FlexMDM/scaling_flexmdm/flexmdm_inference.py @@ -0,0 +1,666 @@ +import torch +import numpy as np +import torch.nn.functional as F +from tqdm import tqdm +import torch.distributed as dist +from llada_inference import add_gumbel_noise +from typing import Optional, List, Tuple +import matplotlib.pyplot as plt +from flexmdm_interpolant import ModelPrediction +from flexmdm_interpolant import AnyOrderMaskInsertionInterpolant, LinearSchedule + + + +def clean_decode(ids, *, mask_id, tokenizer): + """ + Convert a list/1-D tensor of token-ids to a human-readable string. + - strips EOS + - keeps MASK (re-writes it to literal "[MASK]" so it's obvious) + """ + eos = tokenizer.eos_token_id + tokens = tokenizer.convert_ids_to_tokens(ids) + + pretty = [] + for tid, t in zip(ids, tokens): + if tid == eos: # skip EOS completely + continue + if tid == mask_id: # show mask explicitly + pretty.append("[MASK]") + else: + pretty.append(t) + + return tokenizer.convert_tokens_to_string(pretty).strip() + +def valid_insertion_positions(xt: torch.Tensor, pad: int, prompt_len: int) -> torch.Tensor: + not_pad_tokens = (xt != pad) # B X L + left_pad_tensor = torch.ones((xt.shape[0], 1), dtype = torch.bool, device = xt.device) + valid_positions = torch.cat([left_pad_tensor, not_pad_tokens], dim = 1) # B X (L+1) + valid_positions[:, : prompt_len] = False + return valid_positions + +def length(xt: torch.Tensor, pad: int, mask: int) -> torch.Tensor: + seq_len = (xt != pad).sum(dim = 1) + clean_tokens = ((xt != mask) & (xt != pad)).sum(dim = 1) + return seq_len, clean_tokens + +def plot_len_trace(len_trace: List[Tuple[int, int, int]], model_name: str): + plt.figure(figsize=(10, 5)) + steps, seq_lens, clean_tokens = zip(*len_trace) + plt.plot(steps, seq_lens, label = "Sequence length") + plt.plot(steps, clean_tokens, label = "Number of clean tokens") + plt.xlabel("Generation step") + plt.ylabel("Number") + plt.title("Quantity changes over generation steps") + plt.legend() + plt.savefig(f"len_trace_{model_name}.png") + plt.close() + +@torch.no_grad() +def any_order_mask_insertion_tau_leaping_sampling( + model: torch.nn.Module, + steps: int, + mask: int, + pad: int, + batch_size: int, + max_length: int, + prompts: Optional[torch.Tensor] = None, + confidence_based_sampling: bool = True, + return_trace: bool = False, + alpha: float = 5.0, # hyperparameter for window size calculation + max_window: int = 32, # Maximum window size for sliding window + confidence_method: str = "prob_diff", # "position", "top_prob", "prob_diff", "entropy" + use_sliding_window: bool = True, # whether to use sliding window for position selection +): + insertion_schedule = LinearSchedule() + unmasking_schedule = LinearSchedule() + + interpolant = AnyOrderMaskInsertionInterpolant( + insertion_schedule=insertion_schedule, + unmask_schedule=unmasking_schedule, + vocab_size=0, + mask_token = mask, + pad_token = pad, + max_length = 1024 + ) + + device = model.device + + # Initialize with prompts if provided, otherwise all pad tokens + if prompts is not None: + if prompts.size(0) == 1 and batch_size > 1: + prompts = prompts.expand(batch_size, -1) + + # Vectorized prompt handling + if prompts.size(1) > max_length: + prompts = prompts[:, :max_length] + + xt = torch.full((batch_size, max_length), pad, dtype=torch.int64, device=device) + + # Get individual prompt lengths for each batch element + prompt_lens = (prompts != pad).sum(dim=1) # (B,) - individual lengths per batch + + # Create vectorized mask for copying prompts + pos_indices = torch.arange(max_length, device=device).unsqueeze(0) # (1, L) + prompt_mask = pos_indices < prompt_lens.unsqueeze(1) # (B, L) + xt[prompt_mask] = prompts[prompt_mask] + + # Create vectorized prompt length mask for insertion protection + prompt_len_mask = pos_indices < prompt_lens.unsqueeze(1) # (B, L) - for protecting positions + else: + xt = torch.full((batch_size, max_length), pad, dtype=torch.int64, device=device) + prompt_lens = torch.zeros(batch_size, dtype=torch.long, device=device) + prompt_len_mask = torch.zeros((batch_size, max_length), dtype=torch.bool, device=device) + + sampling_trace = [] + dt = 1.0 / steps + t = torch.zeros(batch_size, device=device) + + # Precompute row indices for scatter + batch_idx_L = ( + torch.arange(batch_size, device=device) + .view(batch_size, 1) + .expand(batch_size, max_length) + ) + pos_idx_L = ( + torch.arange(max_length, device=device) + .view(1, max_length) + .expand(batch_size, max_length) + ) + + for i in range(steps): + # --- predict rates --- + pred = model(input_ids = xt, timesteps = t) + pred = ModelPrediction( + token_logits=pred["logits"], + expected_gaps=pred["length"], + ) + xt_len = (xt != pad).sum(dim=1) + pred_rate = interpolant.to_actual_rate(xt, pred, t) + unmask_rate = pred_rate.unmask_rate # (B, L, V) + len_rate = pred_rate.length_rate # (B, L+1) + + if i == steps - 1: + # last step: deterministic unmask via argmax + mask_pos = xt == mask + new_token = unmask_rate.argmax(dim=2) + new_xt = xt.clone() + new_xt[mask_pos] = new_token[mask_pos] + new_xt = torch.where(xt == pad, pad, new_xt) + new_xt = torch.where((xt != mask) & (xt != pad), xt, new_xt) + xt = new_xt + t = t + dt + continue + + if confidence_based_sampling: + # Confidence-based unmasking (vectorized) + mask_positions = (xt == mask) # (B, L) + num_mask_positions = mask_positions.sum(dim=1) # (B,) + + rate_scale_factor = unmasking_schedule.rate_scale_factor(t) + unmask_counts = torch.poisson(num_mask_positions.float() * rate_scale_factor * dt).long() # (B,) + + + # 2. Calculate confidence based on selected method + if confidence_method == "position": + # Position-based confidence: position i / len(xt)i dun + xt_len = (xt != pad).sum(dim=1) # (B,) - current sequence lengths + position_indices = torch.arange(max_length, device=device).unsqueeze(0).expand(batch_size, -1) # (B, L) + confidence = 1.0 - (position_indices.float() / xt_len.unsqueeze(1).float().clamp(min=1)) # (B, L) + + elif confidence_method == "top_prob": + # Top probability confidence + token_logits = pred.token_logits # (B, L, V) + unmask_probs = F.softmax(token_logits, dim=-1) # (B, L, V) + confidence = unmask_probs.max(dim=-1)[0] # (B, L) + + elif confidence_method == "prob_diff": + # Probability difference confidence (top - second top) + token_logits = pred.token_logits # (B, L, V) + unmask_probs = F.softmax(token_logits, dim=-1) # (B, L, V) + top2_probs, _ = torch.topk(unmask_probs, k=2, dim=-1) # (B, L, 2) + confidence = top2_probs[:, :, 0] - top2_probs[:, :, 1] # (B, L) + + elif confidence_method == "entropy": + # Entropy-based confidence (lower entropy = higher confidence) + token_logits = pred.token_logits # (B, L, V) + unmask_probs = F.softmax(token_logits, dim=-1) # (B, L, V) + # Calculate entropy: -sum(p * log(p)) + entropy = -torch.sum(unmask_probs * torch.log(unmask_probs + 1e-10), dim=-1) # (B, L) + # Convert to confidence: lower entropy = higher confidence + confidence = -entropy # (B, L) - negative entropy so lower entropy gives higher confidence + + else: + raise ValueError(f"Unknown confidence_method: {confidence_method}") + + # 3. Apply window constraint if enabled + if use_sliding_window: + # Vectorized window creation: calculate dynamic k for each batch + k_values = torch.minimum( + torch.minimum( + (alpha * unmask_counts).long(), + torch.tensor(max_window, device=device + ) + ), num_mask_positions) # (B,) + + # Get cumulative count of mask positions + mask_cumsum = mask_positions.cumsum(dim=1) # (B, L) + + # Create window mask: position is eligible if it's a mask and within first k masks + is_within_window = mask_cumsum <= k_values.unsqueeze(1) # (B, L) + window_mask = mask_positions & is_within_window # (B, L) + + # Set confidence to -inf for positions outside the window or non-mask positions + confidence = torch.where(window_mask, confidence, torch.tensor(-float('inf'), device=device)) + else: + # No window constraint - only mask positions are eligible + confidence = torch.where(mask_positions, confidence, torch.tensor(-float('inf'), device=device)) + + new_xt = xt.clone() + + # Vectorized unmasking + max_unmask = unmask_counts.max().item() + if max_unmask > 0: + # Get top-k indices for all batches + _, all_top_indices = torch.topk(confidence, k=max_unmask, dim=1, largest=True) # (B, max_unmask) + + # Create mask for valid unmask operations + unmask_mask = torch.arange(max_unmask, device=device).unsqueeze(0) < unmask_counts.unsqueeze(1) # (B, max_unmask) + + # Get most likely tokens (need logits for token generation) + if confidence_method == "position": + token_logits = pred.token_logits # (B, L, V) + most_likely_tokens = token_logits.argmax(dim=-1) # (B, L) + + # Gather the tokens to place at selected positions + selected_positions = all_top_indices[unmask_mask] # Flattened valid positions + batch_indices = torch.arange(batch_size, device=device).unsqueeze(1).expand(-1, max_unmask)[unmask_mask] # Corresponding batch indices + + # Apply unmasking with sampled tokens + new_xt[batch_indices, selected_positions] = most_likely_tokens[batch_indices, selected_positions] + else: + # Original tau-leaping unmask via Poisson + counts = torch.poisson(unmask_rate * dt).long() + mask_pos = xt == mask + counts[~mask_pos.unsqueeze(-1).expand_as(counts)] = 0 + counts[..., mask] = 0 + sum_c = counts.sum(dim=2) + one_event = sum_c == 1 + new_token = counts.argmax(dim=2) + new_xt = xt.clone() + new_xt[one_event] = new_token[one_event] + new_xt = torch.where(xt == pad, pad, new_xt) + new_xt = torch.where((xt != mask) & (xt != pad), xt, new_xt) + xt = new_xt + + # insertion only on non-last + if i != steps - 1: + # --- Poisson insertion, compute new lengths and fill masks --- + ext = torch.poisson(len_rate * dt).long() # (B, L+1) + xt_len = xt.ne(pad).sum(dim=1) # (B,) + gaps = torch.arange(max_length + 1, device=device).view(1, -1) + ext = ext * (gaps <= xt_len.view(batch_size, 1)).long() + + # Don't insert within the prompt region + if prompts is not None: + # Create extended prompt mask for (L+1) positions + extended_prompt_mask = torch.zeros((batch_size, max_length + 1), dtype=torch.bool, device=device) + extended_pos_indices = torch.arange(max_length + 1, device=device).unsqueeze(0) + extended_prompt_mask[:, :max_length] = extended_pos_indices[:, :max_length] < prompt_lens.unsqueeze(1) + ext[extended_prompt_mask] = 0 + + total_ext = ext.sum(dim=1) + valid = xt_len + total_ext <= max_length + ext = ext * valid.view(batch_size, 1).long() + + # compute prefix sums of insertions + ext_ex = ext.int().cumsum(dim=1) # (B, L+1) + new_len = xt_len + total_ext # (B,) + + # initialize with pads, then fill mask up to new_len + xt_tmp = torch.full_like(xt, pad) + mask_pos = pos_idx_L < new_len.view(batch_size, 1) + xt_tmp[mask_pos] = mask + + # Vectorized: Preserve the original prompt tokens + if prompts is not None: + xt_tmp[prompt_len_mask] = prompts[prompt_len_mask] + + # shift and scatter original tokens (excluding prompt region) + new_pos_orig = pos_idx_L + ext_ex[:, :max_length] # (B, L) + orig_mask = pos_idx_L < xt_len.view(batch_size, 1) + + # Vectorized: Don't move prompt tokens + if prompts is not None: + orig_mask = orig_mask & ~prompt_len_mask + + flat_b = batch_idx_L[orig_mask] + flat_p = new_pos_orig[orig_mask] + xt_tmp[flat_b, flat_p] = new_xt[orig_mask] + else: + xt_tmp = new_xt + + xt = xt_tmp + t = t + dt + if return_trace: + sampling_trace.append(xt) + + return xt, sampling_trace + + +@torch.no_grad() +def generate_flexmdm( + model, + prompt, + tokenizer, + steps: int, + model_type: str, + temperature: float, + remasking: str = 'random', + mask_id: int = 126336, + alpha: float = 5.0, # Add alpha parameter + max_window: int = 32, # Maximum window size for sliding window + confidence_method: str = "prob_diff", # "position", "top_prob", "prob_diff", "entropy" + use_sliding_window: bool = True, # Add sliding window parameter +): + pad_id = tokenizer.pad_token_id + device = model.device + B = prompt.size(0) if isinstance(prompt, torch.Tensor) else 1 + L = 1024 + dt = 1.0 / steps + t = 0.0 + + with torch.autocast(device_type="cuda"): + return any_order_mask_insertion_tau_leaping_sampling( + model, steps, mask_id, pad_id, B, L, + prompts=prompt, return_trace=True, alpha=alpha, + confidence_method=confidence_method, use_sliding_window=use_sliding_window, max_window=max_window + ) + + +@torch.no_grad() +def any_order_mask_insertion_infilling_sampling( + model: torch.nn.Module, + steps: int, + mask: int, + pad: int, + batch_size: int, + max_length: int, + prefix: Optional[torch.Tensor] = None, + suffix: Optional[torch.Tensor] = None, + confidence_based_sampling: bool = True, + return_trace: bool = False, + alpha: float = 5.0, # hyperparameter for window size calculation + max_window: int = 32, # Maximum window size for sliding window + confidence_method: str = "prob_diff", # "position", "top_prob", "prob_diff", "entropy" + use_sliding_window: bool = True, # whether to use sliding window for position selection +): + insertion_schedule = LinearSchedule() + unmasking_schedule = LinearSchedule() + + interpolant = AnyOrderMaskInsertionInterpolant( + insertion_schedule=insertion_schedule, + unmask_schedule=unmasking_schedule, + vocab_size=0, + mask_token = mask, + pad_token = pad, + max_length = 1024 + ) + + device = model.device + + # Initialize sequence with prefix immediately followed by suffix + xt = torch.full((batch_size, max_length), pad, dtype=torch.int64, device=device) + + # Handle prefix - vectorized + # NOTE: prefix tensor does NOT need manual padding - function handles it automatically + if prefix is not None: + if prefix.size(0) == 1 and batch_size > 1: + prefix = prefix.expand(batch_size, -1) + # Function automatically handles variable-length prefixes by calculating actual lengths + prefix_lens = (prefix != pad).sum(dim=1) # (B,) - finds actual length excluding pad tokens + + # Vectorized prefix copying + pos_indices = torch.arange(max_length, device=device).unsqueeze(0) # (1, L) + prefix_mask = pos_indices < prefix_lens.unsqueeze(1) # (B, L) + xt[prefix_mask] = prefix[prefix_mask] + else: + prefix_lens = torch.zeros(batch_size, dtype=torch.long, device=device) + + # Handle suffix - vectorized placement immediately after prefix + # NOTE: suffix tensor does NOT need manual padding - function handles it automatically + if suffix is not None: + if suffix.size(0) == 1 and batch_size > 1: + suffix = suffix.expand(batch_size, -1) + # Function automatically handles variable-length suffixes by calculating actual lengths + suffix_lens = (suffix != pad).sum(dim=1) # (B,) - finds actual length excluding pad tokens + + # Vectorized suffix copying - place immediately after prefix + pos_indices = torch.arange(max_length, device=device).unsqueeze(0) # (1, L) + + # Create suffix placement mask: positions from prefix_len to prefix_len + suffix_len + suffix_start_positions = prefix_lens.unsqueeze(1) # (B, 1) + suffix_relative_positions = pos_indices - suffix_start_positions # (B, L) + suffix_mask = (suffix_relative_positions >= 0) & (suffix_relative_positions < suffix_lens.unsqueeze(1)) # (B, L) + + # Create corresponding indices for suffix tensor + suffix_indices = torch.where(suffix_mask, suffix_relative_positions, 0) + + # Apply suffix where mask is true + batch_indices = torch.arange(batch_size, device=device).unsqueeze(1).expand_as(suffix_mask) + valid_suffix_mask = suffix_mask & (suffix_indices < suffix.size(1)) + + if valid_suffix_mask.any(): + xt[valid_suffix_mask] = suffix[batch_indices[valid_suffix_mask], suffix_indices[valid_suffix_mask]] + else: + suffix_lens = torch.zeros(batch_size, dtype=torch.long, device=device) + + # Calculate the current sequence length (prefix + suffix) + current_seq_lens = prefix_lens + suffix_lens # (B,) + + # Calculate infill region boundaries - between prefix and suffix + infill_start = prefix_lens # (B,) - start right after prefix + + # Create position indices for vectorized operations + pos_indices = torch.arange(max_length, device=device).unsqueeze(0) # (1, L) + + sampling_trace = [] + dt = 1.0 / steps + t = torch.zeros(batch_size, device=device) + + # Precompute row indices for scatter + batch_idx_L = ( + torch.arange(batch_size, device=device) + .view(batch_size, 1) + .expand(batch_size, max_length) + ) + pos_idx_L = ( + torch.arange(max_length, device=device) + .view(1, max_length) + .expand(batch_size, max_length) + ) + + for i in range(steps): + # Update infill region mask based on current sequence - vectorized + current_lens = (xt != pad).sum(dim=1) # (B,) + infill_end = current_lens - suffix_lens # (B,) - end before suffix starts + infill_mask = (pos_indices >= infill_start.unsqueeze(1)) & (pos_indices < infill_end.unsqueeze(1)) # (B, L) + + # --- predict rates --- + pred = model(input_ids = xt, timesteps = t) + pred = ModelPrediction( + token_logits=pred["logits"], + expected_gaps=pred["length"], + ) + xt_len = (xt != pad).sum(dim=1) + pred_rate = interpolant.to_actual_rate(xt, pred, t) + unmask_rate = pred_rate.unmask_rate # (B, L, V) + len_rate = pred_rate.length_rate # (B, L+1) + + if i == steps - 1: + # last step: deterministic unmask via argmax (only in infill region) + mask_pos = (xt == mask) & infill_mask + new_token = unmask_rate.argmax(dim=2) + new_xt = xt.clone() + new_xt[mask_pos] = new_token[mask_pos] + xt = new_xt + t = t + dt + continue + + if confidence_based_sampling: + # Confidence-based unmasking (vectorized, constrained to infill region) + mask_positions = (xt == mask) & infill_mask # (B, L) - only masks in infill region + num_mask_positions = mask_positions.sum(dim=1) # (B,) + + # 1. Determine number of tokens to unmask using Poisson + rate_scale_factor = unmasking_schedule.rate_scale_factor(t) + unmask_counts = torch.poisson(num_mask_positions.float() * rate_scale_factor * dt).long() # (B,) + + # 2. Calculate confidence based on selected method + if confidence_method == "position": + # Position-based confidence within infill region + infill_lengths = infill_end - infill_start # (B,) + relative_pos = pos_indices - infill_start.unsqueeze(1) # (B, L) + confidence = 1.0 - (relative_pos.float() / infill_lengths.unsqueeze(1).float().clamp(min=1)) # (B, L) + + elif confidence_method == "top_prob": + # Top probability confidence + token_logits = pred.token_logits # (B, L, V) + unmask_probs = F.softmax(token_logits, dim=-1) # (B, L, V) + confidence = unmask_probs.max(dim=-1)[0] # (B, L) + + elif confidence_method == "prob_diff": + # Probability difference confidence (top - second top) + token_logits = pred.token_logits # (B, L, V) + unmask_probs = F.softmax(token_logits, dim=-1) # (B, L, V) + top2_probs, _ = torch.topk(unmask_probs, k=2, dim=-1) # (B, L, 2) + confidence = top2_probs[:, :, 0] - top2_probs[:, :, 1] # (B, L) + + elif confidence_method == "entropy": + # Entropy-based confidence (lower entropy = higher confidence) + token_logits = pred.token_logits # (B, L, V) + unmask_probs = F.softmax(token_logits, dim=-1) # (B, L, V) + entropy = -torch.sum(unmask_probs * torch.log(unmask_probs + 1e-10), dim=-1) # (B, L) + confidence = -entropy # (B, L) - negative entropy so lower entropy gives higher confidence + + else: + raise ValueError(f"Unknown confidence_method: {confidence_method}") + + # 3. Apply window constraint if enabled (within infill region) + if use_sliding_window: + # Calculate dynamic k for each batch (based on masks in infill region) + k_values = torch.minimum( + torch.minimum( + (alpha * unmask_counts).long(), + torch.tensor(max_window, device=device) + ), num_mask_positions) # (B,) + + # Get cumulative count of mask positions in infill region + mask_cumsum = mask_positions.cumsum(dim=1) # (B, L) + + # Create window mask: position is eligible if it's a mask in infill and within first k masks + is_within_window = mask_cumsum <= k_values.unsqueeze(1) # (B, L) + window_mask = mask_positions & is_within_window # (B, L) + + # Set confidence to -inf for positions outside the window or non-eligible positions + confidence = torch.where(window_mask, confidence, torch.tensor(-float('inf'), device=device)) + else: + # No window constraint - only mask positions in infill region are eligible + confidence = torch.where(mask_positions, confidence, torch.tensor(-float('inf'), device=device)) + + new_xt = xt.clone() + + # Vectorized unmasking + max_unmask = unmask_counts.max().item() + if max_unmask > 0: + # Get top-k indices for all batches + _, all_top_indices = torch.topk(confidence, k=max_unmask, dim=1, largest=True) # (B, max_unmask) + + # Create mask for valid unmask operations + unmask_mask = torch.arange(max_unmask, device=device).unsqueeze(0) < unmask_counts.unsqueeze(1) # (B, max_unmask) + + # Get most likely tokens + if confidence_method == "position": + token_logits = pred.token_logits # (B, L, V) + most_likely_tokens = token_logits.argmax(dim=-1) # (B, L) + + # Gather the tokens to place at selected positions + selected_positions = all_top_indices[unmask_mask] # Flattened valid positions + batch_indices = torch.arange(batch_size, device=device).unsqueeze(1).expand(-1, max_unmask)[unmask_mask] # Corresponding batch indices + + # Apply unmasking with sampled tokens + new_xt[batch_indices, selected_positions] = most_likely_tokens[batch_indices, selected_positions] + else: + # Original tau-leaping unmask via Poisson (constrained to infill region) + counts = torch.poisson(unmask_rate * dt).long() + mask_pos = (xt == mask) & infill_mask + counts[~mask_pos.unsqueeze(-1).expand_as(counts)] = 0 + counts[..., mask] = 0 + sum_c = counts.sum(dim=2) + one_event = sum_c == 1 + new_token = counts.argmax(dim=2) + new_xt = xt.clone() + new_xt[one_event] = new_token[one_event] + + # insertion only on non-last (constrained to infill region) + if i != steps - 1: + # --- Poisson insertion, compute new lengths and fill masks --- + ext = torch.poisson(len_rate * dt).long() # (B, L+1) + xt_len = xt.ne(pad).sum(dim=1) # (B,) + gaps = torch.arange(max_length + 1, device=device).view(1, -1) + ext = ext * (gaps <= xt_len.view(batch_size, 1)).long() + + # Constrain insertions to infill region only - vectorized + extended_pos_indices = torch.arange(max_length + 1, device=device).unsqueeze(0) + current_infill_end = xt_len - suffix_lens # Current position where suffix starts + extended_infill_mask = (extended_pos_indices >= infill_start.unsqueeze(1)) & (extended_pos_indices <= current_infill_end.unsqueeze(1)) + ext[~extended_infill_mask] = 0 + + total_ext = ext.sum(dim=1) + valid = xt_len + total_ext <= max_length + ext = ext * valid.view(batch_size, 1).long() + + # compute prefix sums of insertions + ext_ex = ext.int().cumsum(dim=1) # (B, L+1) + new_len = xt_len + total_ext # (B,) + + # initialize with pads, then fill mask up to new_len + xt_tmp = torch.full_like(xt, pad) + mask_pos = pos_idx_L < new_len.view(batch_size, 1) + xt_tmp[mask_pos] = mask + + # Vectorized: Preserve prefix at the beginning + if prefix is not None: + prefix_mask = pos_indices < prefix_lens.unsqueeze(1) + xt_tmp[prefix_mask] = prefix[prefix_mask] + + # Vectorized: Preserve suffix at the end (accounting for insertions) + if suffix is not None: + # Calculate new suffix start positions for all batches + new_suffix_starts = new_len - suffix_lens # (B,) + + # Create suffix placement mask for new positions + new_suffix_positions = pos_indices - new_suffix_starts.unsqueeze(1) # (B, L) + new_suffix_mask = (new_suffix_positions >= 0) & (new_suffix_positions < suffix_lens.unsqueeze(1)) # (B, L) + new_suffix_mask = new_suffix_mask & (new_suffix_starts.unsqueeze(1) >= 0) # Ensure valid start positions + + # Create indices for suffix tensor + suffix_tensor_indices = torch.where(new_suffix_mask, new_suffix_positions, 0) + + # Apply suffix where mask is valid + batch_indices = torch.arange(batch_size, device=device).unsqueeze(1).expand_as(new_suffix_mask) + valid_new_suffix_mask = new_suffix_mask & (suffix_tensor_indices < suffix.size(1)) + + if valid_new_suffix_mask.any(): + xt_tmp[valid_new_suffix_mask] = suffix[batch_indices[valid_new_suffix_mask], suffix_tensor_indices[valid_new_suffix_mask]] + + # shift and scatter original tokens (only tokens in the infill region) - vectorized + new_pos_orig = pos_idx_L + ext_ex[:, :max_length] # (B, L) + orig_mask = pos_idx_L < xt_len.view(batch_size, 1) + + # Only move tokens that are in the infill region (between prefix and suffix) - vectorized + current_infill_mask = (pos_idx_L >= prefix_lens.unsqueeze(1)) & (pos_idx_L < (xt_len - suffix_lens).unsqueeze(1)) + orig_mask = orig_mask & current_infill_mask + + flat_b = batch_idx_L[orig_mask] + flat_p = new_pos_orig[orig_mask] + xt_tmp[flat_b, flat_p] = new_xt[orig_mask] + else: + xt_tmp = new_xt + + xt = xt_tmp + t = t + dt + if return_trace: + sampling_trace.append(xt) + + return xt, sampling_trace + + +@torch.no_grad() +def generate_flexmdm_infilling( + model, + prefix, + suffix, + tokenizer, + steps: int, + model_type: str, + temperature: float, + remasking: str = 'random', + mask_id: int = 126336, + alpha: float = 5.0, + max_window: int = 32, + confidence_method: str = "prob_diff", + use_sliding_window: bool = True, +): + pad_id = tokenizer.pad_token_id + device = model.device + B = prefix.size(0) if isinstance(prefix, torch.Tensor) else 1 + L = 1024 + + with torch.autocast(device_type="cuda"): + return any_order_mask_insertion_infilling_sampling( + model, steps, mask_id, pad_id, B, L, + prefix=prefix, suffix=suffix, return_trace=True, alpha=alpha, + confidence_method=confidence_method, use_sliding_window=use_sliding_window, max_window=max_window + ) + + diff --git a/FlexMDM/scaling_flexmdm/flexmdm_interpolant.py b/FlexMDM/scaling_flexmdm/flexmdm_interpolant.py new file mode 100644 index 0000000000000000000000000000000000000000..81d8285bea8bff83227e2c0bdd5ba9ed0451f27b --- /dev/null +++ b/FlexMDM/scaling_flexmdm/flexmdm_interpolant.py @@ -0,0 +1,367 @@ +import torch +from dataclasses import dataclass +from torch import Tensor +import abc +from typing import Tuple +import abc +from omegaconf import DictConfig +import torch +import torch.nn as nn +from torch import Tensor +from typing import Optional +import torch.nn.functional as F + + + +@dataclass +class ModelPrediction: + token_logits: Tensor + length_posterior: Optional[Tensor] + expected_gaps: Tensor + + def __init__( + self, + token_logits: Tensor, + length_posterior: Optional[Tensor] = None, + expected_gaps: Optional[Tensor] = None, + ): + assert length_posterior is not None or expected_gaps is not None + self.token_logits = token_logits + self.length_posterior = length_posterior + self.expected_gaps = expected_gaps + if self.expected_gaps is None: + _, _, L = self.length_posterior.shape + index = torch.arange(0, L, device=token_logits.device).view(1, 1, -1) + self.expected_gaps = (self.length_posterior * index).sum(dim=-1) + + +@dataclass +class Rate: + unmask_rate: Tensor # Shape [Batch, Length, Vocab] + length_rate: Tensor # Shape [Batch] + + +@dataclass +class HittingTime: + insertion_time: Tensor # Shape [Batch, Length] + unmasking_time: Tensor # Shape [Batch, Length] + + def __iter__(self): + yield from [self.insertion_time, self.unmasking_time] + + +@dataclass +class JointInterpolantResult: + # Joint Interpolant + xt: Tensor # Shape [Batch, Length] + st: Tensor # Shape [Batch, Length] + _x1: Tensor + _pad_token: int + _mask_token: int + + + def to(self, device): + self.xt = self.xt.to(device) + self.st = self.st.to(device) + self._x1 = self._x1.to(device) + return self + + @property + def mask_indices(self) -> Tensor: + return self.xt == self._mask_token + + @property + def unmasked(self) -> Tensor: + return torch.gather(self._x1, 1, self.st) + + @property + def xt_length(self) -> Tensor: + # Calculate length of xt + return (self.xt != self._pad_token).sum(dim=1) + + @property + def x1_length(self) -> Tensor: + # Calculate length of x1 + return (self._x1 != self._pad_token).sum(dim=1) + + @property + def gaps_and_mask(self) -> tuple[Tensor, Tensor]: + x1_len = self.x1_length + gaps = self.st.clone() + + pad_front = gaps.new_zeros((gaps.shape[0], 1)) - 1 # -1 for the front padding + pad_back = gaps.new_zeros((gaps.shape[0], 1)) + gaps = torch.cat([pad_front, gaps, pad_back], dim=1) # Add a leading zero + + gaps.scatter_( + 1, self.xt_length.unsqueeze(1) + 1, x1_len.unsqueeze(1) + ) # Fill the last position with x1_len + + gaps = gaps[:, 1:] - gaps[:, :-1] - 1 + gaps = torch.clamp(gaps, min=0) + + idx = torch.arange(gaps.size(1), device=self.xt.device).unsqueeze( + 0 + ) # shape [1, max_gap] + mask = idx <= self.xt_length.unsqueeze(1) + gaps[~mask] = 0 + + return gaps, mask + +def get_schedule_from_config(config: DictConfig): + match config.type: + case "geometric": + return GeometricSchedule(min_val=config.min, max_val=config.max) + case "linear": + return LinearSchedule() + case _: + raise ValueError(f"Invalid schedule type: {config.type}") + + +class Schedule(abc.ABC): + """ + Generic schedule class for masking or noising + This represents function a : [0, 1] -> [0, 1] satisfying a(0) = 0, a(1) = 1 or at least approximately + """ + + @abc.abstractmethod + def at(self, t: Tensor): + """ + Return value a(t) + """ + raise NotImplementedError + + @abc.abstractmethod + def derivative_at(self, t: Tensor): + """ + Return d/dt a(t) + """ + raise NotImplementedError + + def rate_scale_factor(self, t: Tensor) -> Tensor: + """ + Return d/dt a(t) / (1 - a(t)) common in rate matrix calculation + """ + return self.derivative_at(t) / (1 - self.at(t)) + + def sample(self, shape, device) -> Tensor: + """ + Sample from the schedule, returns a tensor of shape `shape` with values in [0, 1] + """ + uniform = torch.rand(shape, device=device) + return self.inv(uniform) + + def sample_truncated(self, threshold, shape, device) -> Tensor: + """ + Sample from a truncated schedule, returns a tensor of shape `shape` with values in [threshold, 1] + """ + uniform = torch.rand(shape, device=device) + return self.inv(uniform * (1 - threshold) + threshold) + + @abc.abstractmethod + def inv(self, alpha: Tensor): + """ + Given alpha in [0, 1] such that a(t)=alpha, returns the corresponding t. + """ + raise NotImplementedError + + +class LinearSchedule(Schedule): + def __init__(self): + pass + + def at(self, t: Tensor): + return t + + def derivative_at(self, t: Tensor): + return torch.ones_like(t, device=t.device) + + def inv(self, alpha: Tensor): + return alpha + + +class GeometricSchedule(Schedule, nn.Module): + def __init__(self, min_val: float, max_val: float): + super().__init__() + self.register_buffer("min", Tensor([min_val])) + self.register_buffer("max", Tensor([max_val])) + + def at(self, t: Tensor): + min_val = self.min.to(t.device) + max_val = self.max.to(t.device) + return torch.exp(-(min_val ** (1 - t)) * max_val**t) + + def derivative_at(self, t): + min_val = self.min.to(t.device) + max_val = self.max.to(t.device) + return ( + self.at(t) + * min_val ** (1 - t) + * max_val**t + * (min_val.log() - max_val.log()) + ) + + def inv(self, alpha: Tensor): + log_min = self.min.to(alpha.device).log() + log_max = self.max.to(alpha.device).log() + return (torch.log(-torch.log(alpha)) - log_min) / (log_max - log_min) + + +class JointInterpolant(abc.ABC): + def __init__( + self, + vocab_size: int, + mask_token: int, + pad_token: int, + max_length: int, + ): + """ + TODO: Add knobs + """ + self.mask_token = mask_token + self.pad_token = pad_token + self.max_length = max_length + self.vocab_size = vocab_size + + @abc.abstractmethod + def elbo_weight(self, t: Tensor, x1: Tensor): + """ + Return the ELBO weight for the training, can be changed depends on the empirical results + Shape: + t: [B] + Returns: + weight_unmask: [B, L] + weight_delete: [B, L+1] + """ + raise NotImplementedError + + @abc.abstractmethod + def to_actual_rate(self, prediction: ModelPrediction, t: Tensor) -> Rate: + raise NotImplementedError + + @abc.abstractmethod + def sample_interpolant(self, t: Tensor, x1: Tensor) -> JointInterpolantResult: + """ + Sample the interpolant xt from x1 at time t + Shapes: + x1: [B, L] + t: [B] + Returns: + xt: [B, L] + st: [B, L] boolean mask of positions that corresponds to xt + xt_mask_indices: [B, L] boolean mask of positions that are masked at xt + x1_remained: [B, L] tokens that are not deleted, used for the training target + gap_counts: [B, L+1] the number of deleted tokens between xt slots + """ + raise NotImplementedError + + +class AnyOrderMaskInsertionInterpolant(JointInterpolant): + def __init__( + self, + insertion_schedule: Schedule, + unmask_schedule: Schedule, + vocab_size: int, + mask_token: int, + pad_token: int, + max_length: int, + ): + super().__init__(vocab_size, mask_token, pad_token, max_length) + self.insertion_schedule = insertion_schedule + self.unmask_schedule = unmask_schedule + + def hitting_time(self, t: Tensor, x1: Tensor) -> tuple[Tensor, Tensor]: + """ + t1 is sampled from a uniform distribution over [0, 1]. when t1 < self.mask_schedule.at(t) + t2 is sampled from a uniform distribution over [t1, 1] + """ + B, L = x1.shape + eps = 1e-6 + + insert_time = self.insertion_schedule.sample((B, L), device=x1.device) + insert_time = eps + (1 - eps) * insert_time # ensure t1 is not 0 + unmask_time = self.unmask_schedule.sample_truncated( + insert_time, (B, L), device=x1.device + ) + + return insert_time, unmask_time + + def elbo_weight(self, t: Tensor, x1: Tensor): + """ + Return the ELBO weight for the training, can be changed depends on the empirical results + """ + eps = 1e-6 + insert_weight = self.insertion_schedule.rate_scale_factor(t) + insert_weight = insert_weight[:, None].expand(-1, x1.shape[1] + 1) + + unmask_weight = 1.0 / (1 - t + eps) + unmask_weight = unmask_weight.unsqueeze(1).expand(-1, x1.shape[1]) + + return unmask_weight.clone(), insert_weight.clone() + + def to_actual_rate( + self, xt: Tensor, prediction: ModelPrediction, t: Tensor + ) -> Rate: + """ + Return the actual rate for the sampling + Args: + xt: [B, L] the sampled tokens + prediction: ModelPrediction object containing token_posterior and expected_gaps + t: [B] the time parameter + """ + token_posterior = F.softmax(prediction.token_logits, dim=-1) # (B, L, V) + unmask_rate = token_posterior * self.unmask_schedule.rate_scale_factor(t).view( + -1, 1, 1 + ) + length_rate = ( + prediction.expected_gaps + * self.insertion_schedule.rate_scale_factor(t).view(-1, 1) + ) + + return Rate( + unmask_rate=unmask_rate, # (B, L, V) + length_rate=length_rate, # (B, L+1) + ) + + def sample_interpolant(self, t: Tensor, x1: Tensor, prompt_indices: Tensor) -> JointInterpolantResult: + """ + Shapes: + x1: [B, L] + t: [B] + Returns: + xt: [B, L] + st: [B, L] boolean mask of positions that corresponds to xt + xt_mask_indices: [B, L] boolean mask of positions that are masked at xt + x1_remained: [B, L] tokens that are not deleted, used for the training target + gap_counts: [B, L+1] the number of deleted tokens between xt slots + """ + # sample the stopping time (B, L, 2) + insertion_time, unmasking_time = self.hitting_time(t, x1) + + clean_tokens = x1.ne(self.pad_token) + deleted_tokens = clean_tokens & (t[:, None] < insertion_time) & (~prompt_indices) + masked_tokens = ( + clean_tokens + & (t[:, None] >= insertion_time) + & (t[:, None] < unmasking_time) + ) & (~prompt_indices) + + xt = torch.where( + deleted_tokens, + self.pad_token, # for deletion, change to pad token + torch.where( + masked_tokens, + self.mask_token, # for masking, change to mask token + x1, + ), + ) + + st = xt.ne(self.pad_token).argsort(dim=1, descending=True, stable=True) + xt = torch.gather(xt, 1, st) + st[xt == self.pad_token] = 0 + + return JointInterpolantResult( + xt=xt, st=st, _x1=x1, _pad_token=self.pad_token, _mask_token=self.mask_token + ) + + diff --git a/FlexMDM/scaling_flexmdm/flexmdm_trainer.py b/FlexMDM/scaling_flexmdm/flexmdm_trainer.py new file mode 100644 index 0000000000000000000000000000000000000000..579b0153baacf6edec5e23cd883b0544ae61f442 --- /dev/null +++ b/FlexMDM/scaling_flexmdm/flexmdm_trainer.py @@ -0,0 +1,261 @@ +import torch +import torch.nn.functional as F +from torch import Tensor +from transformers import Trainer +from transformers import DefaultDataCollator +import random +from tqdm import tqdm +import pickle +import torch.distributed as dist +from flexmdm_interpolant import AnyOrderMaskInsertionInterpolant, GeometricSchedule, LinearSchedule + +def jump_kernel_elbo(x, y, eps=1e-6): + # x_safe: true length + # y_safe: predicted length + x_safe = torch.clamp(x, min=eps) + y_safe = torch.clamp(y, min=eps) + + return y_safe - x_safe + x_safe * (torch.log(x_safe) - torch.log(y_safe)) + +def move_to_device(list_of_tensors, device): + return [t.to(device) for t in list_of_tensors] + + +def vectorized_infill_process(input_ids, pad_token, prefix_cutoff, infill_tokens): + """ + Performs a vectorized infill process on a batch of input IDs without loops. + """ + batch_size, seq_len = input_ids.shape + device = input_ids.device + infill_tokens = torch.tensor(infill_tokens, dtype=input_ids.dtype, device=device) + infill_len = len(infill_tokens) + + sample_lengths = (input_ids != pad_token).sum(dim=1) + + # Generate prefix_ends indices + high_prefix_end = sample_lengths - 2 + prefix_range = high_prefix_end - prefix_cutoff + rand_prefix_ends = torch.rand(batch_size, device=device) * prefix_range + prefix_ends = (prefix_cutoff + rand_prefix_ends).long() + + # Generate suffix_starts indices + low_suffix_start = prefix_ends + 1 + high_suffix_start = sample_lengths + suffix_range = high_suffix_start - low_suffix_start + rand_suffix_starts = torch.rand(batch_size, device=device) * suffix_range + suffix_starts = (low_suffix_start + rand_suffix_starts).long() + + # Calculate new lengths and create the new tensor + new_lengths = sample_lengths + 2 * infill_len + new_sample = torch.full((batch_size, input_ids.shape[1]), pad_token, dtype=input_ids.dtype, device=device) + + # Create index tensors for both the new and original sequences + new_indices = torch.arange(input_ids.shape[1], device=device).unsqueeze(0) + orig_indices = torch.arange(seq_len, device=device).unsqueeze(0) + + # --- Copy prefix --- + prefix_mask_orig = orig_indices < prefix_ends.unsqueeze(1) + prefix_mask_new = new_indices < prefix_ends.unsqueeze(1) + new_sample[prefix_mask_new] = input_ids[prefix_mask_orig] + + # --- Insert first infill token block --- + infill_offsets = torch.arange(infill_len, device=device) + insertion_indices = prefix_ends.unsqueeze(1) + infill_offsets + new_sample.scatter_(1, insertion_indices, infill_tokens.repeat(batch_size, 1)) + + # --- Copy middle part --- + middle_indices_orig = (orig_indices >= prefix_ends.unsqueeze(1)) & (orig_indices < suffix_starts.unsqueeze(1)) + middle_indices_new = (new_indices >= prefix_ends.unsqueeze(1) + infill_len) & (new_indices < suffix_starts.unsqueeze(1) + infill_len) + new_sample[middle_indices_new] = input_ids[middle_indices_orig] + + # --- Insert second infill token block --- + insertion_indices = suffix_starts.unsqueeze(1) + infill_len + infill_offsets + new_sample.scatter_(1, insertion_indices, infill_tokens.repeat(batch_size, 1)) + + # --- Copy suffix --- + suffix_indices_orig = (orig_indices >= suffix_starts.unsqueeze(1)) & (orig_indices < sample_lengths.unsqueeze(1)) + suffix_indices_new = (new_indices >= suffix_starts.unsqueeze(1) + 2 * infill_len) & (new_indices < new_lengths.unsqueeze(1)) + new_sample[suffix_indices_new] = input_ids[suffix_indices_orig] + + # Create vectorized prompt indices + prompt_indices = (new_indices < prefix_ends.unsqueeze(1) + infill_len) | (new_indices >= suffix_starts.unsqueeze(1) + infill_len) + prompt_indices = prompt_indices & (new_sample != pad_token) + + return new_sample, prompt_indices + +class dLLMVariableLengthTrainer(Trainer): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.label_names = ["input_ids"] + + def compute_loss(self, model, inputs, num_items_in_batch=None, return_outputs=False): + """ + Variable Length Diffusion Loss Computation + """ + result, elbo_weights, t = inputs.pop("interpolant_result"), inputs.pop("elbo_weights"), inputs.pop("t") + if "length" in inputs: + inputs.pop("length") + if "prompt_indices" in inputs: + inputs.pop("prompt_indices") + if "prefix_cutoff" in inputs: + inputs.pop("prefix_cutoff") + + masked_indices, x1_remained = result.mask_indices, result.unmasked + gap_counts, len_loss_indices = result.gaps_and_mask + unmask_weight, insert_weight = elbo_weights + + normalize_constant = 1024 + batch_size = x1_remained.shape[0] + + # device movement + device = next(model.parameters()).device + masked_indices, x1_remained, gap_counts, len_loss_indices, unmask_weight, insert_weight, t = move_to_device( + [masked_indices, x1_remained, gap_counts, len_loss_indices, unmask_weight, insert_weight, t], device + ) + + + # model forward pass + out = model(timesteps = t, **inputs) + logits, scalar_pred = out["logits"], out["length"] + + + # compute the unmasking loss + unmask_loss = unmask_weight[masked_indices] * F.cross_entropy( + logits[masked_indices], x1_remained[masked_indices], reduction="none" + ) + unmask_loss = unmask_loss.sum() / (batch_size * normalize_constant) + + # compute the length loss + insertion_loss = insert_weight[len_loss_indices] * jump_kernel_elbo( + gap_counts[len_loss_indices], scalar_pred[len_loss_indices]) + insertion_loss = insertion_loss.sum() / (batch_size * normalize_constant) + + scale = 1 / self.args.gradient_accumulation_steps + loss = (unmask_loss + insertion_loss) * scale + + # log each loss at the end of the gradient accumulation step + log_timing = ( + self.state.global_step % self.args.gradient_accumulation_steps == 0 + ) + unmask_mean = self.accelerator.gather(unmask_loss).mean() + insertion_mean = self.accelerator.gather(insertion_loss).mean() + + if log_timing and self.accelerator.is_main_process: + self.log( + { + "unmask_loss": (unmask_mean).item(), + "insertion_loss": (insertion_mean).item() + } + ) + + # for the evaluation loop, return_ouputs = True + return loss if not return_outputs else (loss, logits) + + +class dLLMVariableDataCollator(DefaultDataCollator): + def __init__(self, *args, **kwargs): + super().__init__() + self.max_length = kwargs["max_length"] + self.mask_token_id = kwargs["tokenizer"].mask_token_id + self.tokenizer = kwargs["tokenizer"] + self.low_discrepancy = kwargs["low_discrepancy"] + if kwargs["tokenizer"].mask_token_id is None: + assert ( + "mask_token_id" in kwargs + ), "For dLLM models, pass a mask_token_id or set it equal to tokenizer.mask_token_id" + self.mask_token_id = kwargs["mask_token_id"] + + self.is_infill_task = kwargs.get("is_infill_task", False) + self.infill_tokens = kwargs.get("infill_tokens", None) + + # Convert infill_token to list if it's not already + if self.infill_tokens is not None and not isinstance(self.infill_tokens, list): + self.infill_tokens = [self.infill_tokens] + + self.insertion_schedule = GeometricSchedule(min_val=10.0, max_val=0.01) + self.unmasking_schedule = LinearSchedule() + + self.interpolant = AnyOrderMaskInsertionInterpolant( + insertion_schedule=self.insertion_schedule, + unmask_schedule=self.unmasking_schedule, + vocab_size=kwargs["tokenizer"].vocab_size, + mask_token = self.mask_token_id, + pad_token = kwargs["tokenizer"].pad_token_id, + max_length = self.max_length + ) + + def forward_process(self, batch, prompt_indices, eps=1e-3): + input_ids = batch["input_ids"] + B, _ = input_ids.shape + if "t" not in batch: + if self.low_discrepancy: + if dist.is_initialized() and dist.is_available(): + rank = dist.get_rank() + world_size = dist.get_world_size() + global_batch_size = B * world_size + else: + rank = 0 + global_batch_size = B + + intervals = torch.arange(B, device=input_ids.device, dtype=torch.float32) + rank * B + offset = torch.rand(B, device=input_ids.device) + t = (intervals + offset) / global_batch_size + else: + t = torch.rand((B,), device=input_ids.device) + else: + t = batch["t"] + + # sample the time step t: preventing blow-up + t = (1 - eps) * t + eps # (B,) + + # sample from the interpolant + interpolant_result = self.interpolant.sample_interpolant(t, input_ids, prompt_indices) + + + # compute the ELBO weights + elbo_weights = self.interpolant.elbo_weight(t, input_ids) + + return interpolant_result, elbo_weights, t + + def __call__(self, examples): + # pad the examples to the max length + for ex in examples: + for key in ("input_ids", "attention_mask", "labels"): + if key in ex and len(ex[key]) > self.max_length: + ex[key] = ex[key][: self.max_length] + + batch = self.tokenizer.pad(examples, + padding = "max_length", + max_length = self.max_length, + return_tensors = "pt" + ) + + if self.is_infill_task: + input_ids, prompt_indices = vectorized_infill_process( + batch["input_ids"], + self.tokenizer.pad_token_id, + batch["prefix_cutoff"], + self.infill_tokens + ) + batch["input_ids"] = input_ids + batch["prompt_indices"] = prompt_indices + + # extract the prompt tokens + elif "prompt_lengths" not in batch: + # The case when there's no prompt + prompt_indices = torch.zeros(batch["input_ids"].shape[0], self.max_length, dtype=torch.bool) + else: + prompt_lengths = batch.pop("prompt_lengths") + suffix_lengths = batch.pop("suffix_length", None) + prompt_lengths = prompt_lengths.unsqueeze(1) # (B, 1) + positions = torch.arange(self.max_length) # (1, L) + prompt_indices = (positions < prompt_lengths).bool() # (B, L) + + interpolant_result, elbo_weights, t = self.forward_process(batch, prompt_indices) + batch["interpolant_result"] = interpolant_result + batch["elbo_weights"] = elbo_weights + batch["t"] = t + batch["input_ids"] = interpolant_result.xt + + return batch + diff --git a/FlexMDM/scaling_flexmdm/flexmdm_transfer_openwebtext.py b/FlexMDM/scaling_flexmdm/flexmdm_transfer_openwebtext.py new file mode 100644 index 0000000000000000000000000000000000000000..dfc6d517a88710c62557001f05e4040061ec7f25 --- /dev/null +++ b/FlexMDM/scaling_flexmdm/flexmdm_transfer_openwebtext.py @@ -0,0 +1,251 @@ +import torch +import torch.nn as nn +import argparse +from transformers import AutoTokenizer, AutoModel, TrainingArguments +from transformers.trainer_callback import TrainerCallback +from transformers.trainer_utils import is_main_process +from datasets import load_dataset, load_from_disk, Features, Sequence, Value, concatenate_datasets +from datasets.distributed import split_dataset_by_node +import os, multiprocessing, random, pathlib +from torch.utils.data import DataLoader +from peft import LoraConfig, get_peft_model, TaskType +from flexmdm_trainer import * +from collections import Counter +from llada_dit import LLaDA_DIT +from pathlib import Path +import torch.distributed as dist +import random +import tqdm +import numpy as np +import wandb +import glob + + + +def init_seed(seed): + random.seed(seed) + os.environ["PYTHONHASHSEED"] = str(seed) + np.random.seed(seed) + torch.manual_seed(seed) + torch.cuda.manual_seed(seed) + # torch.backends.cudnn.deterministic = True # for the training speed, we comment this out + + +# ------------------------------------------------------------ +# Util function for logging +# ------------------------------------------------------------ +def count_parameters(named_params, key: str | None = None): + return sum(p.numel() + for n, p in named_params + if p.requires_grad and (key is None or key in n) + ) + +class LogLrCallback(TrainerCallback): + def on_step_end(self, args, state, control, **kwargs): + if not is_main_process(args): + return + opt = kwargs["optimizer"] + wandb.log( + { + "lr/lora": opt.param_groups[0]["lr"], + "lr/token_head": opt.param_groups[1]["lr"], + "lr/from_scratch": opt.param_groups[2]["lr"], + "step": state.global_step, + } + ) + + +# Initialize argument parser +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument( + "--model_name", type=str, default="GSAI-ML/LLaDA-8B-Base", help="Name of the pretrained model" + ) + + # Training hyperparameters + parser.add_argument("--batch_size", type=int, default=4, help="batch size per device") + parser.add_argument("--lora_lr", type=float, default=1e-4, help="Learning rate for the LoRA") + parser.add_argument("--lr", type=float, default=1e-4, help="Learning rate for other parameters") + parser.add_argument("--grad_accum_steps", type=int, default=2, help="Gradient accumulation steps") + parser.add_argument("--max_steps", type=int, default=500000, help="Maximum number of training steps") + parser.add_argument("--resume_from_checkpoint", type=str, default=None, help="Path to the checkpoint to resume from") + parser.add_argument("--low_discrepancy", type=bool, default=False, help="whether to use low discrepancy sampling") + + # Output directory and job name + parser.add_argument( + "--output_dir", + type=str, + default="/n/netscratch/albergo_lab/Lab/transdim-flow/sft-datamix-checkpoints", + help="Directory to save model checkpoints and logs", + ) + parser.add_argument("--job_name", type=str, default="llada-sft-openwebtext", help="Job Name") + parser.add_argument("--train_data", type=str, default="openwebtext", help="Path to training data") + parser.add_argument("--wandb", action="store_true", help="whether to use wandb") + parser.add_argument("--variable_length", action="store_true", help="whether to use variable length training") + parser.add_argument("--sanity_run", action="store_true", help="whether to run the sanity run (overfitting the model)") + + # CLI flags for openwebtext dataset preprocessing + parser.add_argument("--sft_max_length", type=int, default=1024, help="Maximum sequence length for tokenization") + parser.add_argument("--cache_path", type=str, default="/n/netscratch/albergo_lab/Everyone/jay_brian/datamix", help="Path of the tokenized openwebtext dataset") + + return parser.parse_args() + + + +# Model loading with LoRA integration +def load_model_and_tokenizer(args): + # Load the backbone LLaDA model + backbone = AutoModel.from_pretrained( + args.model_name, + trust_remote_code=True, + torch_dtype=torch.bfloat16, + return_dict=True, + ) + + # Load tokenizer + tokenizer = AutoTokenizer.from_pretrained(args.model_name, padding_side="right", trust_remote_code=True, use_fast=True) + + print("Tokenizer and backbone loaded!") + + backbone.config.output_hidden_states = True + backbone.config.return_dict = True + + # lora adapter for the backbone LLaDA + lora_config = LoraConfig( + r=128, + lora_alpha=128, + target_modules=["q_proj", "k_proj", "v_proj", "transformer.ff_out"], + lora_dropout=0.1, + bias="none", + task_type=TaskType.CAUSAL_LM, + ) + backbone = get_peft_model(backbone, lora_config) + backbone = backbone.to(torch.bfloat16) + + if args.variable_length: + model = LLaDA_DIT(backbone, pad_token_id = tokenizer.pad_token_id, d_model = 4096) + else: + model = backbone + + if args.resume_from_checkpoint: + ckpt_dir = Path(args.resume_from_checkpoint) + state = torch.load(ckpt_dir/ "pytorch_model.bin", map_location="cpu") + model.load_state_dict(state, strict=False) + print(f"Resumed from checkpoint {args.resume_from_checkpoint}") + + print("Final trainer model loaded!") + + return tokenizer, model + + +# Dataset loading +def load_data(args, tokenizer): + # load the pre-processed tokenzied dataset (already int64) + cache_dir = pathlib.Path(args.cache_path) + if not cache_dir.exists(): + raise FileNotFoundError(f"Cache directory {cache_dir} does not exist") + ds = load_from_disk(cache_dir) + ds = ds.shuffle(seed=42) + data = ds.train_test_split(test_size=0.001, seed=42) + print("Training and evaluation datasets successfully loaded!") + + if args.sanity_run: + data = data["train"].select(range(128)) + print("Sanity run dataset loaded!") + data.save_to_disk("sanity_run_dataset") + return data, data + + return data["train"], data["test"] + + +# Training setup +def train_model(args, tokenizer, model): + # Load dataset + train_dataset, eval_dataset = load_data(args, tokenizer) + + # Training arguments setup + training_args = TrainingArguments( + output_dir=os.path.join(args.output_dir, args.job_name), + max_steps = args.max_steps, + per_device_train_batch_size=args.batch_size, + gradient_accumulation_steps=args.grad_accum_steps, + eval_strategy= 'steps', + eval_steps = 1000, + prediction_loss_only = True, + logging_steps = 10, + save_steps = 10000, + save_total_limit=20, + save_safetensors=False, + max_grad_norm=1.0, + bf16=True, + lr_scheduler_type="cosine", + lr_scheduler_kwargs={"num_cycles": 5}, + warmup_ratio=0.05, + remove_unused_columns=False, + report_to="wandb" if args.wandb else None, + ) + + # setup the trainable parameters + lora_params = [p for n, p in model.named_parameters() if "lora" in n and p.requires_grad] + head_params = [p for n, p in model.named_parameters() if "lora" not in n and "ff_out" in n and p.requires_grad] + from_scratch_params = [p for n, p in model.named_parameters() if "lora" not in n and "ff_out" not in n and p.requires_grad] + + trainable = [p for _, p in model.named_parameters() if p.requires_grad] + assert set(trainable) == set(lora_params) | set(head_params) | set(from_scratch_params), "Trainable parameters are not correctly set" + + # parameter count check + print(f"Total trainable parameters: {count_parameters(model.named_parameters(), key = None)}") + print(f" └─ LoRA adapter params : {count_parameters(model.named_parameters() , key = 'lora')}") + print(f" └─ Token Head params : {count_parameters(model.named_parameters(), key = 'ff_out')}") + print(f" └─ Scalar Length Head params : {count_parameters(model.named_parameters(), key = 'scalar_length_head')}") + print(f" └─ Time embedding params : {count_parameters(model.named_parameters(), key = '.temb_mod')}") + + + # Initialize Trainer with custom dLLMTrainer + if args.variable_length: + optimizer = torch.optim.AdamW( + [ + {"params": lora_params, "lr": args.lora_lr, "weight_decay": 0.0}, + {"params": head_params, "lr": args.lora_lr / 4, "weight_decay": 0.01}, + {"params": from_scratch_params, "lr": args.lr, "weight_decay": 0.01} + ], + ) + trainer = dLLMVariableLengthTrainer( + model=model, + args=training_args, + data_collator=dLLMVariableDataCollator(tokenizer=tokenizer, mask_token_id=126336, + max_length=args.sft_max_length, compute_metrics = None, + low_discrepancy = args.low_discrepancy), + train_dataset=train_dataset, + eval_dataset=eval_dataset, + optimizers=(optimizer, None), + ) + else: + raise NotImplementedError("Currently we don't support fixed length training") + + local_rank = int(os.environ.get("LOCAL_RANK", 0)) + + if args.wandb and local_rank == 0: + wandb.init(project="SFT-llada", name=args.job_name, entity="jaeyeon_kim-harvard-university") + + # double-check the optimizer + for i, g in enumerate(trainer.optimizer.param_groups): + print(f"group {i} init-lr={g['lr']} wd={g['weight_decay']}") + + # add the callback + trainer.add_callback(LogLrCallback()) + + # Start training + trainer.train() + + +if __name__ == "__main__": + init_seed(42) + # Parse command-line arguments + args = parse_args() + + # Load model and tokenizer + tokenizer, model = load_model_and_tokenizer(args) + + # Train the model + train_model(args, tokenizer, model) \ No newline at end of file diff --git a/FlexMDM/scaling_flexmdm/flexmdm_transfer_preprocess.py b/FlexMDM/scaling_flexmdm/flexmdm_transfer_preprocess.py new file mode 100644 index 0000000000000000000000000000000000000000..1970705bf78c93479f3be5451e86571e6e536a18 --- /dev/null +++ b/FlexMDM/scaling_flexmdm/flexmdm_transfer_preprocess.py @@ -0,0 +1,253 @@ +import re +from transformers import GPT2TokenizerFast, GPTNeoXTokenizerFast +from datasets import Dataset, load_dataset +from typing import Literal, List +from transformers import AutoTokenizer + +TEXT_DATASETS = ["wikitext2", "openwebtext"] +MIN_LEN = 50 + + +def wt_detokeniser(string): + # contractions + string = string.replace("s '", "s'") + string = re.sub(r"/' [0-9]/", r"/'[0-9]/", string) + # number separators + string = string.replace(" @-@ ", "-") + string = string.replace(" @,@ ", ",") + string = string.replace(" @.@ ", ".") + # punctuation + string = string.replace(" : ", ": ") + string = string.replace(" ; ", "; ") + string = string.replace(" . ", ". ") + string = string.replace(" ! ", "! ") + string = string.replace(" ? ", "? ") + string = string.replace(" , ", ", ") + # double brackets + string = re.sub(r"\(\s*([^\)]*?)\s*\)", r"(\1)", string) + string = re.sub(r"\[\s*([^\]]*?)\s*\]", r"[\1]", string) + string = re.sub(r"{\s*([^}]*?)\s*}", r"{\1}", string) + string = re.sub(r"\"\s*([^\"]*?)\s*\"", r'"\1"', string) + string = re.sub(r"'\s*([^']*?)\s*'", r"'\1'", string) + # miscellaneous + string = string.replace("= = = =", "====") + string = string.replace("= = =", "===") + string = string.replace("= =", "==") + string = string.replace(" " + chr(176) + " ", chr(176)) + string = string.replace(" \n", "\n") + string = string.replace("\n ", "\n") + string = string.replace(" N ", " 1 ") + string = string.replace(" 's", "'s") + return string + + +def setup_tokeniser(tokeniser_name: str) -> GPT2TokenizerFast: + match tokeniser_name: + case "gpt2": + tokeniser = GPT2TokenizerFast.from_pretrained("gpt2") + case "gpt-neo": + tokeniser = GPTNeoXTokenizerFast.from_pretrained("EleutherAI/gpt-neox-20b") + case _: + raise ValueError(f"Tokeniser {tokeniser_name} not supported") + tokeniser.add_special_tokens( + { + "pad_token": "[PAD]", + "mask_token": "[MASK]", + } + ) + return tokeniser + +from transformers import GPT2TokenizerFast + +tokeniser = GPT2TokenizerFast.from_pretrained("gpt2") +detokeniser = None + +def find_delimiter_positions(tokens, delimiter_tokens): + """Return the start indices where the delimiter occurs in the token sequence.""" + positions = [] + n = len(delimiter_tokens) + for i in range(len(tokens) - n + 1): + if tokens[i:i+n] == delimiter_tokens: + positions.append(i) + return positions + +def recursive_split(tokens, max_length, delimiter_tokens): + if len(tokens) <= max_length: + return [tokens] + + # Find all positions where the delimiter sequence occurs + split_candidates = find_delimiter_positions(tokens, delimiter_tokens) + if not split_candidates: + # Safe fallback: naive split + return [tokens[i:min(i + max_length, len(tokens))] for i in range(0, len(tokens), max_length)] + + # Find delimiter closest to the midpoint + midpoint = len(tokens) // 2 + split_point = min(split_candidates, key=lambda x: abs(x - midpoint)) + + # Recurse on both sides, skipping the delimiter + dlen = len(delimiter_tokens) + left = recursive_split(tokens[:split_point], max_length, delimiter_tokens) + right = recursive_split(tokens[split_point + dlen:], max_length, delimiter_tokens) + + return left + right + +def preprocess_batch(batch, pad_token, max_length, delimiter, detokeniser, tokeniser): + all_input_ids = [] + all_lengths = [] + + for text in batch["text"]: + if detokeniser is not None: + text = detokeniser(text) + + tokens = tokeniser.encode(text, add_special_tokens=False) + chunks = recursive_split(tokens, max_length, delimiter) + + all_input_ids.extend([c + [pad_token] * (max_length - len(c)) if len(c) < max_length else c for c in chunks]) + all_lengths.extend([len(chunk) for chunk in chunks]) + + print(len(all_input_ids), len(all_lengths)) + return { + "input_ids": all_input_ids, + "length": all_lengths, + } + + +def setup_tokeniser_from_dataset(dataset_name: str): + tokeniser = None + match dataset_name: + case "wikitext2" | "openwebtext" | "EleutherAI/proof-pile-2": + # tokeniser = setup_tokeniser("gpt2") + tokeniser = AutoTokenizer.from_pretrained("GSAI-ML/LLaDA-8B-Base", use_fast=True) + case "dclm": + tokeniser = setup_tokeniser("gpt-neo") + case _: + raise ValueError(f"Tokeniser for dataset {dataset_name} not supported") + + return tokeniser + + +def decode_sequence_with_mask( + seqs: List[List[int]], tokeniser: GPT2TokenizerFast, pad_token: int, mask_token: int +) -> List[str]: + """ + Decode a sequence with visible mask tokens. + """ + decoded = [] + for seq in seqs: + tokens = tokeniser.convert_ids_to_tokens(seq) + filtered = [] + for tok, tok_id in zip(tokens, seq): + if tok_id == pad_token: + continue + if tok_id == mask_token: + filtered.append("[MASK]") + else: + filtered.append(tok) + text = tokeniser.convert_tokens_to_string(filtered) + decoded.append(text) + return decoded + + +def get_text_dataset( + name: str, + split: Literal["train", "validation", "test"], + cache_dir=None, + max_length=1024, + num_proc=64, + filter_max_length=False, + drop_columns=[] +) -> Dataset: + match name: + case "EleutherAI/proof-pile-2": + ds_all = load_dataset(name, "default", cache_dir=cache_dir, trust_remote_code=True) + train_ds = ds_all["train"] + if split in ["train", "validation"]: + split_data = train_ds.train_test_split(test_size=0.02, seed=42) + dataset = split_data["train"] if split == "train" else split_data["test"] + else: + raise ValueError(f"Dataset {name} does not support split {split}") + case "wikitext2": + dataset = load_dataset( + "wikitext", "wikitext-2-raw-v1", cache_dir=cache_dir, split=split + ) + case "openwebtext": + ds_all = load_dataset(name, cache_dir=cache_dir, trust_remote_code=True) + train_ds = ds_all["train"] + if split in ["train", "validation"]: + split_data = train_ds.train_test_split(test_size=0.02, seed=42) + dataset = split_data["train"] if split == "train" else split_data["test"] + else: + raise ValueError(f"Dataset {name} does not support split {split}") + case _: + raise ValueError(f"Dataset {name} not supported") + + match name: + case "wikitext2": + detokeniser = wt_detokeniser + case "openwebtext": + detokeniser = None + case "dclm": + detokeniser = None + case "EleutherAI/proof-pile-2": + detokeniser = None + case _: + raise ValueError(f"Dataset {name} not supported") + + tokeniser = setup_tokeniser_from_dataset(name) + pad_token = tokeniser.pad_token_id + + if filter_max_length: + def preprocess(sample): + text = sample["text"] + if detokeniser is not None: + text = detokeniser(text) + text = tokeniser(text, return_attention_mask=False) + if len(text["input_ids"]) < MIN_LEN: + return {"input_ids": []} + text["input_ids"] += max(0, max_length - len(text["input_ids"])) * [pad_token] + return text + + tokenised_dataset = dataset.map( + preprocess, + num_proc=num_proc, + load_from_cache_file=True, + remove_columns=["text"], + ) + tokenised_dataset = tokenised_dataset.filter( + lambda x: 0 < len(x["input_ids"]) <= max_length, + num_proc=num_proc, + load_from_cache_file=True, + ) + tokenised_dataset = tokenised_dataset.with_format("torch") + + return tokenised_dataset + else: + tokenised_dataset = dataset.map( + lambda batch: preprocess_batch( + batch, + pad_token=pad_token, + max_length=max_length, + detokeniser=detokeniser, + tokeniser=tokeniser, + delimiter=[198, 198] + ), + batched=True, + num_proc=num_proc, + remove_columns=["text", *drop_columns], + ) + tokenised_dataset = tokenised_dataset.with_format("torch") + + return tokenised_dataset + + + +if __name__ == "__main__": + import datasets + + proof_ds = get_text_dataset("EleutherAI/proof-pile-2", "train", drop_columns=["meta"]) + owt_ds = get_text_dataset("openwebtext", "train") + + ds = datasets.concatenate_datasets([owt_ds, proof_ds]) + ds.save_to_disk(f"/n/netscratch/albergo_lab/Everyone/jay_brian/datamix") + print("✔ saved tokenised dataset to disk") diff --git a/FlexMDM/scaling_flexmdm/instruction_finetuning.py b/FlexMDM/scaling_flexmdm/instruction_finetuning.py new file mode 100644 index 0000000000000000000000000000000000000000..7ca68e72ebb6663d8885ad7f86038a988bf5bd30 --- /dev/null +++ b/FlexMDM/scaling_flexmdm/instruction_finetuning.py @@ -0,0 +1,298 @@ +import torch +import argparse +from transformers import AutoTokenizer, AutoModel, TrainingArguments +from datasets import load_dataset +from peft import LoraConfig, get_peft_model, TaskType +import os +from llada_trainer import * +from flexmdm_trainer import * +import random +import numpy as np +import wandb +from preprocess_math import preprocess_gsm8k +from llada_dit import LLaDA_DIT +import preprocess_code_infilling +from pathlib import Path + + +INFILL_DATASETS = ["code-infill"] + +def init_seed(seed): + random.seed(seed) + os.environ["PYTHONHASHSEED"] = str(seed) + np.random.seed(seed) + torch.manual_seed(seed) + torch.cuda.manual_seed(seed) + torch.backends.cudnn.deterministic = True + + +# ------------------------------------------------------------ +# Util function for counting the number of parameters +# ------------------------------------------------------------ +def count_parameters(named_params, key: str | None = None): + return sum(p.numel() + for n, p in named_params + if p.requires_grad and (key is None or key in n) + ) + + +# ------------------------------------------------------------ +# Util function for adding special tokens to the tokenizer +# ------------------------------------------------------------ +SPECIAL_TOKENS = ["", "", "", ""] + +def get_reasoning_tokenizer(model_name: str, model: AutoModel | None = None, add_to_model: bool = True): + tokenizer = AutoTokenizer.from_pretrained(model_name, padding_side="right", trust_remote_code=True, use_fast=True) + device = model.device + + missing = [t for t in SPECIAL_TOKENS if t not in tokenizer.get_vocab()] + if missing: + tokenizer.add_special_tokens({"additional_special_tokens": missing}) + + if add_to_model: + model.resize_token_embeddings(len(tokenizer)).to(device) + + return tokenizer, model + + +# Initialize argument parser +def parse_args(): + parser = argparse.ArgumentParser() + + # Hyperparameters + parser.add_argument( + "--model_name", type=str, default="GSAI-ML/LLaDA-8B-Base", help="Name of the pretrained model" + ) + parser.add_argument("--batch_size", type=int, default=4, help="Batch size for training") + parser.add_argument( + "--max_length", type=int, default=1024, help="Maximum sequence length for tokenization" + ) + parser.add_argument("--num_epochs", type=int, default=100, help="Number of training epochs") + parser.add_argument("--lora_lr", type=float, default=1e-4, help="Learning rate for the LoRA") + parser.add_argument("--lr", type=float, default=1e-4, help="Learning rate for other parameters") + parser.add_argument("--grad_accum_steps", type=int, default=2, help="Gradient accumulation steps") + parser.add_argument( + "--output_dir", + type=str, + default="/n/netscratch/albergo_lab/Lab/sft-datamix-gsm8k-checkpoints", + help="Directory to save model checkpoints and logs", + ) + parser.add_argument("--job_name", type=str, default="llada-sft-gsm8k-test-run", help="Job Name") + parser.add_argument("--train_data", type=str, default="gsm8k", help="Path to training data") + parser.add_argument("--wandb", action="store_true", help="whether to use wandb") + parser.add_argument("--variable_length", action="store_true", help="whether to use variable length training") + parser.add_argument("--resume_from_checkpoint", type=str, default=None, help="Path to the checkpoint to resume from") + + return parser.parse_args() + + +# Model loading with LoRA integration +def load_model_and_tokenizer(args): + # Load the backbone LLaDA model + backbone = AutoModel.from_pretrained( + args.model_name, + trust_remote_code=True, + torch_dtype=torch.bfloat16, + return_dict=True, + ) + print("Backbone LLaDA loaded!") + + backbone.config.output_hidden_states = True + backbone.config.return_dict = True + + # Add LoRA to backbone first (like eval.py) + lora_config = LoraConfig( + r=128, + lora_alpha=128, + target_modules=["q_proj", "k_proj", "v_proj", "transformer.ff_out"], + lora_dropout=0.1, + bias="none", + task_type=TaskType.CAUSAL_LM, + ) + backbone = get_peft_model(backbone, lora_config) + + # Load tokenizer and add reasoning tokens if needed for GSM8K + tokenizer = AutoTokenizer.from_pretrained(args.model_name, padding_side="right", trust_remote_code=True, use_fast=True) + + print("Tokenizer loaded!") + + if args.variable_length: + model = LLaDA_DIT(backbone, pad_token_id=tokenizer.pad_token_id, d_model=4096) + else: + model = backbone + + # Load checkpoint if provided (following eval.py pattern) + if args.resume_from_checkpoint: + ckpt_dir = Path(args.resume_from_checkpoint) + checkpoint_path = ckpt_dir / "pytorch_model.bin" + + if checkpoint_path.exists(): + state = torch.load(checkpoint_path, map_location="cpu") + missing, unexpected = model.load_state_dict(state, strict=False) + print(f"Missing keys: {missing}") + print(f"Unexpected keys: {unexpected}") + print(f"Resumed from checkpoint {args.resume_from_checkpoint}") + else: + print(f"Checkpoint file not found at {checkpoint_path}") + + model = model.to(torch.bfloat16) + print("Final trainer model loaded!") + + return tokenizer, model + + +# Dataset loading +def load_data(args, tokenizer): + if args.train_data == "gsm8k": + train_data = preprocess_gsm8k( + split="train", tokenizer=tokenizer, max_length=args.max_length + ) + eval_data = preprocess_gsm8k( + split="test", tokenizer=tokenizer, max_length=args.max_length + ) + elif args.train_data == "code-infill": + train_data = code.preprocess_opc_coder(tokenizer, args.max_length) + eval_data = code.preprocess_human_eval(tokenizer, args.max_length) + else: + data = load_dataset(args.train_data, split="train") + train_data, eval_data = preprocess_dataset(data, tokenizer, args.max_length) + + print("Train data length: ", len(train_data)) + print("Eval data length: ", len(eval_data)) + train_dataset = dLLMSFTDataset(train_data, tokenizer, args.max_length) + eval_dataset = dLLMSFTDataset(eval_data, tokenizer, args.max_length, eval=True) + return train_dataset, eval_dataset + + +# Training setup +def train_model(args, tokenizer, model): + # Load dataset + train_dataset, eval_dataset = load_data(args, tokenizer) + + # Training arguments setup + training_args = TrainingArguments( + output_dir=os.path.join(args.output_dir, args.job_name), + num_train_epochs=args.num_epochs, + per_device_train_batch_size=args.batch_size, + gradient_accumulation_steps=args.grad_accum_steps, + eval_strategy="steps", + eval_steps = 100, + logging_steps = 10, + save_steps = 100, + save_safetensors=False, + load_best_model_at_end=False, + max_grad_norm=1.0, + bf16=True, + lr_scheduler_type="cosine", + lr_scheduler_kwargs={"num_cycles": 5}, + warmup_ratio=0.05, + remove_unused_columns=False, + report_to="wandb" if args.wandb else None, + ) + + # Create optimizer and scheduler + num_train_steps = int( + len(train_dataset) + * args.num_epochs + / (args.batch_size * args.grad_accum_steps * torch.cuda.device_count()) + ) + + # setup the trainable parameters + lora_params = [p for n, p in model.named_parameters() if "lora" in n and p.requires_grad] + head_params = [p for n, p in model.named_parameters() if "lora" not in n and p.requires_grad] + + # parameter count check + num_lora_params = count_parameters(model.named_parameters(), key = 'lora') + whole_trainable_params = count_parameters(model.named_parameters()) + print(f"Total trainable parameters: {whole_trainable_params:,}") + print(f" └─ LoRA adapter params : {num_lora_params:,}") + print(f" └─ From-scratch params : {whole_trainable_params - num_lora_params:,}") + + # Initialize Trainer with custom dLLMTrainer + if args.variable_length: + optimizer = torch.optim.AdamW( + [ + {"params": lora_params, "lr": args.lora_lr, "weight_decay": 0.1}, + {"params": head_params, "lr": args.lr, "weight_decay": 0.1} + ], + ) + + if args.train_data in INFILL_DATASETS: + infill_tokens = tokenizer.encode("") + print("infill tokens: ", infill_tokens) + trainer = dLLMVariableLengthTrainer( + model=model, + args=training_args, + data_collator=dLLMVariableDataCollator( + tokenizer=tokenizer, + mask_token_id=126336, + max_length=args.max_length + 2 * len(infill_tokens), + low_discrepancy=True, + is_infill_task=True, + infill_tokens=infill_tokens + ), + train_dataset=train_dataset, + eval_dataset=eval_dataset, + optimizers=(optimizer, None), + ) + else: + trainer = dLLMVariableLengthTrainer( + model=model, + args=training_args, + data_collator=dLLMVariableDataCollator(tokenizer=tokenizer, mask_token_id=126336, max_length=args.max_length, low_discrepancy=True), + train_dataset=train_dataset, + eval_dataset=eval_dataset, + optimizers=(optimizer, None), + ) + else: + if args.train_data in INFILL_DATASETS: + instruction = tokenizer.encode("You are instructed to perform an infill task. Please fill in the missing parts of the code. The prefix and suffix are provided and delimited by and tokens.") + prefix_delimiters = [tokenizer.encode(""), tokenizer.encode("")] + suffix_delimiters = [tokenizer.encode(""), tokenizer.encode("")] + total_length = args.max_length + len(prefix_delimiters[0]) + len(prefix_delimiters[1]) + len(suffix_delimiters[0]) + len(suffix_delimiters[1]) + len(instruction) + trainer = dLLMTrainer( + model=model, + args=training_args, + data_collator=dLLMDataCollator( + tokenizer=tokenizer, + mask_token_id=126336, + instruction_tokens=instruction, + prefix_delimiters=prefix_delimiters, + suffix_delimiters=suffix_delimiters, + max_length=total_length, + is_infill_task=True, + ), + train_dataset=train_dataset, + eval_dataset=eval_dataset, + ) + else: + trainer = dLLMTrainer( + model=model, + args=training_args, + data_collator=dLLMDataCollator(tokenizer=tokenizer, mask_token_id=126336, max_length=args.max_length), + train_dataset=train_dataset, + eval_dataset=eval_dataset, + optimizers=(optimizer, None), + ) + + optimizer = torch.optim.AdamW(lora_params, lr = args.lora_lr, weight_decay = 0.1) + + local_rank = int(os.environ.get("LOCAL_RANK", 0)) + + if args.wandb and local_rank == 0: + wandb.init(project="SFT-llada", name=args.job_name, entity="jaeyeon_kim-harvard-university") + + # Start training + trainer.train() + + +if __name__ == "__main__": + init_seed(42) + # Parse command-line arguments + args = parse_args() + + # Load model and tokenizer + tokenizer, model = load_model_and_tokenizer(args) + + # Train the model + train_model(args, tokenizer, model) diff --git a/FlexMDM/scaling_flexmdm/llada_dit.py b/FlexMDM/scaling_flexmdm/llada_dit.py new file mode 100644 index 0000000000000000000000000000000000000000..8ffad57a11eceb3b8160eb296c2bf204a08f114e --- /dev/null +++ b/FlexMDM/scaling_flexmdm/llada_dit.py @@ -0,0 +1,114 @@ +import math +import torch +import torch.nn as nn +import torch.nn.functional as F +from llada_utils import load_llada_modules +(LLaDASequentialBlock, ModelConfig, ActivationType, LayerNormType) = load_llada_modules() + +# ------------------------------------------------------------ +# Additional scalar length head +# ------------------------------------------------------------ +class ScalarLengthHead(nn.Module): + def __init__(self, hidden_size: int): + super().__init__() + self.norm = nn.LayerNorm(hidden_size) + self.proj1 = nn.Linear(hidden_size, hidden_size) + self.act = nn.GELU() + self.proj2 = nn.Linear(hidden_size, 1) + self.softplus = nn.Softplus() + + def forward(self, x: torch.Tensor) -> torch.Tensor: + h = self.norm(x) + h = self.act(self.proj1(h)) + s = self.proj2(h) + out = self.softplus(s).squeeze(-1) + return out * 1024 + + +class LLaDA_DIT(nn.Module): + def __init__(self, backbone: nn.Module, pad_token_id: int, d_model: int): + super().__init__() + + # define the architecture configuations + self.backbone = backbone + self.d_model = d_model + self.pad_token_id = pad_token_id + self._temb = None + self.scalar_length_head = ScalarLengthHead(d_model) + + # define the time embedding + self.time_mlp = nn.Sequential( + nn.Linear(self.d_model, self.d_model * 4), + nn.SiLU(), + nn.Linear(self.d_model * 4, self.d_model), + ) + + # extract the core transformer blocks (backbone model is PEFT model) + core = self.backbone.base_model.model.model.transformer + + # define the hook: change LN to AdaLN + # ---- adding AdaLN for each block is costly, so we use a grouping AdaLN ---- + group_size = 8 + group_mod = None + for i, block in enumerate(core.blocks): + if i % group_size == 0: + group_mod = nn.Linear(self.d_model, 2 * self.d_model) + nn.init.zeros_(group_mod.weight) ; nn.init.zeros_(group_mod.bias) + + block.add_module("temb_mod", group_mod) + + for ln in (block.attn_norm, block.ff_norm): + ln.register_forward_hook(self.make_hook(group_mod)) + + def timestep_embedding(self, t, dim, max_period=10000): + half = dim // 2 + freqs = torch.exp( + -math.log(max_period) + * torch.arange(start=0, end=half, dtype=torch.float32) + / half + ).to(device=t.device) + args = t[:, None].float() * freqs[None] + embedding = torch.cat([torch.cos(args), torch.sin(args)], dim=-1) + if dim % 2: + embedding = torch.cat( + [embedding, torch.zeros_like(embedding[:, :1])], dim=-1 + ) + return embedding + + + def make_hook(self, mod): + # called before each LN forward + def hook(module, _inp, output): + if self._temb is None: + return output + scale, shift = mod(self._temb).chunk(2, dim=-1) + x = (1 + scale[:, None, :]) * output + shift[:, None, :] + return x + return hook + + def forward(self, input_ids: torch.LongTensor, attention_mask: torch.Tensor | None = None, timesteps: torch.LongTensor = None, **backbone_kwargs): + assert timesteps is not None, "timesteps must be provided" + + self._temb = self.time_mlp( + self.timestep_embedding(timesteps, self.d_model) + ) + input_ids = torch.cat( + [input_ids, self.pad_token_id * torch.ones((input_ids.shape[0], 1), device = input_ids.device, dtype = torch.int64)] + , dim = -1 + ) + out = self.backbone(input_ids = input_ids, attention_mask = attention_mask, output_hidden_states = True, return_dict = True, **backbone_kwargs) + # get the unmasking prediction + logits = out.logits[:, :-1, :] + + # get the length prediction + hidden_v = out.hidden_states[-1] # extract the last hidden state + length = self.scalar_length_head(hidden_v) + + # reset the time embedding for every forward pass + self._temb = None + + return {"logits": logits, "length": length} + + @property + def device(self): + return self.backbone.device \ No newline at end of file diff --git a/FlexMDM/scaling_flexmdm/llada_inference.py b/FlexMDM/scaling_flexmdm/llada_inference.py new file mode 100644 index 0000000000000000000000000000000000000000..2cc8099e79e97f11af2d0cd5ad33f2ad44961f02 --- /dev/null +++ b/FlexMDM/scaling_flexmdm/llada_inference.py @@ -0,0 +1,133 @@ +import torch +import numpy as np +import torch.nn.functional as F +from tqdm import tqdm +import torch.distributed as dist + + +def add_gumbel_noise(logits, temperature): + """ + The Gumbel max is a method for sampling categorical distributions. + Using float16 for better performance while maintaining reasonable quality. + """ + if temperature == 0.0: + return logits # Skip noise when temperature is 0 + + # Use float32 instead of float64 for better performance + logits = logits.to(torch.float32) + noise = torch.rand_like(logits, dtype=torch.float32) + gumbel_noise = (-torch.log(noise)) ** temperature + return logits.exp() / gumbel_noise + + +def get_num_transfer_tokens(mask_index, steps): + """ + Precompute the number of tokens to transition at each step. + Optimized to be more efficient. + """ + mask_num = mask_index.sum(dim=1, keepdim=True) + base = mask_num // steps + remainder = mask_num % steps + + # Create tensor once and modify in-place + num_transfer_tokens = base.expand(-1, steps).clone() + + # Handle remainder more efficiently + if remainder.sum() > 0: + indices = torch.arange(steps, device=mask_index.device) + mask = indices.unsqueeze(0) < remainder + num_transfer_tokens[mask] += 1 + + return num_transfer_tokens.to(torch.int64) + + +@torch.no_grad() +def generate_mdm( + model, + prompt, + tokenizer, + steps, + gen_length, + block_length, + temperature, + cfg_scale, + remasking, + mask_id: int = 126336, +): + """ + Optimized version of the generate function. + """ + # Use mixed precision for faster computation + if prompt is not None: + B, prompt_len = prompt.shape[0], prompt.shape[1] + else: + B = 1 + prompt_len = 0 + with torch.autocast(device_type="cuda"): + x = torch.full( + (B, prompt_len + gen_length), mask_id, dtype=torch.long, device=model.device + ) + if prompt is not None: + x[:, : prompt.shape[1]] = prompt.clone() + + prompt_index = x != mask_id + + # get rank iff in the distributed setting + rank = dist.get_rank() if dist.is_initialized() else 0 + + assert gen_length % block_length == 0 + num_blocks = gen_length // block_length + steps_per_block = max(1, steps // num_blocks) + for num_block in tqdm(range(num_blocks), disable=(rank != 0)): + start_idx = prompt_len + num_block * block_length + end_idx = prompt_len + (num_block + 1) * block_length + + print(start_idx, end_idx) + + block_mask_index = x[:, start_idx:end_idx] == mask_id + num_transfer_tokens = get_num_transfer_tokens(block_mask_index, steps_per_block) + + for i in range(steps_per_block): + mask_index = x == mask_id + + # Handle classifier-free guidance more efficiently + if cfg_scale > 0.0: + un_x = x.clone() + un_x[prompt_index] = mask_id + x_ = torch.cat([x, un_x], dim=0) + + # Get logits in a single forward pass + logits = model(x_).logits + logits, un_logits = torch.chunk(logits, 2, dim=0) + logits = un_logits + (cfg_scale + 1) * (logits - un_logits) + else: + logits = model(x).logits + + # Apply Gumbel noise for sampling + logits_with_noise = add_gumbel_noise(logits, temperature) + x0 = torch.argmax(logits_with_noise, dim=-1) + + # Handle remasking strategy + if remasking == "low_confidence": + # Use float32 instead of float64 for better performance + p = F.softmax(logits, dim=-1) + x0_p = torch.gather(p, dim=-1, index=x0.unsqueeze(-1)).squeeze(-1) + elif remasking == "random": + x0_p = torch.rand(x0.shape, device=x0.device) + else: + raise NotImplementedError(remasking) + + # Ensure we don't process tokens beyond the current block + x0_p[:, end_idx:] = -np.inf + + # Update masked tokens + x0 = torch.where(mask_index, x0, x) + confidence = torch.where(mask_index, x0_p, torch.tensor(-np.inf, device=x0.device)) + + # Select tokens to transfer based on confidence + for j in range(confidence.shape[0]): + num_tokens = num_transfer_tokens[j, i].item() + if num_tokens > 0: + _, select_indices = torch.topk(confidence[j], k=num_tokens) + x[j, select_indices] = x0[j, select_indices] + return x diff --git a/FlexMDM/scaling_flexmdm/llada_trainer.py b/FlexMDM/scaling_flexmdm/llada_trainer.py new file mode 100644 index 0000000000000000000000000000000000000000..7d060bdd17a81a452664dcc9a1e32db276c5e2a6 --- /dev/null +++ b/FlexMDM/scaling_flexmdm/llada_trainer.py @@ -0,0 +1,395 @@ +import torch +import torch.nn.functional as F +from transformers import Trainer +from transformers import DefaultDataCollator +import random +from tqdm import tqdm +import pickle +import torch.distributed as dist + + + +def one_infill_process(input_ids, pad_token, prefix_end, suffix_start, instruction_tokens, prefix_delimiters, suffix_delimiters): + """ + Performs a single fill-in-the-middle (FIM) transformation on a sequence. + + This function takes a sequence of token IDs and reformats it for a text infilling task. + It rearranges the sequence into the FIM format: + `[instruction_tokens] [prefix_delimiters[0]] [prefix] [prefix_delimiters[1]] [suffix_delimiters[0]] [suffix] [suffix_delimiters[1]] [middle]` + where: + - `instruction_tokens`: Special instruction tokens at the beginning + - `prefix`: The part of the sequence before the selected span + - `middle`: The selected span of text to be "filled in" + - `suffix`: The part of the sequence after the selected span + - `prefix_delimiters`: Pair of tokens that wrap the prefix + - `suffix_delimiters`: Pair of tokens that wrap the suffix + + Args: + input_ids (torch.Tensor): A sequence of input token IDs. + pad_token (int): The ID of the padding token. + prefix_end (int): The end index for the prefix. + suffix_start (int): The start index for the suffix. + instruction_tokens (list[int]): A list of instruction token IDs at the beginning. + prefix_delimiters (list[int]): A list of two token IDs to wrap the prefix. + suffix_delimiters (list[int]): A list of two token IDs to wrap the suffix. + + Returns: + tuple[torch.Tensor, torch.Tensor]: + - new_sample (torch.Tensor): The transformed sequence with the FIM format. + - prompt_indices (torch.Tensor): A boolean mask indicating the prompt tokens in `new_sample`. + """ + device = input_ids.device + + instruction_len = len(instruction_tokens) + prefix_open_len = len(prefix_delimiters[0]) + prefix_close_len = len(prefix_delimiters[1]) + suffix_open_len = len(suffix_delimiters[0]) + suffix_close_len = len(suffix_delimiters[1]) + + input_len = (input_ids != pad_token).sum() + + instruction_tokens = torch.tensor(instruction_tokens, dtype=input_ids.dtype, device=device) + prefix_open_delim = torch.tensor(prefix_delimiters[0], dtype=input_ids.dtype, device=device) + prefix_close_delim = torch.tensor(prefix_delimiters[1], dtype=input_ids.dtype, device=device) + suffix_open_delim = torch.tensor(suffix_delimiters[0], dtype=input_ids.dtype, device=device) + suffix_close_delim = torch.tensor(suffix_delimiters[1], dtype=input_ids.dtype, device=device) + + new_sample = torch.full((input_ids.shape[0],), pad_token, dtype=input_ids.dtype, device=device) + new_sample[:instruction_len] = instruction_tokens + new_sample[instruction_len:instruction_len + prefix_open_len] = prefix_open_delim + new_sample[instruction_len + prefix_open_len:instruction_len + prefix_open_len + prefix_end] = input_ids[:prefix_end] + new_sample[instruction_len + prefix_open_len + prefix_end:instruction_len + prefix_open_len + prefix_end + prefix_close_len] = prefix_close_delim + + suffix_offset = instruction_len + prefix_open_len + prefix_end + prefix_close_len + new_sample[suffix_offset:suffix_offset + suffix_open_len] = suffix_open_delim + new_sample[suffix_offset + suffix_open_len:suffix_offset + suffix_open_len + (input_len - suffix_start)] = input_ids[suffix_start:input_len] + new_sample[suffix_offset + suffix_open_len + (input_len - suffix_start):suffix_offset + suffix_open_len + (input_len - suffix_start) + suffix_close_len] = suffix_close_delim + + middle_start = suffix_offset + suffix_open_len + (input_len - suffix_start) + suffix_close_len + + new_sample[middle_start:middle_start + (suffix_start - prefix_end)] = input_ids[prefix_end:suffix_start] + prompt_indices = torch.ones_like(new_sample, dtype=torch.bool) + prompt_indices[:middle_start] = True + prompt_indices[middle_start:] = False + + return new_sample, prompt_indices + + + + + + + +def vectorized_infill_process(input_ids, pad_token, prefix_cutoff, instruction_tokens, prefix_delimiters, suffix_delimiters): + batch_size, _ = input_ids.shape + device = input_ids.device + + input_lengths = (input_ids != pad_token).sum(dim=1) + prefix_range = input_lengths - prefix_cutoff + rand_prefix_ends = torch.rand(batch_size, device=device) * prefix_range + prefix_ends = (prefix_cutoff + rand_prefix_ends).long() + + # Generate suffix_starts indices + low_suffix_start = prefix_ends + 1 + high_suffix_start = input_lengths + suffix_range = high_suffix_start - low_suffix_start + rand_suffix_starts = torch.rand(batch_size, device=device) * suffix_range + suffix_starts = (low_suffix_start + rand_suffix_starts).long() + + new_samples, prompt_indices = [], [] + for i in range(batch_size): + new_sample, prompt_index = one_infill_process( + input_ids[i], + pad_token, + prefix_ends[i], + suffix_starts[i], + instruction_tokens, + prefix_delimiters, + suffix_delimiters + ) + new_samples.append(new_sample) + prompt_indices.append(prompt_index) + + new_samples = torch.stack(new_samples) + prompt_indices = torch.stack(prompt_indices) + + return new_samples, prompt_indices + + + +class dLLMDataCollator(DefaultDataCollator): + """ + Adds the forward noising process to the batch. + Modify forward_process to change the noise schedule + """ + + def __init__(self, *args, **kwargs): + super().__init__() + self.mask_token_id = kwargs["tokenizer"].mask_token_id + self.tokenizer = kwargs["tokenizer"] + if "max_length" in kwargs: + self.max_length = kwargs["max_length"] + if kwargs["tokenizer"].mask_token_id is None: + assert ( + "mask_token_id" in kwargs + ), "For dLLM models, pass a mask_token_id or set it equal to tokenizer.mask_token_id" + self.mask_token_id = kwargs["mask_token_id"] + + # Optional infill parameters + self.is_infill_task = kwargs.get("is_infill_task", False) + if self.is_infill_task: + self.instruction_tokens = kwargs.get("instruction_tokens", []) + self.prefix_delimiters = kwargs.get("prefix_delimiters", []) + self.suffix_delimiters = kwargs.get("suffix_delimiters", []) + + # Flatten delimiter lists if they are nested + if self.prefix_delimiters and isinstance(self.prefix_delimiters[0], list): + self.prefix_delimiters = [token for sublist in self.prefix_delimiters for token in sublist] + if self.suffix_delimiters and isinstance(self.suffix_delimiters[0], list): + self.suffix_delimiters = [token for sublist in self.suffix_delimiters for token in sublist] + + def forward_process(self, batch, eps=1e-3): + input_ids = batch["input_ids"] + B, N = input_ids.shape + if "t" not in batch: + t = torch.rand((B,), device=input_ids.device) + else: + t = batch["t"] + + t = (1 - eps) * t + eps + t = t[:, None].repeat(1, N) + + mask_indices = torch.rand((B, N), device=input_ids.device) < t + noisy_batch = torch.where(mask_indices, self.mask_token_id, input_ids) + return noisy_batch, t, mask_indices + + def __call__(self, batch): + batch = super().__call__(batch) + + # Pad input_ids to max_length before processing + if hasattr(self, 'max_length'): + batch = self.tokenizer.pad(batch, + padding = "max_length", + max_length = self.max_length, + return_tensors = "pt" + ) + + print(batch["input_ids"].shape) + + batch["labels"] = batch["input_ids"].clone() + # Apply infill transformation if enabled + if self.is_infill_task: + batch["input_ids"], infill_prompt_indices = vectorized_infill_process( + batch["input_ids"], + self.tokenizer.pad_token_id, + batch["prefix_cutoff"], + self.instruction_tokens, + self.prefix_delimiters, + self.suffix_delimiters + ) + batch["labels"] = batch["input_ids"].clone() + + noisy_batch, batch["t"], mask_indices = self.forward_process(batch) + batch["labels"][~mask_indices] = -100 + batch["num_prompt_tokens"] = 0 + + if "prompt_lengths" in batch: + prompt_lengths = batch.pop("prompt_lengths") + prompt_lengths = prompt_lengths.unsqueeze(1) # (B, 1) + prompt_length_indices = torch.arange(noisy_batch.shape[1]).unsqueeze(0) # (1, L) + + # mask the prompt tokens + prompt_mask = prompt_length_indices < prompt_lengths # (B, L) + noisy_batch[prompt_mask] = batch["input_ids"][prompt_mask].clone() + batch["labels"][prompt_mask] = -100 + batch["num_prompt_tokens"] = prompt_mask.sum() + elif self.is_infill_task: + # Use infill prompt indices to mask prompt tokens + noisy_batch[infill_prompt_indices] = batch["input_ids"][infill_prompt_indices].clone() + batch["labels"][infill_prompt_indices] = -100 + batch["num_prompt_tokens"] = infill_prompt_indices.sum() + + batch["input_ids"] = noisy_batch.long() + return batch + +class dLLMTrainer(Trainer): + def compute_loss(self, model, inputs, num_items_in_batch=None, return_outputs=False): + """ + Absorbing state diffusion loss computation + NOTE: time step t here is different from ours + """ + normalize_constant = 4096 + batch_size = inputs["input_ids"].size(0) + + labels, t, num_prompt_tokens = inputs.pop("labels"), inputs.pop("t"), inputs.pop("num_prompt_tokens") + + if "prefix_cutoff" in inputs: + inputs.pop("prefix_cutoff") + + outputs = model(**inputs) + logits = outputs.logits + unscaled_loss = F.cross_entropy( + logits.view(-1, logits.shape[-1]), labels.view(-1), reduction="none" + ).view(logits.shape[0], -1) + if (self.state.global_step + 1) % self.args.logging_steps == 0: + self.log({"unscaled_loss": (unscaled_loss.sum() / (labels != -100).sum()).item()}) + loss = unscaled_loss / t + loss = loss.sum() / (batch_size * normalize_constant) + + # double-check debug + if return_outputs: + print("Retuning outputs") + return loss, {"dummy": None} + + return loss + + +class dLLMSFTDataset(torch.utils.data.Dataset): + """ + Similar to AR datasets, except in inference, we keep the timsteps fixed + """ + + def __init__(self, data, tokenizer, max_length, eval=False): + super().__init__() + self.data = data + self.tokenizer = tokenizer + self.max_length = max_length + self.eval = eval + if self.eval: + self.t = torch.linspace(0, 1, len(self.data)) + + def __len__(self): + return len(self.data) + + def __getitem__(self, idx): + out = self.data[idx] + if self.eval: + out["t"] = self.t[idx] + return out + + +class dLLMDataCollator(DefaultDataCollator): + """ + Adds the forward noising process to the batch. + Modify forward_process to change the noise schedule + """ + + def __init__(self, *args, **kwargs): + super().__init__() + self.mask_token_id = kwargs["tokenizer"].mask_token_id + self.tokenizer = kwargs["tokenizer"] + if "max_length" in kwargs: + self.max_length = kwargs["max_length"] + if kwargs["tokenizer"].mask_token_id is None: + assert ( + "mask_token_id" in kwargs + ), "For dLLM models, pass a mask_token_id or set it equal to tokenizer.mask_token_id" + self.mask_token_id = kwargs["mask_token_id"] + + # Optional infill parameters + self.is_infill_task = kwargs.get("is_infill_task", False) + if self.is_infill_task: + self.instruction_tokens = kwargs.get("instruction_tokens", []) + self.prefix_delimiters = kwargs.get("prefix_delimiters", []) + self.suffix_delimiters = kwargs.get("suffix_delimiters", []) + + def forward_process(self, batch, eps=1e-3): + input_ids = batch["input_ids"] + B, N = input_ids.shape + if "t" not in batch: + t = torch.rand((B,), device=input_ids.device) + else: + t = batch["t"] + + t = (1 - eps) * t + eps + t = t[:, None].repeat(1, N) + + mask_indices = torch.rand((B, N), device=input_ids.device) < t + noisy_batch = torch.where(mask_indices, self.mask_token_id, input_ids) + return noisy_batch, t, mask_indices + + def __call__(self, batch): + batch = super().__call__(batch) + + # Pad input_ids to max_length before processing + if hasattr(self, 'max_length'): + batch = self.tokenizer.pad(batch, + padding = "max_length", + max_length = self.max_length, + return_tensors = "pt" + ) + + batch["labels"] = batch["input_ids"].clone() + # Apply infill transformation if enabled + if self.is_infill_task: + batch["input_ids"], infill_prompt_indices = vectorized_infill_process( + batch["input_ids"], + self.tokenizer.pad_token_id, + batch["prefix_cutoff"], + self.instruction_tokens, + self.prefix_delimiters, + self.suffix_delimiters + ) + batch["labels"] = batch["input_ids"].clone() + + noisy_batch, batch["t"], mask_indices = self.forward_process(batch) + batch["labels"][~mask_indices] = -100 + batch["num_prompt_tokens"] = 0 + + if "prompt_lengths" in batch: + prompt_lengths = batch.pop("prompt_lengths") + prompt_lengths = prompt_lengths.unsqueeze(1) # (B, 1) + prompt_length_indices = torch.arange(noisy_batch.shape[1]).unsqueeze(0) # (1, L) + + # mask the prompt tokens + prompt_mask = prompt_length_indices < prompt_lengths # (B, L) + noisy_batch[prompt_mask] = batch["input_ids"][prompt_mask].clone() + batch["labels"][prompt_mask] = -100 + batch["num_prompt_tokens"] = prompt_mask.sum() + elif self.is_infill_task: + # Use infill prompt indices to mask prompt tokens + noisy_batch[infill_prompt_indices] = batch["input_ids"][infill_prompt_indices].clone() + batch["labels"][infill_prompt_indices] = -100 + batch["num_prompt_tokens"] = infill_prompt_indices.sum() + + batch["input_ids"] = noisy_batch.long() + return batch + + +SYSTEM_PROMPT = """ +Respond in the following format: + +Your reasoning here + + +... + +""" + + +def preprocess_dataset(data, tokenizer, max_length, test_split=0.01): + preprocessed_data = [] + # TODO: check if the pad_token = mask_token + for i in tqdm(range(len(data)), desc="Preprocessing dataset"): + question = SYSTEM_PROMPT + "\n\n" + data[i]["question"] + trajectory = f"{data[i]['thinking_trajectories'][0]}\n{data[i]['attempt']}" + prompt = [{"role": "user", "content": question}] + response = [{"role": "assistant", "content": trajectory}] + inputs = tokenizer.apply_chat_template(prompt + response, tokenize=False) + prompt = tokenizer.apply_chat_template(prompt, tokenize=False) + "\n" + tokenized_input = tokenizer( + inputs, return_tensors="pt", truncation=True, max_length=max_length, padding="max_length" + ).input_ids.squeeze(0) + num_tokens = tokenized_input.shape[0] + tokenized_prompt = tokenizer(prompt, return_tensors="pt", truncation=True, max_length=max_length) + preprocessed_data.append( + { + "input_ids": tokenized_input, + "prompt_lengths": tokenized_prompt.attention_mask.sum(-1), + } + ) + + random.shuffle(preprocessed_data) + test_data = preprocessed_data[: int(len(preprocessed_data) * test_split)] + train_data = preprocessed_data[int(len(preprocessed_data) * test_split) :] + return train_data, test_data diff --git a/FlexMDM/scaling_flexmdm/llada_utils.py b/FlexMDM/scaling_flexmdm/llada_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..70ced1f3010609683d2b417d8f19b425329d05fc --- /dev/null +++ b/FlexMDM/scaling_flexmdm/llada_utils.py @@ -0,0 +1,46 @@ +import importlib.util, sys, types, pathlib +from huggingface_hub import hf_hub_download +from typing import Tuple, Type +import tempfile + + +# ------------------------------------------------------------ +# load llada modules +# ------------------------------------------------------------ + +def load_llada_modules( + repo_id: str = "GSAI-ML/LLaDA-8B-Base", +) -> Tuple[Type, ...]: + """ + Returns: + (LLaDASequentialBlock, ModelConfig, ActivationType, LayerNormType) + """ + tmp_dir = tempfile.mkdtemp(prefix="llada_") + modeling_path = hf_hub_download(repo_id, "modeling_llada.py", + local_dir=tmp_dir, local_dir_use_symlinks=False) + configuration_path = hf_hub_download(repo_id, "configuration_llada.py", + local_dir=tmp_dir, local_dir_use_symlinks=False) + + pkg_name = "llada_remote" + pkg = types.ModuleType(pkg_name) + pkg.__path__ = [tmp_dir] + sys.modules[pkg_name] = pkg + + spec_conf = importlib.util.spec_from_file_location( + f"{pkg_name}.configuration_llada", configuration_path) + conf_mod = importlib.util.module_from_spec(spec_conf) + sys.modules[spec_conf.name] = conf_mod + spec_conf.loader.exec_module(conf_mod) + + spec_model = importlib.util.spec_from_file_location( + f"{pkg_name}.modeling_llada", modeling_path) + model_mod = importlib.util.module_from_spec(spec_model) + sys.modules[spec_model.name] = model_mod + spec_model.loader.exec_module(model_mod) # type: ignore[arg-type] + + return ( + model_mod.LLaDASequentialBlock, + model_mod.ModelConfig, + model_mod.ActivationType, + model_mod.LayerNormType, + ) \ No newline at end of file diff --git a/FlexMDM/scaling_flexmdm/preprocess_code_infilling.py b/FlexMDM/scaling_flexmdm/preprocess_code_infilling.py new file mode 100644 index 0000000000000000000000000000000000000000..06224235057e6f6a3dd472095e401560297ddfeb --- /dev/null +++ b/FlexMDM/scaling_flexmdm/preprocess_code_infilling.py @@ -0,0 +1,58 @@ +from datasets import load_dataset + +def preprocess_opc_coder(tokenizer, max_length): + ds = load_dataset("OpenCoder-LLM/opc-sft-stage2", "educational_instruct")['train'] + + def process_sample(sample): + # Tokenize instruction and output separately + instruction_tokens = tokenizer(sample['instruction'], add_special_tokens=False)['input_ids'] + output_tokens = tokenizer(sample['output'], add_special_tokens=False)['input_ids'] + + # Combine instruction and output + input_ids = instruction_tokens + output_tokens + + # Pad to max_length + if len(input_ids) < max_length: + input_ids = input_ids + [tokenizer.pad_token_id] * (max_length - len(input_ids)) + elif len(input_ids) > max_length: + input_ids = input_ids[:max_length] + + # Set prefix_cutoff to the length of the instruction + prefix_cutoff = len(instruction_tokens) + + return { + 'input_ids': input_ids, + 'prefix_cutoff': prefix_cutoff + } + + processed_ds = ds.map(process_sample, remove_columns=ds.column_names) + return processed_ds + + +def preprocess_human_eval(tokenizer, max_length): + ds = load_dataset("openai/openai_humaneval")['test'] + + def process_sample(sample): + # Tokenize prompt and canonical_solution separately + prompt_tokens = tokenizer(sample['prompt'], add_special_tokens=False)['input_ids'] + solution_tokens = tokenizer(sample['canonical_solution'], add_special_tokens=False)['input_ids'] + + # Combine prompt and solution + input_ids = prompt_tokens + solution_tokens + + # Pad to max_length + if len(input_ids) < max_length: + input_ids = input_ids + [tokenizer.pad_token_id] * (max_length - len(input_ids)) + elif len(input_ids) > max_length: + input_ids = input_ids[:max_length] + + # Set prefix_cutoff to the length of the prompt + prefix_cutoff = len(prompt_tokens) + + return { + 'input_ids': input_ids, + 'prefix_cutoff': prefix_cutoff + } + + processed_ds = ds.map(process_sample, remove_columns=ds.column_names) + return processed_ds \ No newline at end of file diff --git a/FlexMDM/scaling_flexmdm/preprocess_math.py b/FlexMDM/scaling_flexmdm/preprocess_math.py new file mode 100644 index 0000000000000000000000000000000000000000..678a06199f877434098997e6729920545d260008 --- /dev/null +++ b/FlexMDM/scaling_flexmdm/preprocess_math.py @@ -0,0 +1,163 @@ +import torch +import numpy as np +import torch.nn.functional as F + +from transformers import AutoTokenizer, AutoModel +from tqdm import tqdm +import time +import random +import re +from datasets import load_dataset +from parsers import Parser, is_equiv +import torch.distributed as dist +from torch.utils.data import DataLoader + +GSM_SYSTEM_PROMPT = """You are a math expert. You will be given a question to solve. Solve it step by step. Wrap the final answer in a \\boxed{}. +Respond in the following format: + +Your reasoning here + + +\\boxed{...} +""" + + +# ----------------------------------------------------------------------------- +# helpers +# ----------------------------------------------------------------------------- + +def extract_rationale_and_answer(answer_field: str): + """Split raw GSM8K `answer` into (reasoning_text, final_answer_str).""" + if "####" in answer_field: + rationale, final_ans = answer_field.split("####", 1) + return rationale.strip(), final_ans.strip() + # fallback – dataset might already be split elsewhere + return "", answer_field.strip() + +# ----------------------------------------------------------------------------- +# data preprocessing code +# ----------------------------------------------------------------------------- + +def preprocess_gsm8k( + split: str, tokenizer: AutoTokenizer, max_length: int +): + ds = load_dataset("gsm8k", "main", split=split) + prompt_builder = GSM8KDataset(tokenizer, num_examples=0, add_reasoning=True) + preprocessed = [] + + for ex in tqdm(ds, desc = "Preprocessing"): + q = ex["question"].strip() + rat, ans = extract_rationale_and_answer(ex["answer"]) + prompt_text = prompt_builder.create_prompt(q) + target_txt = f"{rat}\n\\boxed{{{ans}}}" + full_txt = prompt_text + target_txt + + enc = tokenizer(full_txt, truncation=True, max_length=max_length, + padding="max_length", return_tensors="pt") + input_ids = enc.input_ids.squeeze(0) + prompt_len = len(tokenizer(prompt_text).input_ids) + + preprocessed.append({"input_ids": input_ids, "prompt_lengths": prompt_len}) + + # ---- shuffle & split like your original helper ---- + random.shuffle(preprocessed) + + return preprocessed + + +class GSM8KDataset(torch.utils.data.Dataset): + def __init__( + self, + tokenizer, + num_examples=0, + add_reasoning=True, + system_prompt=GSM_SYSTEM_PROMPT, + subsample=-1, + ): + self.tokenizer = tokenizer + self.num_examples = num_examples + self.add_reasoning = add_reasoning + self.system_prompt = system_prompt + self.load_test_dataset() + self.create_few_shot_prompt() + + self.subsample = ( + np.random.choice(len(self.dataset), subsample, replace=False) + if subsample != -1 + else np.arange(len(self.dataset)) + ) + print(f"evaluating {len(self.subsample)} examples") + assert subsample <= len(self.dataset), "Subsample size is greater than dataset size" + + def __len__(self): + return len(self.subsample) + + def load_test_dataset(self): + self.dataset = load_dataset("gsm8k", "main", split="test") + + def create_prompt(self, input_text): + # Format similar to your chat function + if self.num_examples > 0: + prompt = f"{self.few_shot_prompt}\n\nQuestion: {input_text}\nAnswer:\n" + else: + prompt = input_text + messages = [{"role": "user", "content": self.system_prompt + "\n\n" + prompt}] + user_input = self.tokenizer.apply_chat_template(messages, add_generation_prompt=True, tokenize=False) + if self.add_reasoning: + return user_input + "" + else: + return user_input + + def load_few_shot_examples(self): + if isinstance(self.dataset, GSM8KDataset): + train_data = load_dataset("gsm8k", "main", split="train") + examples = random.sample(range(len(train_data)), self.num_examples) + return [train_data[example] for example in examples] + else: + return [] + + def create_few_shot_prompt(self): + """Create few-shot prompt from dataset examples""" + few_shot_examples = self.load_few_shot_examples() + + formatted_examples = [] + for example in few_shot_examples: + input_text = example["question"] + answer = example["answer"] + formatted_examples.append(f"Question: {input_text}\nAnswer:\n{answer}") + self.few_shot_prompt = "\n\n".join(formatted_examples) + + def __getitem__(self, idx): + question = self.dataset[self.subsample[idx].item()]["question"] + answer = Parser.extract_answer_gsm8k(self.dataset[self.subsample[idx].item()]["answer"]) + prompt = self.create_prompt(question) + return prompt, question, answer + + def collate_fn(self, batch): + prompts = [item[0] for item in batch] + questions = [item[1] for item in batch] + answers = [item[2] for item in batch] + input_ids = self.tokenizer( + prompts, padding_side="left", return_tensors="pt", padding="longest" + ).input_ids + return {"input_ids": input_ids, "questions": questions, "answers": answers, "prompts": prompts} + + + +# test code +# if __name__ == "__main__": +# train_data, test_data = preprocess_gsm8k( +# split="train", model_name="GSAI-ML/LLaDA-8B-Base", max_length=4096, test_split=0.01 +# ) + +# def collate_fn(batch): +# ids = torch.stack([item["input_ids"] for item in batch]) +# plen = torch.tensor([item["prompt_len"] for item in batch]) +# return {"input_ids": ids, "prompt_lengths": plen} + +# loader = DataLoader(train_data, batch_size=2, collate_fn=collate_fn) + +# for batch in loader: +# print(batch["input_ids"].shape) +# print(batch["prompt_lengths"]) +# break \ No newline at end of file diff --git a/FlexMDM/scaling_flexmdm/scripts/IFT_code.sh b/FlexMDM/scaling_flexmdm/scripts/IFT_code.sh new file mode 100644 index 0000000000000000000000000000000000000000..29e45406c90c0b435f1928de6b3b2833a53b0bb6 --- /dev/null +++ b/FlexMDM/scaling_flexmdm/scripts/IFT_code.sh @@ -0,0 +1,47 @@ +#!/bin/bash +#SBATCH --job-name=sft_code_infill +#SBATCH --account=kempner_albergo_lab +#SBATCH --partition=kempner_h100 +#SBATCH --nodes=4 +#SBATCH --gpus-per-node=4 +#SBATCH --mem=512GB +#SBATCH --ntasks-per-node=1 +#SBATCH --cpus-per-task=16 +#SBATCH --time=3-00:00:00 +#SBATCH --output=slurm_logs/sft_gsm8k/%j.out +#SBATCH --error=slurm_logs/sft_gsm8k/%j.err +#SBATCH --mail-type=END,FAIL +#SBATCH --mail-user=brianlee.lck@gmail.com + +export HF_HOME=/n/netscratch/albergo_lab/Everyone/hf_cache +export HF_HUB_ENABLE_HF_TRANSFER=1 + +module load cuda/12.4.1-fasrc01 + +export NCCL_SOCKET_FAMILY=AF_INET +export MASTER_ADDR=$(scontrol show hostnames $SLURM_NODELIST | head -n 1) +export MASTER_PORT=$(shuf -i 15000-59999 -n 1) +export NODE_RANK=$SLURM_NODEID + +# Create output directory if it doesn't exist +mkdir -p slurm_logs/sft_gsm8k + +srun --ntasks-per-node=1 --gpus-per-task=4 \ + python -m torch.distributed.run \ + --nproc_per_node=4 \ + --nnodes=$SLURM_JOB_NUM_NODES \ + --node_rank=$NODE_RANK \ + --rdzv_backend=c10d \ + --rdzv_endpoint=$MASTER_ADDR:$MASTER_PORT \ + --rdzv_id=$SLURM_JOB_ID \ + instruction_finetuning.py \ + --wandb \ + --job_name=llada-sft-code-infill \ + --train_data=code-infill \ + --num_epochs 50 \ + --resume_from_checkpoint /n/netscratch/sham_lab/Everyone/jay_brian/sft-datamix-checkpoints/llada-sft-openwebtext-linear/checkpoint-200000 \ + + + + + diff --git a/FlexMDM/scaling_flexmdm/scripts/IFT_math.sh b/FlexMDM/scaling_flexmdm/scripts/IFT_math.sh new file mode 100644 index 0000000000000000000000000000000000000000..c2eccf19e22dda93eb560a381c7f7d41eda593e7 --- /dev/null +++ b/FlexMDM/scaling_flexmdm/scripts/IFT_math.sh @@ -0,0 +1,43 @@ +#!/bin/bash +#SBATCH --job-name=sft_gsm8k +#SBATCH --account=kempner_albergo_lab +#SBATCH --partition=kempner_h100 +#SBATCH --nodes=4 +#SBATCH --gpus-per-node=4 +#SBATCH --mem=512GB +#SBATCH --ntasks-per-node=1 +#SBATCH --cpus-per-task=16 +#SBATCH --time=3-00:00:00 +#SBATCH --output=slurm_logs/sft_gsm8k/%j.out +#SBATCH --error=slurm_logs/sft_gsm8k/%j.err +#SBATCH --mail-type=END,FAIL +#SBATCH --mail-user=brianlee.lck@gmail.com + +export HF_HOME=/n/netscratch/albergo_lab/Everyone/hf_cache +export HF_HUB_ENABLE_HF_TRANSFER=1 + +module load cuda/12.4.1-fasrc01 + +export NCCL_SOCKET_FAMILY=AF_INET +export MASTER_ADDR=$(scontrol show hostnames $SLURM_NODELIST | head -n 1) +export MASTER_PORT=$(shuf -i 15000-59999 -n 1) +export NODE_RANK=$SLURM_NODEID + +export TORCH_DISTRIBUTED_DEBUG=DETAIL + +# Create output directory if it doesn't exist +mkdir -p slurm_logs/sft_gsm8k + +srun --ntasks-per-node=1 --gpus-per-task=4 \ + python -m torch.distributed.run \ + --nproc_per_node=4 \ + --nnodes=$SLURM_JOB_NUM_NODES \ + --node_rank=$NODE_RANK \ + --rdzv_backend=c10d \ + --rdzv_endpoint=$MASTER_ADDR:$MASTER_PORT \ + --rdzv_id=$SLURM_JOB_ID \ + instruction_finetuning.py \ + --wandb \ + --variable_length \ + --job_name=llada-sft-gsm8k \ + --train_data=gsm8k \ No newline at end of file diff --git a/FlexMDM/scaling_flexmdm/scripts/flexmdm_transfer.sh b/FlexMDM/scaling_flexmdm/scripts/flexmdm_transfer.sh new file mode 100644 index 0000000000000000000000000000000000000000..b7dfe2f82d15123003723b76f0016de6e3beb703 --- /dev/null +++ b/FlexMDM/scaling_flexmdm/scripts/flexmdm_transfer.sh @@ -0,0 +1,40 @@ +#!/bin/bash +#SBATCH --job-name=test_sft_openwebtext +#SBATCH --account=kempner_albergo_lab +#SBATCH --partition=kempner_h100 +#SBATCH --nodes=4 +#SBATCH --gpus-per-node=4 +#SBATCH --mem=512GB +#SBATCH --ntasks-per-node=1 +#SBATCH --cpus-per-task=16 +#SBATCH --time=3-00:00:00 +#SBATCH --output=slurm_logs/sft_openwebtext/%j.out +#SBATCH --error=slurm_logs/sft_openwebtext/%j.err +#SBATCH --mail-type=END,FAIL +#SBATCH --mail-user=brianlee.lck@gmail.com + +export HF_HOME=/n/netscratch/albergo_lab/Everyone/hf_cache +export HF_HUB_ENABLE_HF_TRANSFER=1 + +module load cuda/12.4.1-fasrc01 +conda activate d1 + +export NCCL_SOCKET_FAMILY=AF_INET +export MASTER_ADDR=$(scontrol show hostnames $SLURM_NODELIST | head -n 1) +export MASTER_PORT=$(shuf -i 15000-59999 -n 1) +export NODE_RANK=$SLURM_NODEID + +export TORCH_DISTRIBUTED_DEBUG=DETAIL + +srun --ntasks-per-node=1 --gpus-per-task=4 \ + python -m torch.distributed.run \ + --nproc_per_node=4 \ + --nnodes=$SLURM_JOB_NUM_NODES \ + --node_rank=$NODE_RANK \ + --rdzv_backend=c10d \ + --rdzv_endpoint=$MASTER_ADDR:$MASTER_PORT \ + --rdzv_id=$SLURM_JOB_ID \ + flexmdm_transfer_openwebtext.py \ + --wandb \ + --variable_length \ + --low_discrepancy True diff --git a/FlexMDM/scaling_flexmdm/scripts/flexmdm_transfer_preprocess.sh b/FlexMDM/scaling_flexmdm/scripts/flexmdm_transfer_preprocess.sh new file mode 100644 index 0000000000000000000000000000000000000000..a8dea3f431dc3c762a5153eed171fca14f0befbb --- /dev/null +++ b/FlexMDM/scaling_flexmdm/scripts/flexmdm_transfer_preprocess.sh @@ -0,0 +1,17 @@ +#!/bin/bash +#SBATCH --job-name=datamix +#SBATCH --account=albergo_lab +#SBATCH --partition=sapphire +#SBATCH --nodes=1 +#SBATCH --mem=256GB +#SBATCH --cpus-per-task=64 +#SBATCH --time=3-00:00:00 +#SBATCH --output=slurm_logs/datamix/%j.out +#SBATCH --error=slurm_logs/datamix/%j.err +#SBATCH --mail-type=END,FAIL +#SBATCH --mail-user=brianlee.lck@gmail.com + +export HF_HOME=/n/netscratch/albergo_lab/Everyone/hf_cache +export HF_HUB_ENABLE_HF_TRANSFER=1 + +python flexmdm_transfer_preprocess.py \ No newline at end of file diff --git a/FlexMDM/scaling_flexmdm/scripts/inference_code.sh b/FlexMDM/scaling_flexmdm/scripts/inference_code.sh new file mode 100644 index 0000000000000000000000000000000000000000..fcd8283af67d71a22eedf5d8eaaf40f99c23d946 --- /dev/null +++ b/FlexMDM/scaling_flexmdm/scripts/inference_code.sh @@ -0,0 +1,43 @@ +#!/bin/bash +#SBATCH --job-name=gen_humaneval +#SBATCH --account=albergo_lab +#SBATCH --partition=gpu_h200 +#SBATCH --nodes=1 +#SBATCH --ntasks-per-node=4 +#SBATCH --gpus-per-node=4 +#SBATCH --mem=128GB +#SBATCH -C h200 +#SBATCH --cpus-per-task=16 +#SBATCH --time=03-00:00:00 +#SBATCH --output=slurm_logs/humaneval_sample/%j.out +#SBATCH --error=slurm_logs/humaneval_sample/%j.err +#SBATCH --mail-type=END,FAIL +#SBATCH --mail-user=brianlee.lck@gmail.com + + +export HF_HOME=/n/netscratch/albergo_lab/Everyone/hf_cache +export HF_HUB_ENABLE_HF_TRANSFER=1 + +export NCCL_SOCKET_FAMILY=AF_INET +export MASTER_ADDR=$(scontrol show hostnames $SLURM_NODELIST | head -n 1) +export MASTER_PORT=$(shuf -i 15000-59999 -n 1) +export NODE_RANK=$SLURM_NODEID + + +python -m torch.distributed.run \ + --nproc_per_node=1 \ + --nnodes=1 \ + --node_rank=0 \ + --master_addr=$MASTER_ADDR \ + --master_port=$MASTER_PORT \ + eval_humaneval_infill.py \ + --checkpoint_path /n/netscratch/albergo_lab/Lab/brianlck/checkpoints/llada-varlen-code/llada-sft-varlen-code-infill/llada-sft-code-infill/last-checkpoint \ + --output_dir human_eval/var-len \ + --alpha 15.0 \ + --max_window 32 \ + --diffusion_steps 4096 \ + --confidence_method top_prob \ + --variable_length \ + --use_sliding_window \ + --batch_size 4 \ + diff --git a/FlexMDM/scaling_flexmdm/scripts/inference_math.sh b/FlexMDM/scaling_flexmdm/scripts/inference_math.sh new file mode 100644 index 0000000000000000000000000000000000000000..057d50cf9a051e04c4f2e7a6ed14d267a6b8eefc --- /dev/null +++ b/FlexMDM/scaling_flexmdm/scripts/inference_math.sh @@ -0,0 +1,35 @@ +#!/bin/bash +#SBATCH --job-name=test_sft_openwebtext +#SBATCH --account=kempner_albergo_lab +#SBATCH --partition=kempner_h100 +#SBATCH --nodes=1 +#SBATCH --ntasks-per-node=4 +#SBATCH --gpus-per-node=4 +#SBATCH --mem=512GB +#SBATCH --cpus-per-task=16 +#SBATCH --time=3-00:00:00 +#SBATCH --output=slurm_logs/sft_openwebtext/%j.out +#SBATCH --error=slurm_logs/sft_openwebtext/%j.err +#SBATCH --mail-type=END,FAIL +#SBATCH --mail-user=brianlee.lck@gmail.com + + +export MASTER_ADDR=$(scontrol show hostnames $SLURM_JOB_NODELIST | head -n 1) +export MASTER_PORT=29500 +export WORLD_SIZE=$SLURM_NTASKS +export RANK=$SLURM_PROCID + + +srun python -m torch.distributed.run \ + --nproc_per_node=4 \ + --nnodes=1 \ + --node_rank=0 \ + --master_addr=$MASTER_ADDR \ + --master_port=$MASTER_PORT \ + eval_gsm8k.py \ + --variable_length \ + --checkpoint_path_variable /n/netscratch/albergo_lab/Lab/sft-gsm8k-checkpoints/llada-sft-gsm8k/checkpoint-9800/ \ + --output_dir results/gsm8k \ + --diffusion_steps 256 \ + --batch_size 8 \ + --dataset gsm8k diff --git a/FlexMDM/schedule.py b/FlexMDM/schedule.py new file mode 100644 index 0000000000000000000000000000000000000000..38e27fd72d24cbba5b95c1d1f63fa0a2b7e2c48b --- /dev/null +++ b/FlexMDM/schedule.py @@ -0,0 +1,156 @@ +import abc +from omegaconf import DictConfig +import torch +import torch.nn as nn +from torch import Tensor + + +def get_schedule_from_config(config: DictConfig): + match config.type: + case "geometric": + return GeometricSchedule(min_val=config.min, max_val=config.max) + case "linear": + return LinearSchedule() + case "sin": + return SinSchedule() + case "cosine": + return CosineSchedule() + case "polynomial": + return PolynomialSchedule(exp=config.exp) + case _: + raise ValueError(f"Invalid schedule type: {config.type}") + + +class Schedule(abc.ABC): + """ + Generic schedule class for masking or noising + This represents function a : [0, 1] -> [0, 1] satisfying a(0) = 0, a(1) = 1 or at least approximately + """ + + @abc.abstractmethod + def at(self, t: Tensor): + """ + Return value a(t) + """ + raise NotImplementedError + + @abc.abstractmethod + def derivative_at(self, t: Tensor): + """ + Return d/dt a(t) + """ + raise NotImplementedError + + def rate_scale_factor(self, t: Tensor) -> Tensor: + """ + Return d/dt a(t) / (1 - a(t)) common in rate matrix calculation + """ + return self.derivative_at(t) / (1 - self.at(t)) + + def sample(self, shape, device) -> Tensor: + """ + Sample from the schedule, returns a tensor of shape `shape` with values in [0, 1] + """ + uniform = torch.rand(shape, device=device) + return self.inv(uniform) + + def sample_truncated(self, threshold, shape, device) -> Tensor: + """ + Sample from a truncated schedule, returns a tensor of shape `shape` with values in [threshold, 1] + """ + uniform = torch.rand(shape, device=device) + threshold = self.at(threshold) + return self.inv(uniform * (1 - threshold) + threshold) + + @abc.abstractmethod + def inv(self, alpha: Tensor): + """ + Given alpha in [0, 1] such that a(t)=alpha, returns the corresponding t. + """ + raise NotImplementedError + + +class LinearSchedule(Schedule): + def __init__(self): + pass + + def at(self, t: Tensor): + return t + + def derivative_at(self, t: Tensor): + return torch.ones_like(t, device=t.device) + + def inv(self, alpha: Tensor): + return alpha + + +class GeometricSchedule(Schedule, nn.Module): + def __init__(self, min_val: float, max_val: float): + super().__init__() + self.register_buffer("min", Tensor([min_val])) + self.register_buffer("max", Tensor([max_val])) + + def at(self, t: Tensor): + min_val = self.min.to(t.device) + max_val = self.max.to(t.device) + return torch.exp(-(min_val ** (1 - t)) * max_val**t) + + def derivative_at(self, t): + min_val = self.min.to(t.device) + max_val = self.max.to(t.device) + return ( + self.at(t) + * min_val ** (1 - t) + * max_val**t + * (min_val.log() - max_val.log()) + ) + + def inv(self, alpha: Tensor): + log_min = self.min.to(alpha.device).log() + log_max = self.max.to(alpha.device).log() + return (torch.log(-torch.log(alpha)) - log_min) / (log_max - log_min) + + +class SinSchedule(Schedule, nn.Module): + def __init__(self): + super().__init__() + + def at(self, t: Tensor): + return torch.sin(torch.pi / 2 * t) + + def derivative_at(self, t: Tensor): + return (torch.pi / 2) * torch.cos(torch.pi / 2 * t) + + def inv(self, alpha: Tensor): + return (2 / torch.pi) * torch.asin(alpha.clamp(min=0., max=1.)) + + +class CosineSchedule(Schedule, nn.Module): + def __init__(self): + super().__init__() + + def at(self, t: Tensor): + return 1 - torch.cos(torch.pi / 2 * t) + + def derivative_at(self, t: Tensor): + return (torch.pi / 2) * torch.sin(torch.pi / 2 * t) + + def rate_scale_factor(self, t): + return (torch.pi/2) * torch.tan(torch.pi / 2 * t) + + def inv(self, alpha): + return (2 / torch.pi) * torch.arccos(1 - alpha.clamp(min=0., max=1.)) + +class PolynomialSchedule(Schedule, nn.Module): + def __init__(self, exp): + super().__init__() + self.exp = exp + + def at(self, t: Tensor): + return t ** self.exp + + def derivative_at(self, t: Tensor): + return self.exp * t ** (self.exp - 1) + + def inv(self, alpha: Tensor): + return alpha ** (1 / self.exp) \ No newline at end of file diff --git a/FlexMDM/scripts/bracket/var-len.sh b/FlexMDM/scripts/bracket/var-len.sh new file mode 100644 index 0000000000000000000000000000000000000000..0d3196d5c8a306b5418e5d5f8fca1d0bcd26c479 --- /dev/null +++ b/FlexMDM/scripts/bracket/var-len.sh @@ -0,0 +1,20 @@ +#!/bin/bash +#SBATCH --job-name=bracket +#SBATCH --partition=kempner_requeue +#SBATCH --account=kempner_albergo_lab +#SBATCH --nodes=1 +#SBATCH --gpus-per-node=1 +#SBATCH --mem=200GB +#SBATCH --time=01:00:00 +#SBATCH -o slurm_logs/bracket/job-%j.out +#SBATCH -e slurm_logs/bracket/job-%j.err +#SBATCH --mail-type=END,FAIL +#SBATCH --mail-user=brianlee.lck@gmail.com +#SBATCH --signal=SIGUSR1@90 + +source /n/netscratch/albergo_lab/Lab/brianlck/interpretable-flow/.venv/bin/activate + +export HF_HOME=/n/netscratch/albergo_lab/Everyone/hf_cache +export HF_HUB_ENABLE_HF_TRANSFER=1 + +srun python train.py --config-path config/bracket --config-name any_order \ No newline at end of file diff --git a/FlexMDM/scripts/eval_genppl.sh b/FlexMDM/scripts/eval_genppl.sh new file mode 100644 index 0000000000000000000000000000000000000000..2a0e0b80482b18005209ae4526382afbd4b9e140 --- /dev/null +++ b/FlexMDM/scripts/eval_genppl.sh @@ -0,0 +1,23 @@ +#!/bin/bash +#SBATCH --job-name=eval_genppl +#SBATCH --account=kempner_sham_lab +#SBATCH --partition=kempner_h100 +#SBATCH --nodes=1 +#SBATCH --gpus-per-node=1 +#SBATCH --mem=100GB +#SBATCH --time=1-00:00:00 +#SBATCH --output=slurm_logs/eval/job-%j.out +#SBATCH --mail-type=END,FAIL +#SBATCH --mail-user=jaeyeon_kim@g.harvard.edu + + +# define paths +source ~/.bashrc + +conda deactivate +conda activate jay_vlmdm + +# MDM +python evaluation.py --model mdm --num_samples 1024 --temperature 0.9 --genppl + +python evaluation.py --model vlmdm --num_samples 1024 --temperature 0.9 --genppl \ No newline at end of file diff --git a/FlexMDM/scripts/eval_len_stats.sh b/FlexMDM/scripts/eval_len_stats.sh new file mode 100644 index 0000000000000000000000000000000000000000..0e572f1394ad2ae803478aa13cf78b26c35be410 --- /dev/null +++ b/FlexMDM/scripts/eval_len_stats.sh @@ -0,0 +1,23 @@ +#!/bin/bash +#SBATCH --job-name=eval_len_stats +#SBATCH --account=kempner_sham_lab +#SBATCH --partition=kempner_h100 +#SBATCH --nodes=1 +#SBATCH --gpus-per-node=1 +#SBATCH --mem=100GB +#SBATCH --time=1-00:00:00 +#SBATCH --output=slurm_logs/eval_len_stats/job-%j.out +#SBATCH --mail-type=END,FAIL +#SBATCH --mail-user=jaeyeon_kim@g.harvard.edu + + +# define paths +source ~/.bashrc + +conda deactivate +conda activate jay_vlmdm + +# MDM +python evaluation.py --model mdm --num_samples 1024 --temperature 0.9 --len_stats + +python evaluation.py --model vlmdm --num_samples 1024 --temperature 0.9 --len_stats \ No newline at end of file diff --git a/FlexMDM/scripts/evaluate_samples_only.sh b/FlexMDM/scripts/evaluate_samples_only.sh new file mode 100644 index 0000000000000000000000000000000000000000..d233282bf2af1e538ab5cb6f37c7a7b2c2c86af7 --- /dev/null +++ b/FlexMDM/scripts/evaluate_samples_only.sh @@ -0,0 +1,77 @@ +#!/bin/bash +#SBATCH --job-name=evaluate_samples_only +#SBATCH --account=kempner_albergo_lab +#SBATCH --partition=kempner_h100 +#SBATCH --nodes=1 +#SBATCH --gpus-per-node=1 +#SBATCH --mem=100GB +#SBATCH --constraint h100 +#SBATCH --time=1-00:00:00 +#SBATCH --output=slurm_logs/vlmdm/job-%A_%a.out +#SBATCH --mail-type=END,FAIL +#SBATCH --mail-user=brianlee.lck@gmail.com +#SBATCH --array=0-19 + +# ...existing environment setup... +source /n/netscratch/albergo_lab/Lab/brianlck/interpretable-flow/.venv/bin/activate +export CUDA_LAUNCH_BLOCKING=1 +export TORCH_USE_CUDA_DSA=1 +export HF_HOME=/n/netscratch/albergo_lab/Everyone/hf_cache +export HF_HUB_ENABLE_HF_TRANSFER=1 + +# define models, checkpoints, step sizes, and samplers +MODELS=(mdm flow) +CKPTS=( + /n/netscratch/albergo_lab/Lab/brianlck/interpretable-flow/outputs/2025-06-30/18-45-34/checkpoints/openwebtext/mdm/20250630-184537/last.ckpt + /n/netscratch/albergo_lab/Lab/brianlck/interpretable-flow/outputs/2025-07-13/10-16-40/checkpoints/openwebtext/any_order/20250713-101641/last.ckpt +) +STEP_SIZES=(128 256 1024 2048 4096) +SAMPLERS=(euler tau-leaping) + +# compute which model, sampler, and step to run +TOTAL_STEPS=${#STEP_SIZES[@]} +TOTAL_SAMPLERS=${#SAMPLERS[@]} +TASK=$SLURM_ARRAY_TASK_ID +MODEL_IDX=$(( TASK / (TOTAL_STEPS * TOTAL_SAMPLERS) )) +REM=$(( TASK % (TOTAL_STEPS * TOTAL_SAMPLERS) )) +SAMPLER_IDX=$(( REM / TOTAL_STEPS )) +STEP_IDX=$(( REM % TOTAL_STEPS )) + +MODEL=${MODELS[MODEL_IDX]} +CKPT=${CKPTS[MODEL_IDX]} +SAMPLER=${SAMPLERS[SAMPLER_IDX]} +STEP=${STEP_SIZES[STEP_IDX]} + +echo "Evaluating samples for model=$MODEL sampler=$SAMPLER step=$STEP (task $TASK)" +# evaluate samples +srun python evaluate_samples.py \ + --input-json "tmp/owt/${SAMPLER}/${MODEL}_generated_samples_${STEP}.json" \ + --batch-size 32 \ + --results-output "/n/netscratch/albergo_lab/Lab/brianlck/interpretable-flow/tmp/owt/${SAMPLER}/${MODEL}_eval_results_${STEP}.json" \ + --length-plot-output "/n/netscratch/albergo_lab/Lab/brianlck/interpretable-flow/tmp/owt/${SAMPLER}/${MODEL}_length_plot_${STEP}.png" \ + --perplexity \ + --eval-mode "sentence" + +srun python evaluate_samples.py \ + --input-json "tmp/owt/${SAMPLER}/${MODEL}_generated_samples_${STEP}.json" \ + --batch-size 32 \ + --results-output "/n/netscratch/albergo_lab/Lab/brianlck/interpretable-flow/tmp/owt/${SAMPLER}/1M_linear/${MODEL}_eval_results_${STEP}.json" \ + --length-plot-output "/n/netscratch/albergo_lab/Lab/brianlck/interpretable-flow/tmp/owt/${SAMPLER}/1M_linear/${MODEL}_length_plot_${STEP}.png" \ + --perplexity \ + --eval-mode "sentence" + +srun python evaluate_samples.py \ + --input-json "tmp/owt/${SAMPLER}/geometric_${MODEL}_generated_samples_${STEP}.json" \ + --batch-size 32 \ + --results-output "/n/netscratch/albergo_lab/Lab/brianlck/interpretable-flow/tmp/owt/${SAMPLER}/geometic_${MODEL}_eval_results_${STEP}.json" \ + --length-plot-output "/n/netscratch/albergo_lab/Lab/brianlck/interpretable-flow/tmp/owt/${SAMPLER}/geometic_${MODEL}_length_plot_${STEP}.png" \ + --perplexity \ + --eval-mode "sentence" + +srun python evaluate_samples.py \ + --input-json "tmp/owt/${SAMPLER}/cosine_${MODEL}_generated_samples_${STEP}.json" \ + --batch-size 32 \ + --results-output "/n/netscratch/albergo_lab/Lab/brianlck/interpretable-flow/tmp/owt/${SAMPLER}/cosine${MODEL}_eval_results_${STEP}.json" \ + --length-plot-output "/n/netscratch/albergo_lab/Lab/brianlck/interpretable-flow/tmp/owt/${SAMPLER}/cosine_${MODEL}_length_plot_${STEP}.png" \ + --perplexity \ + --eval-mode "sentence" diff --git a/FlexMDM/scripts/generate_samples.sh b/FlexMDM/scripts/generate_samples.sh new file mode 100644 index 0000000000000000000000000000000000000000..3f148785c69736e9efc74f69c5c3eeac7009430a --- /dev/null +++ b/FlexMDM/scripts/generate_samples.sh @@ -0,0 +1,69 @@ +#!/bin/bash +#SBATCH --job-name=generate_samples +#SBATCH --account=kempner_albergo_lab +#SBATCH --partition=kempner_requeue +#SBATCH --nodes=1 +#SBATCH --gpus-per-node=1 +#SBATCH --mem=100GB +#SBATCH --constraint h100 +#SBATCH --time=1-00:00:00 +#SBATCH --output=slurm_logs/vlmdm/job-%A_%a.out +#SBATCH --mail-type=END,FAIL +#SBATCH --mail-user=brianlee.lck@gmail.com +#SBATCH --array=15-19 + +source /n/netscratch/albergo_lab/Lab/brianlck/interpretable-flow/.venv/bin/activate + +export CUDA_LAUNCH_BLOCKING=1 +export TORCH_USE_CUDA_DSA=1 +export HF_HOME=/n/netscratch/albergo_lab/Everyone/hf_cache +export HF_HUB_ENABLE_HF_TRANSFER=1 + +# define models, checkpoints, step sizes, and samplers +MODELS=(mdm flow) +CKPTS=( + /n/netscratch/albergo_lab/Lab/brianlck/interpretable-flow/outputs/2025-06-30/18-45-34/checkpoints/openwebtext/mdm/20250630-184537/last.ckpt + /n/netscratch/albergo_lab/Lab/brianlck/interpretable-flow/outputs/2025-08-02/01-29-58/checkpoints/openwebtext/any_order/20250802-012959/last.ckpt +) +STEP_SIZES=(128 256 1024 2048 4096) +SAMPLERS=(euler tau-leaping) +SUBDIR=1M_linear + +# compute which model, sampler, and step to run +TOTAL_STEPS=${#STEP_SIZES[@]} +TOTAL_SAMPLERS=${#SAMPLERS[@]} +TASK=$SLURM_ARRAY_TASK_ID +MODEL_IDX=$(( TASK / (TOTAL_STEPS * TOTAL_SAMPLERS) )) +REM=$(( TASK % (TOTAL_STEPS * TOTAL_SAMPLERS) )) +SAMPLER_IDX=$(( REM / TOTAL_STEPS )) +STEP_IDX=$(( REM % TOTAL_STEPS )) + +MODEL=${MODELS[MODEL_IDX]} +CKPT=${CKPTS[MODEL_IDX]} +SAMPLER=${SAMPLERS[SAMPLER_IDX]} +STEP=${STEP_SIZES[STEP_IDX]} + +echo "Running model=$MODEL sampler=$SAMPLER step=$STEP (task $TASK)" + +# generate samples +# srun python generate_samples.py \ +# --checkpoint_path "${CKPT}" \ +# --total_samples 1024 \ +# --model_type "${MODEL}" \ +# --sampler_type "${SAMPLER}" \ +# --step_size "${STEP}" \ +# -o "tmp/owt/${SAMPLER}/${SUBDIR}/${MODEL}_generated_samples_${STEP}.json" + +# evaluate samples +srun python evaluate_samples.py \ + --input-json "tmp/owt/${SAMPLER}/${SUBDIR}/${MODEL}_generated_samples_${STEP}.json" \ + --batch-size 32 \ + --results-output "/n/netscratch/albergo_lab/Lab/brianlck/interpretable-flow/tmp/owt/${SAMPLER}/${SUBDIR}/${MODEL}_eval_results_${STEP}.json" \ + --length-plot-output "/n/netscratch/albergo_lab/Lab/brianlck/interpretable-flow/tmp/owt/${SAMPLER}/${SUBDIR}/${MODEL}_length_plot_${STEP}.png" \ + --eval-mode "sentence" \ + --mauve \ + --entropy \ + --perplexity \ + --reference-perplexity + + diff --git a/FlexMDM/scripts/generate_samples_single.sh b/FlexMDM/scripts/generate_samples_single.sh new file mode 100644 index 0000000000000000000000000000000000000000..0d4686aafaa80ca49dca8b197967c31acda56113 --- /dev/null +++ b/FlexMDM/scripts/generate_samples_single.sh @@ -0,0 +1,55 @@ +#!/bin/bash +#SBATCH --job-name=generate_samples_single +#SBATCH --account=kempner_albergo_lab +#SBATCH --partition=kempner_requeue +#SBATCH --nodes=1 +#SBATCH --gpus-per-node=1 +#SBATCH --mem=100GB +#SBATCH --constraint h100 +#SBATCH --time=1-00:00:00 +#SBATCH --output=slurm_logs/vlmdm/job-%j.out +#SBATCH --mail-type=END,FAIL +#SBATCH --mail-user=brianlee.lck@gmail.com + +source /n/netscratch/albergo_lab/Lab/brianlck/interpretable-flow/.venv/bin/activate + +export CUDA_LAUNCH_BLOCKING=1 +export TORCH_USE_CUDA_DSA=1 +export HF_HOME=/n/netscratch/albergo_lab/Everyone/hf_cache +export HF_HUB_ENABLE_HF_TRANSFER=1 + +# Configuration parameters - modify these as needed +MODEL="flow" +CKPT="/n/netscratch/albergo_lab/Lab/brianlck/interpretable-flow/outputs/2025-07-29/00-17-21/checkpoints/openwebtext/any_order/20250729-001723/last.ckpt" +SAMPLER="tau-leaping" +STEP=1024 +TOTAL_SAMPLES=1024 +SUBDIR="new_linear" + +echo "Running model=$MODEL sampler=$SAMPLER step=$STEP" + +# Create output directory +mkdir -p "tmp/owt/${SAMPLER}" + +# generate samples +# srun python generate_samples.py \ +# --checkpoint_path "${CKPT}" \ +# --total_samples "${TOTAL_SAMPLES}" \ +# --model_type "${MODEL}" \ +# --sampler_type "${SAMPLER}" \ +# --step_size "${STEP}" \ +# -o "tmp/owt/${SAMPLER}/${SUBDIR}/${MODEL}_generated_samples_${STEP}.json" + +# evaluate samples +srun python evaluate_samples.py \ + --input-json "tmp/owt/${SAMPLER}/${SUBDIR}/${MODEL}_generated_samples_${STEP}.json" \ + --batch-size 32 \ + --results-output "/n/netscratch/albergo_lab/Lab/brianlck/interpretable-flow/tmp/owt/${SAMPLER}/${SUBDIR}/${MODEL}_eval_results_${STEP}.json" \ + --length-plot-output "/n/netscratch/albergo_lab/Lab/brianlck/interpretable-flow/tmp/owt/${SAMPLER}/${SUBDIR}/${MODEL}_length_plot_${STEP}.png" \ + --eval-mode "sentence" \ + --perplexity \ + --entropy \ + --mauve \ + --reference-perplexity + +echo "Job completed successfully" diff --git a/FlexMDM/scripts/mdm.sh b/FlexMDM/scripts/mdm.sh new file mode 100644 index 0000000000000000000000000000000000000000..327c9d9f166adba8d0be15780e9fc844616ed70b --- /dev/null +++ b/FlexMDM/scripts/mdm.sh @@ -0,0 +1,21 @@ +#!/bin/bash +#SBATCH --job-name=mdm_test_run +#SBATCH --account=kempner_sham_lab +#SBATCH --partition=kempner +#SBATCH --nodes=1 +#SBATCH --gpus-per-node=4 +#SBATCH --mem=100GB +#SBATCH --time=1-00:00:00 +#SBATCH --output=slurm_logs/vlmdm/job-%j.out +#SBATCH --mail-type=END,FAIL +#SBATCH --mail-user=jaeyeon_kim@g.harvard.edu + + +# define paths +source ~/.bashrc + +conda deactivate +conda activate jay_vlmdm + + +python train_MDM.py --mask_schedule_type linear --wandb diff --git a/FlexMDM/scripts/owt/eval.sh b/FlexMDM/scripts/owt/eval.sh new file mode 100644 index 0000000000000000000000000000000000000000..15fdf3684224277fc1a927965e9fa66d6bbe74fb --- /dev/null +++ b/FlexMDM/scripts/owt/eval.sh @@ -0,0 +1,66 @@ +#!/bin/bash +#SBATCH --job-name=openwebtext-evaluation +#SBATCH --partition=kempner_requeue +#SBATCH --account=kempner_albergo_lab +#SBATCH --nodes=1 +#SBATCH --gpus-per-node=1 +#SBATCH --cpus-per-task=1 +#SBATCH --ntasks-per-node=1 +#SBATCH --mem=200GB +#SBATCH --time=03:00:00 +#SBATCH -o slurm_logs/openwebtext/job-%j.out +#SBATCH -e slurm_logs/openwebtext/job-%j.err +#SBATCH --constraint h100 +#SBATCH --mail-type=END,FAIL +#SBATCH --mail-user=brianlee.lck@gmail.com +#SBATCH --signal=SIGUSR1@90 + +source /n/netscratch/albergo_lab/Lab/brianlck/interpretable-flow/.venv/bin/activate + +export HF_HOME=/n/netscratch/albergo_lab/Everyone/hf_cache +export HF_HUB_ENABLE_HF_TRANSFER=1 + +# Directory to search for JSON files +SEARCH_DIR="/n/netscratch/albergo_lab/Lab/brianlck/interpretable-flow" + +# Directories to search +DIRS_TO_SEARCH=( + "${SEARCH_DIR}" + "${SEARCH_DIR}/tmp/owt/euler" + "${SEARCH_DIR}/tmp/owt/tau-leaping" +) + +# Loop through each directory +for dir in "${DIRS_TO_SEARCH[@]}"; do + echo "Searching directory: $dir" + + # Find all JSON files matching the pattern and evaluate them + for json_file in ${dir}/*_generated_samples_*.json; do + if [ -f "$json_file" ]; then + echo "Start processing: $json_file" + + # Extract filename without path and extension + filename=$(basename "$json_file" .json) + + # Extract method and number from filename pattern {method}_generated_samples_{number} + if [[ $filename =~ ^(.+)_generated_samples_([0-9]+)$ ]]; then + method="${BASH_REMATCH[1]}" + number="${BASH_REMATCH[2]}" + + # Create unique output filenames + output_plot="${dir}/gpt_chunk_length_plot_${filename}.png" + output_result="${dir}/gpt_chunk_${method}_eval_result_${number}.json" + + srun python evaluate_samples.py \ + --input-json "$json_file" \ + --batch-size 32 \ + --length-plot-output "$output_plot" \ + --results-output "$output_result" \ + --eval-mode "chunk" \ + --model-type "gpt2-xl" + + echo "Finished processing: $json_file" + fi + fi + done +done diff --git a/FlexMDM/scripts/owt/mdm.sh b/FlexMDM/scripts/owt/mdm.sh new file mode 100644 index 0000000000000000000000000000000000000000..1a6a06d8e5ed6f4f6f468509c03f2504c2be9b1b --- /dev/null +++ b/FlexMDM/scripts/owt/mdm.sh @@ -0,0 +1,27 @@ +#!/bin/bash +#SBATCH --job-name=openwebtext-mdm-linear +#SBATCH --partition=kempner_h100 +#SBATCH --account=kempner_albergo_lab +#SBATCH --nodes=4 +#SBATCH --gpus-per-node=4 +#SBATCH --cpus-per-task=4 +#SBATCH --ntasks-per-node=4 +#SBATCH --mem=200GB +#SBATCH --time=3-00:00:00 +#SBATCH -o slurm_logs/openwebtext/job-%j.out +#SBATCH -e slurm_logs/openwebtext/job-%j.err +#SBATCH --mail-type=END,FAIL +#SBATCH --mail-user=brianlee.lck@gmail.com +#SBATCH --signal=SIGUSR1@90 + +source /n/netscratch/albergo_lab/Everyone/brianlck/interpretable-flow/.venv/bin/activate + +export HF_HOME=/n/netscratch/albergo_lab/Everyone/hf_cache +export HF_HUB_ENABLE_HF_TRANSFER=1 + +export NCCL_SOCKET_FAMILY=AF_INET +export MASTER_ADDR=$(scontrol show hostnames $SLURM_NODELIST | head -n 1) +export MASTER_PORT=$(shuf -i 15000-59999 -n 1) +export NODE_RANK=$SLURM_NODEID + +srun python train.py --config-path config/openwebtext --config-name mdm \ No newline at end of file diff --git a/FlexMDM/scripts/owt/var-len.sh b/FlexMDM/scripts/owt/var-len.sh new file mode 100644 index 0000000000000000000000000000000000000000..1169e1a9c5dcf53f936c494ef68e6b41a5fd1f1f --- /dev/null +++ b/FlexMDM/scripts/owt/var-len.sh @@ -0,0 +1,27 @@ +#!/bin/bash +#SBATCH --job-name=openwebtext-linear +#SBATCH --partition=kempner_h100 +#SBATCH --account=kempner_albergo_lab +#SBATCH --nodes=4 +#SBATCH --gpus-per-node=4 +#SBATCH --cpus-per-task=4 +#SBATCH --ntasks-per-node=4 +#SBATCH --mem=200GB +#SBATCH --time=03-00:00:00 +#SBATCH -o slurm_logs/openwebtext/job-%j.out +#SBATCH -e slurm_logs/openwebtext/job-%j.err +#SBATCH --mail-type=END,FAIL +#SBATCH --mail-user=brianlee.lck@gmail.com +#SBATCH --signal=SIGUSR1@90 + +source /n/netscratch/albergo_lab/Lab/brianlck/interpretable-flow/.venv/bin/activate + +export HF_HOME=/n/netscratch/albergo_lab/Everyone/hf_cache +export HF_HUB_ENABLE_HF_TRANSFER=1 + +export NCCL_SOCKET_FAMILY=AF_INET +export MASTER_ADDR=$(scontrol show hostnames $SLURM_NODELIST | head -n 1) +export MASTER_PORT=$(shuf -i 15000-59999 -n 1) +export NODE_RANK=$SLURM_NODEID + +srun python train.py --config-path config/openwebtext --config-name any_order_small diff --git a/FlexMDM/scripts/prepare_data.py b/FlexMDM/scripts/prepare_data.py new file mode 100644 index 0000000000000000000000000000000000000000..ab327070ac62e5e10cf3e2a745067b164c9e2502 --- /dev/null +++ b/FlexMDM/scripts/prepare_data.py @@ -0,0 +1,118 @@ +from datasets import load_dataset +from transformers import GPTNeoXTokenizerFast +from collections import Counter +import matplotlib.pyplot as plt +import numpy as np +import os + +# Parameters +MAX_EXAMPLES = 4_096_000 +LOG_INTERVAL = 1_000_000 +BATCH_SIZE = 1000 +SAVE_DIR = "length_dist_plots-2" + +# Create output directory +os.makedirs(SAVE_DIR, exist_ok=True) + +# Load full dataset (cached) +dataset = load_dataset("mlfoundations/dclm-baseline-1.0", split="train", num_proc=64) +dataset = dataset.select(range(MAX_EXAMPLES)) + +# Load tokenizer +tokenizer = GPTNeoXTokenizerFast.from_pretrained("EleutherAI/gpt-neox-20b") + +# Initialize counter and length list +counter = Counter() +length_list = [] + +# Process in batches +for start in range(0, MAX_EXAMPLES, BATCH_SIZE): + end = min(start + BATCH_SIZE, MAX_EXAMPLES) + batch = dataset[start:end]["text"] + encodings = tokenizer(batch, truncation=False, add_special_tokens=False) + lengths = [len(ids) for ids in encodings["input_ids"]] + counter.update(lengths) + length_list.extend(lengths) + + # Save plot every 1M + if (end % LOG_INTERVAL == 0) or (end == MAX_EXAMPLES): + count_millions = end // 1_000_000 + x, y = zip(*sorted(counter.items())) + + # Compute percentiles for current subset + lengths_np = np.array(length_list) + results = { + p: int(np.percentile(lengths_np, p)) + for p in [10, 20, 30, 40, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 99] + } + + # Plot + plt.figure(figsize=(12, 6)) + plt.bar(x, y, color="skyblue") + plt.yscale("log") + plt.xscale("log") + plt.xlabel("Token Length") + plt.ylabel("Frequency (log scale)") + plt.title(f"Token Length Distribution (Up to {end:,} Examples)") + plt.grid(True, linestyle="--", alpha=0.5) + + # Annotate percentiles + for p in [50, 60, 70, 80, 90, 95, 99]: + val = results[p] + plt.axvline(val, color="red", linestyle="--", linewidth=1.5) + plt.text( + val + 10, + max(y) / 10, + f"{p}%", + rotation=90, + color="red", + fontsize=10, + verticalalignment="center", + ) + + plt.tight_layout() + filename = os.path.join(SAVE_DIR, f"length_dist_{count_millions}M.png") + plt.savefig(filename) + plt.close() + print(f"✅ Saved plot: {filename}") + +# --- Final Percentiles --- +print("\n📊 Computing Final Percentiles...") + +lengths_np = np.array(length_list) +all_percentiles = [0, 1, 5, 10, 25, 50, 75, 90, 95, 99, 100] +final_results = {p: int(np.percentile(lengths_np, p)) for p in all_percentiles} + +print("\n📊 Token Length Percentiles:") +for p in all_percentiles: + print(f" {p:>3}%: {final_results[p]:,} tokens") + +# --- Save final plot with annotations --- +x, y = zip(*sorted(counter.items())) +plt.figure(figsize=(12, 6)) +plt.bar(x, y, width=5, color="skyblue", edgecolor="black") +plt.yscale("log") +plt.xscale("log") +plt.xlabel("Token Length") +plt.ylabel("Frequency (log scale)") +plt.title("Final Token Length Distribution (4.096M Samples)") +plt.grid(True, linestyle="--", alpha=0.5) + +for p in [50, 95, 99]: + val = final_results[p] + plt.axvline(val, color="red", linestyle="--", linewidth=1.5) + plt.text( + val + 10, + max(y) / 10, + f"{p}%", + rotation=90, + color="red", + fontsize=10, + verticalalignment="center", + ) + +plt.tight_layout() +final_path = os.path.join(SAVE_DIR, "length_dist_final_annotated.png") +plt.savefig(final_path) +plt.close() +print(f"\n✅ Final annotated plot saved: {final_path}") diff --git a/FlexMDM/scripts/prepare_data_openwebtext.py b/FlexMDM/scripts/prepare_data_openwebtext.py new file mode 100644 index 0000000000000000000000000000000000000000..baedcdc7616bd7b3b2e49e7e52d96e302276576a --- /dev/null +++ b/FlexMDM/scripts/prepare_data_openwebtext.py @@ -0,0 +1,117 @@ +from datasets import load_dataset +from transformers import GPT2TokenizerFast +from collections import Counter +import matplotlib.pyplot as plt +import numpy as np +import os + +# Parameters +LOG_INTERVAL = 1_000_000 +BATCH_SIZE = 1000 +SAVE_DIR = "length_dist_plots-2" + +# Create output directory +os.makedirs(SAVE_DIR, exist_ok=True) + +# Load full dataset (cached) +dataset = load_dataset("openwebtext", split="train", num_proc=64) +MAX_EXAMPLES = len(dataset) + +# Load tokenizer +tokenizer = GPT2TokenizerFast.from_pretrained("gpt2") + +# Initialize counter and length list +counter = Counter() +length_list = [] + +# Process in batches +for start in range(0, MAX_EXAMPLES, BATCH_SIZE): + end = min(start + BATCH_SIZE, MAX_EXAMPLES) + batch = dataset[start:end]["text"] + encodings = tokenizer(batch, truncation=False, add_special_tokens=False) + lengths = [len(ids) for ids in encodings["input_ids"]] + counter.update(lengths) + length_list.extend(lengths) + + # Save plot every 1M + if (end % LOG_INTERVAL == 0) or (end == MAX_EXAMPLES): + count_millions = end // 1_000_000 + x, y = zip(*sorted(counter.items())) + + # Compute percentiles for current subset + lengths_np = np.array(length_list) + results = { + p: int(np.percentile(lengths_np, p)) + for p in [10, 20, 30, 40, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 99] + } + + # Plot + plt.figure(figsize=(12, 6)) + plt.bar(x, y, color="skyblue") + plt.yscale("log") + plt.xscale("log") + plt.xlabel("Token Length") + plt.ylabel("Frequency (log scale)") + plt.title(f"Token Length Distribution (Up to {end:,} Examples)") + plt.grid(True, linestyle="--", alpha=0.5) + + # Annotate percentiles + for p in [50, 60, 70, 80, 90, 95, 99]: + val = results[p] + plt.axvline(val, color="red", linestyle="--", linewidth=1.5) + plt.text( + val + 10, + max(y) / 10, + f"{p}%", + rotation=90, + color="red", + fontsize=10, + verticalalignment="center", + ) + + plt.tight_layout() + filename = os.path.join(SAVE_DIR, f"length_dist_{count_millions}M.png") + plt.savefig(filename) + plt.close() + print(f"✅ Saved plot: {filename}") + +# --- Final Percentiles --- +print("\n📊 Computing Final Percentiles...") + +lengths_np = np.array(length_list) +all_percentiles = [0, 1, 5, 10, 25, 50, 75, 90, 95, 99, 100] +final_results = {p: int(np.percentile(lengths_np, p)) for p in all_percentiles} + +print("\n📊 Token Length Percentiles:") +for p in all_percentiles: + print(f" {p:>3}%: {final_results[p]:,} tokens") + +# --- Save final plot with annotations --- +x, y = zip(*sorted(counter.items())) +plt.figure(figsize=(12, 6)) +plt.bar(x, y, width=5, color="skyblue", edgecolor="black") +plt.yscale("log") +plt.xscale("log") +plt.xlabel("Token Length") +plt.ylabel("Frequency (log scale)") +plt.title("Final Token Length Distribution (4.096M Samples)") +plt.grid(True, linestyle="--", alpha=0.5) + +for p in [50, 95, 99]: + val = final_results[p] + plt.axvline(val, color="red", linestyle="--", linewidth=1.5) + plt.text( + val + 10, + max(y) / 10, + f"{p}%", + rotation=90, + color="red", + fontsize=10, + verticalalignment="center", + ) + +plt.tight_layout() +final_path = os.path.join(SAVE_DIR, "length_dist_final_annotated.png") +plt.savefig(final_path) +plt.close() +print(f"\n✅ Final annotated plot saved: {final_path}") diff --git a/FlexMDM/scripts/prepare_dataset.sh b/FlexMDM/scripts/prepare_dataset.sh new file mode 100644 index 0000000000000000000000000000000000000000..2c8c2d03bf427684089b3c9d143c51be0d94bdb0 --- /dev/null +++ b/FlexMDM/scripts/prepare_dataset.sh @@ -0,0 +1,15 @@ +#!/bin/bash +#SBATCH --job-name=load_dclm +#SBATCH --account=albergo_lab +#SBATCH --partition=sapphire +#SBATCH --nodes=1 +#SBATCH --mem=500GB +#SBATCH --time=1-00:00:00 +#SBATCH --cpus-per-task=64 # Match num_proc +#SBATCH --tmp=100G # Local scratch space +#SBATCH --output=slurm_logs/vlmdm/job-%j.out + +export HF_HOME=/n/netscratch/albergo_lab/Everyone/hf_cache +export HF_HUB_ENABLE_HF_TRANSFER=1 + +python scripts/prepare_data.py \ No newline at end of file diff --git a/FlexMDM/scripts/vlmdm_geo.sh b/FlexMDM/scripts/vlmdm_geo.sh new file mode 100644 index 0000000000000000000000000000000000000000..a07d778b3d605e892f9b97eff7e0a4c2de4a4ade --- /dev/null +++ b/FlexMDM/scripts/vlmdm_geo.sh @@ -0,0 +1,21 @@ +#!/bin/bash +#SBATCH --job-name=vlmdm_test_run_geo +#SBATCH --account=kempner_sham_lab +#SBATCH --partition=kempner_h100 +#SBATCH --nodes=1 +#SBATCH --gpus-per-node=4 +#SBATCH --mem=100GB +#SBATCH --time=1-00:00:00 +#SBATCH --output=slurm_logs/vlmdm/job-%j.out +#SBATCH --mail-type=END,FAIL +#SBATCH --mail-user=jaeyeon_kim@g.harvard.edu + + +# define paths +source ~/.bashrc + +conda deactivate +conda activate jay_vlmdm + + +python train.py --mask_schedule_type geometric --unmask_loss_type ce --len_predict_type expectation --len_loss_scheduler --wandb \ No newline at end of file diff --git a/FlexMDM/scripts/vlmdm_linear.sh b/FlexMDM/scripts/vlmdm_linear.sh new file mode 100644 index 0000000000000000000000000000000000000000..48a4946e1134758962e51194166a06c7dc1f19e8 --- /dev/null +++ b/FlexMDM/scripts/vlmdm_linear.sh @@ -0,0 +1,20 @@ +#!/bin/bash +#SBATCH --job-name=vlmdm_test_run_linear +#SBATCH --account=kempner_sham_lab +#SBATCH --partition=kempner_h100 +#SBATCH --nodes=1 +#SBATCH --gpus-per-node=4 +#SBATCH --mem=100GB +#SBATCH --time=1-00:00:00 +#SBATCH --output=slurm_logs/vlmdm/job-%j.out +#SBATCH --mail-type=END,FAIL +#SBATCH --mail-user=jaeyeon_kim@g.harvard.edu + + +# define paths +source ~/.bashrc + +conda deactivate +conda activate jay_vlmdm + +python train.py --insertion_schedule_type linear --len_loss_scheduler --wandb diff --git a/FlexMDM/scripts/wikitext2_any_order.sbatch b/FlexMDM/scripts/wikitext2_any_order.sbatch new file mode 100644 index 0000000000000000000000000000000000000000..db4dfde8e0dfd0244d443bf1244fb9c2aabaca08 --- /dev/null +++ b/FlexMDM/scripts/wikitext2_any_order.sbatch @@ -0,0 +1,22 @@ +#!/bin/bash +#SBATCH --job-name=wikitext2_any_order +#SBATCH --partition=kempner_h100 +#SBATCH --account=kempner_albergo_lab +#SBATCH --partition=kempner_h100 +#SBATCH --nodes=2 +#SBATCH --gpus-per-node=4 +#SBATCH --ntasks-per-node=4 +#SBATCH --mem=100GB +#SBATCH --time=1-00:00:00 +#SBATCH --output=slurm_logs/wikitext2/job-%j.out + +export NCCL_SOCKET_FAMILY=AF_INET +export MASTER_ADDR=$(scontrol show hostnames $SLURM_NODELIST | head -n 1) +export MASTER_PORT=$(shuf -i 15000-59999 -n 1) +export NODE_RANK=$SLURM_NODEID +export NCCL_DEBUG=INFO +export NCCL_DEBUG_SUBSYS=ALL +export TORCH_DISTRIBUTED_DEBUG=DETAIL + + +srun python train.py --config-path config/wikitext2 --config-name any_order \ No newline at end of file diff --git a/FlexMDM/train.py b/FlexMDM/train.py new file mode 100644 index 0000000000000000000000000000000000000000..1257da6d6c50d938791b35739e82632a02346791 --- /dev/null +++ b/FlexMDM/train.py @@ -0,0 +1,144 @@ +import torch +import pytorch_lightning as pl +from pytorch_lightning.loggers import WandbLogger +from pytorch_lightning.callbacks import ModelCheckpoint +import os +import hydra +from omegaconf import DictConfig, OmegaConf +from datetime import datetime +import wandb +import data +from lightning_modules import ( + MaskedDiffusionModule, + AutoregressiveModule, + AnyOrderInsertionFlowModule, +) +from pytorch_lightning.utilities import rank_zero_only + + +torch.set_printoptions(threshold=10_000) +torch.set_float32_matmul_precision("high") + + +def train(config: DictConfig): + # set the random seed + pl.seed_everything(42) + torch.manual_seed(42) + + if "wandb" in config and rank_zero_only.rank == 0: + init_kwargs = dict( + project="interpretable-flow", + entity=config.wandb.entity, + config=OmegaConf.to_container(config, resolve=True), + name=config.wandb.name, + ) + # resume wandb run if we're resuming from a checkpoint + if "resume_path" in config.training: + init_kwargs["resume"] = "allow" + wandb.init(**init_kwargs) + wandb_logger = WandbLogger( + project=wandb.run.project, + name=wandb.run.name, + log_model=True, + ) + else: + wandb_logger = None + + time_string = datetime.now().strftime("%Y%m%d-%H%M%S") + config.training.checkpoint_dir = os.path.join( + config.training.checkpoint_dir, time_string + ) + + # Create checkpoint directory + os.makedirs(config.training.checkpoint_dir, exist_ok=True) + + dataset_bundle = data.setup_data_and_update_config(config) + + match config.trainer: + case "mdm": + module = MaskedDiffusionModule(config) + case "autoregressive": + module = AutoregressiveModule(config) + case "any-order-flow": + module = AnyOrderInsertionFlowModule(config) + case _: + raise NotImplementedError(f"Trainer {config.trainer} is not supported") + + # Initialize trainer + + # Configure trainer arguments + trainer_kwargs = dict( + num_nodes=config.training.nodes, + accelerator="gpu", + devices=config.training.devices, + strategy="ddp", + accumulate_grad_batches=( + config.training.batch_size + // ( + config.training.per_gpu_batch_size + * config.training.nodes + * config.training.devices + ) + ), + log_every_n_steps=10, + enable_checkpointing=True, + default_root_dir=config.training.checkpoint_dir, + gradient_clip_val=1.0, + ) + # Only one of max_steps or max_epochs will be used + if config.training.max_steps is not None: + trainer_kwargs["max_steps"] = config.training.max_steps + elif config.training.num_epochs is not None: + trainer_kwargs["max_epochs"] = config.training.num_epochs + config.training.max_steps = config.training.num_epochs * len( + dataset_bundle.train_loader + ) + else: + raise ValueError( + "Either max_steps or num_epochs must be specified in the config" + ) + + if config.training.warmup_steps is None: + config.training.warmup_steps = int(config.training.max_steps * 0.01) + + # Add ModelCheckpoint callback to save the checkpoint when validation loss is at a new low + checkpoint_callback = ModelCheckpoint( + monitor="val_loss", + mode="min", + save_top_k=config.training.save_top_k, + save_last=True, + filename="epoch-{epoch:02d}-val_loss-{val_loss:.4f}", + dirpath=config.training.checkpoint_dir, + every_n_train_steps=10000, + # every_n_epochs=config.training.save_every_n_epochs, + ) + trainer_kwargs["callbacks"] = [checkpoint_callback] + + if wandb_logger is not None: + trainer_kwargs["logger"] = wandb_logger + + trainer = pl.Trainer(**trainer_kwargs) + + # Train the model + ckpt_path = None + if "resume_path" in config.training: + ckpt_path = config.training.resume_path + + trainer.fit( + module, + train_dataloaders=dataset_bundle.train_loader, + val_dataloaders=dataset_bundle.val_loader, + ckpt_path=ckpt_path, + ) + + if "wandb" in config: + wandb.finish() + + +@hydra.main(config_path=".", config_name="config") +def main(cfg: DictConfig): + train(cfg) + + +if __name__ == "__main__": + main() diff --git a/FlexMDM/utils/state_dict_utils.py b/FlexMDM/utils/state_dict_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/FlexMDM/uv.lock b/FlexMDM/uv.lock new file mode 100644 index 0000000000000000000000000000000000000000..0016812de448060c430525cb424eadbfed8d5bb0 --- /dev/null +++ b/FlexMDM/uv.lock @@ -0,0 +1,3657 @@ +version = 1 +revision = 1 +requires-python = ">=3.11" +resolution-markers = [ + "python_full_version >= '3.12' and sys_platform == 'linux'", + "python_full_version >= '3.12' and sys_platform != 'linux'", + "python_full_version < '3.12' and sys_platform == 'linux'", + "python_full_version < '3.12' and sys_platform != 'linux'", +] + +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265 }, +] + +[[package]] +name = "aiohttp" +version = "3.11.16" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f1/d9/1c4721d143e14af753f2bf5e3b681883e1f24b592c0482df6fa6e33597fa/aiohttp-3.11.16.tar.gz", hash = "sha256:16f8a2c9538c14a557b4d309ed4d0a7c60f0253e8ed7b6c9a2859a7582f8b1b8", size = 7676826 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/98/be30539cd84260d9f3ea1936d50445e25aa6029a4cb9707f3b64cfd710f7/aiohttp-3.11.16-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8cb0688a8d81c63d716e867d59a9ccc389e97ac7037ebef904c2b89334407180", size = 708664 }, + { url = "https://files.pythonhosted.org/packages/e6/27/d51116ce18bdfdea7a2244b55ad38d7b01a4298af55765eed7e8431f013d/aiohttp-3.11.16-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0ad1fb47da60ae1ddfb316f0ff16d1f3b8e844d1a1e154641928ea0583d486ed", size = 468953 }, + { url = "https://files.pythonhosted.org/packages/34/23/eedf80ec42865ea5355b46265a2433134138eff9a4fea17e1348530fa4ae/aiohttp-3.11.16-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:df7db76400bf46ec6a0a73192b14c8295bdb9812053f4fe53f4e789f3ea66bbb", size = 456065 }, + { url = "https://files.pythonhosted.org/packages/36/23/4a5b1ef6cff994936bf96d981dd817b487d9db755457a0d1c2939920d620/aiohttp-3.11.16-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc3a145479a76ad0ed646434d09216d33d08eef0d8c9a11f5ae5cdc37caa3540", size = 1687976 }, + { url = "https://files.pythonhosted.org/packages/d0/5d/c7474b4c3069bb35276d54c82997dff4f7575e4b73f0a7b1b08a39ece1eb/aiohttp-3.11.16-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d007aa39a52d62373bd23428ba4a2546eed0e7643d7bf2e41ddcefd54519842c", size = 1752711 }, + { url = "https://files.pythonhosted.org/packages/64/4c/ee416987b6729558f2eb1b727c60196580aafdb141e83bd78bb031d1c000/aiohttp-3.11.16-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6ddd90d9fb4b501c97a4458f1c1720e42432c26cb76d28177c5b5ad4e332601", size = 1791305 }, + { url = "https://files.pythonhosted.org/packages/58/28/3e1e1884070b95f1f69c473a1995852a6f8516670bb1c29d6cb2dbb73e1c/aiohttp-3.11.16-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a2f451849e6b39e5c226803dcacfa9c7133e9825dcefd2f4e837a2ec5a3bb98", size = 1674499 }, + { url = "https://files.pythonhosted.org/packages/ad/55/a032b32fa80a662d25d9eb170ed1e2c2be239304ca114ec66c89dc40f37f/aiohttp-3.11.16-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8df6612df74409080575dca38a5237282865408016e65636a76a2eb9348c2567", size = 1622313 }, + { url = "https://files.pythonhosted.org/packages/b1/df/ca775605f72abbda4e4746e793c408c84373ca2c6ce7a106a09f853f1e89/aiohttp-3.11.16-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:78e6e23b954644737e385befa0deb20233e2dfddf95dd11e9db752bdd2a294d3", size = 1658274 }, + { url = "https://files.pythonhosted.org/packages/cc/6c/21c45b66124df5b4b0ab638271ecd8c6402b702977120cb4d5be6408e15d/aiohttp-3.11.16-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:696ef00e8a1f0cec5e30640e64eca75d8e777933d1438f4facc9c0cdf288a810", size = 1666704 }, + { url = "https://files.pythonhosted.org/packages/1d/e2/7d92adc03e3458edd18a21da2575ab84e58f16b1672ae98529e4eeee45ab/aiohttp-3.11.16-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e3538bc9fe1b902bef51372462e3d7c96fce2b566642512138a480b7adc9d508", size = 1652815 }, + { url = "https://files.pythonhosted.org/packages/3a/52/7549573cd654ad651e3c5786ec3946d8f0ee379023e22deb503ff856b16c/aiohttp-3.11.16-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:3ab3367bb7f61ad18793fea2ef71f2d181c528c87948638366bf1de26e239183", size = 1735669 }, + { url = "https://files.pythonhosted.org/packages/d5/54/dcd24a23c7a5a2922123e07a296a5f79ea87ce605f531be068415c326de6/aiohttp-3.11.16-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:56a3443aca82abda0e07be2e1ecb76a050714faf2be84256dae291182ba59049", size = 1760422 }, + { url = "https://files.pythonhosted.org/packages/a7/53/87327fe982fa310944e1450e97bf7b2a28015263771931372a1dfe682c58/aiohttp-3.11.16-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:61c721764e41af907c9d16b6daa05a458f066015abd35923051be8705108ed17", size = 1694457 }, + { url = "https://files.pythonhosted.org/packages/ce/6d/c5ccf41059267bcf89853d3db9d8d217dacf0a04f4086cb6bf278323011f/aiohttp-3.11.16-cp311-cp311-win32.whl", hash = "sha256:3e061b09f6fa42997cf627307f220315e313ece74907d35776ec4373ed718b86", size = 416817 }, + { url = "https://files.pythonhosted.org/packages/e7/dd/01f6fe028e054ef4f909c9d63e3a2399e77021bb2e1bb51d56ca8b543989/aiohttp-3.11.16-cp311-cp311-win_amd64.whl", hash = "sha256:745f1ed5e2c687baefc3c5e7b4304e91bf3e2f32834d07baaee243e349624b24", size = 442986 }, + { url = "https://files.pythonhosted.org/packages/db/38/100d01cbc60553743baf0fba658cb125f8ad674a8a771f765cdc155a890d/aiohttp-3.11.16-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:911a6e91d08bb2c72938bc17f0a2d97864c531536b7832abee6429d5296e5b27", size = 704881 }, + { url = "https://files.pythonhosted.org/packages/21/ed/b4102bb6245e36591209e29f03fe87e7956e54cb604ee12e20f7eb47f994/aiohttp-3.11.16-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6ac13b71761e49d5f9e4d05d33683bbafef753e876e8e5a7ef26e937dd766713", size = 464564 }, + { url = "https://files.pythonhosted.org/packages/3b/e1/a9ab6c47b62ecee080eeb33acd5352b40ecad08fb2d0779bcc6739271745/aiohttp-3.11.16-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fd36c119c5d6551bce374fcb5c19269638f8d09862445f85a5a48596fd59f4bb", size = 456548 }, + { url = "https://files.pythonhosted.org/packages/80/ad/216c6f71bdff2becce6c8776f0aa32cb0fa5d83008d13b49c3208d2e4016/aiohttp-3.11.16-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d489d9778522fbd0f8d6a5c6e48e3514f11be81cb0a5954bdda06f7e1594b321", size = 1691749 }, + { url = "https://files.pythonhosted.org/packages/bd/ea/7df7bcd3f4e734301605f686ffc87993f2d51b7acb6bcc9b980af223f297/aiohttp-3.11.16-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69a2cbd61788d26f8f1e626e188044834f37f6ae3f937bd9f08b65fc9d7e514e", size = 1736874 }, + { url = "https://files.pythonhosted.org/packages/51/41/c7724b9c87a29b7cfd1202ec6446bae8524a751473d25e2ff438bc9a02bf/aiohttp-3.11.16-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd464ba806e27ee24a91362ba3621bfc39dbbb8b79f2e1340201615197370f7c", size = 1786885 }, + { url = "https://files.pythonhosted.org/packages/86/b3/f61f8492fa6569fa87927ad35a40c159408862f7e8e70deaaead349e2fba/aiohttp-3.11.16-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ce63ae04719513dd2651202352a2beb9f67f55cb8490c40f056cea3c5c355ce", size = 1698059 }, + { url = "https://files.pythonhosted.org/packages/ce/be/7097cf860a9ce8bbb0e8960704e12869e111abcd3fbd245153373079ccec/aiohttp-3.11.16-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09b00dd520d88eac9d1768439a59ab3d145065c91a8fab97f900d1b5f802895e", size = 1626527 }, + { url = "https://files.pythonhosted.org/packages/1d/1d/aaa841c340e8c143a8d53a1f644c2a2961c58cfa26e7b398d6bf75cf5d23/aiohttp-3.11.16-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7f6428fee52d2bcf96a8aa7b62095b190ee341ab0e6b1bcf50c615d7966fd45b", size = 1644036 }, + { url = "https://files.pythonhosted.org/packages/2c/88/59d870f76e9345e2b149f158074e78db457985c2b4da713038d9da3020a8/aiohttp-3.11.16-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:13ceac2c5cdcc3f64b9015710221ddf81c900c5febc505dbd8f810e770011540", size = 1685270 }, + { url = "https://files.pythonhosted.org/packages/2b/b1/c6686948d4c79c3745595efc469a9f8a43cab3c7efc0b5991be65d9e8cb8/aiohttp-3.11.16-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fadbb8f1d4140825069db3fedbbb843290fd5f5bc0a5dbd7eaf81d91bf1b003b", size = 1650852 }, + { url = "https://files.pythonhosted.org/packages/fe/94/3e42a6916fd3441721941e0f1b8438e1ce2a4c49af0e28e0d3c950c9b3c9/aiohttp-3.11.16-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6a792ce34b999fbe04a7a71a90c74f10c57ae4c51f65461a411faa70e154154e", size = 1704481 }, + { url = "https://files.pythonhosted.org/packages/b1/6d/6ab5854ff59b27075c7a8c610597d2b6c38945f9a1284ee8758bc3720ff6/aiohttp-3.11.16-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:f4065145bf69de124accdd17ea5f4dc770da0a6a6e440c53f6e0a8c27b3e635c", size = 1735370 }, + { url = "https://files.pythonhosted.org/packages/73/2a/08a68eec3c99a6659067d271d7553e4d490a0828d588e1daa3970dc2b771/aiohttp-3.11.16-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fa73e8c2656a3653ae6c307b3f4e878a21f87859a9afab228280ddccd7369d71", size = 1697619 }, + { url = "https://files.pythonhosted.org/packages/61/d5/fea8dbbfb0cd68fbb56f0ae913270a79422d9a41da442a624febf72d2aaf/aiohttp-3.11.16-cp312-cp312-win32.whl", hash = "sha256:f244b8e541f414664889e2c87cac11a07b918cb4b540c36f7ada7bfa76571ea2", size = 411710 }, + { url = "https://files.pythonhosted.org/packages/33/fb/41cde15fbe51365024550bf77b95a4fc84ef41365705c946da0421f0e1e0/aiohttp-3.11.16-cp312-cp312-win_amd64.whl", hash = "sha256:23a15727fbfccab973343b6d1b7181bfb0b4aa7ae280f36fd2f90f5476805682", size = 438012 }, + { url = "https://files.pythonhosted.org/packages/52/52/7c712b2d9fb4d5e5fd6d12f9ab76e52baddfee71e3c8203ca7a7559d7f51/aiohttp-3.11.16-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a3814760a1a700f3cfd2f977249f1032301d0a12c92aba74605cfa6ce9f78489", size = 698005 }, + { url = "https://files.pythonhosted.org/packages/51/3e/61057814f7247666d43ac538abcd6335b022869ade2602dab9bf33f607d2/aiohttp-3.11.16-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9b751a6306f330801665ae69270a8a3993654a85569b3469662efaad6cf5cc50", size = 461106 }, + { url = "https://files.pythonhosted.org/packages/4f/85/6b79fb0ea6e913d596d5b949edc2402b20803f51b1a59e1bbc5bb7ba7569/aiohttp-3.11.16-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ad497f38a0d6c329cb621774788583ee12321863cd4bd9feee1effd60f2ad133", size = 453394 }, + { url = "https://files.pythonhosted.org/packages/4b/04/e1bb3fcfbd2c26753932c759593a32299aff8625eaa0bf8ff7d9c0c34a36/aiohttp-3.11.16-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca37057625693d097543bd88076ceebeb248291df9d6ca8481349efc0b05dcd0", size = 1666643 }, + { url = "https://files.pythonhosted.org/packages/0e/27/97bc0fdd1f439b8f060beb3ba8fb47b908dc170280090801158381ad7942/aiohttp-3.11.16-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a5abcbba9f4b463a45c8ca8b7720891200658f6f46894f79517e6cd11f3405ca", size = 1721948 }, + { url = "https://files.pythonhosted.org/packages/2c/4f/bc4c5119e75c05ef15c5670ef1563bbe25d4ed4893b76c57b0184d815e8b/aiohttp-3.11.16-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f420bfe862fb357a6d76f2065447ef6f484bc489292ac91e29bc65d2d7a2c84d", size = 1774454 }, + { url = "https://files.pythonhosted.org/packages/73/5b/54b42b2150bb26fdf795464aa55ceb1a49c85f84e98e6896d211eabc6670/aiohttp-3.11.16-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58ede86453a6cf2d6ce40ef0ca15481677a66950e73b0a788917916f7e35a0bb", size = 1677785 }, + { url = "https://files.pythonhosted.org/packages/10/ee/a0fe68916d3f82eae199b8535624cf07a9c0a0958c7a76e56dd21140487a/aiohttp-3.11.16-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fdec0213244c39973674ca2a7f5435bf74369e7d4e104d6c7473c81c9bcc8c4", size = 1608456 }, + { url = "https://files.pythonhosted.org/packages/8b/48/83afd779242b7cf7e1ceed2ff624a86d3221e17798061cf9a79e0b246077/aiohttp-3.11.16-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:72b1b03fb4655c1960403c131740755ec19c5898c82abd3961c364c2afd59fe7", size = 1622424 }, + { url = "https://files.pythonhosted.org/packages/6f/27/452f1d5fca1f516f9f731539b7f5faa9e9d3bf8a3a6c3cd7c4b031f20cbd/aiohttp-3.11.16-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:780df0d837276276226a1ff803f8d0fa5f8996c479aeef52eb040179f3156cbd", size = 1660943 }, + { url = "https://files.pythonhosted.org/packages/d6/e1/5c7d63143b8d00c83b958b9e78e7048c4a69903c760c1e329bf02bac57a1/aiohttp-3.11.16-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ecdb8173e6c7aa09eee342ac62e193e6904923bd232e76b4157ac0bfa670609f", size = 1622797 }, + { url = "https://files.pythonhosted.org/packages/46/9e/2ac29cca2746ee8e449e73cd2fcb3d454467393ec03a269d50e49af743f1/aiohttp-3.11.16-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a6db7458ab89c7d80bc1f4e930cc9df6edee2200127cfa6f6e080cf619eddfbd", size = 1687162 }, + { url = "https://files.pythonhosted.org/packages/ad/6b/eaa6768e02edebaf37d77f4ffb74dd55f5cbcbb6a0dbf798ccec7b0ac23b/aiohttp-3.11.16-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2540ddc83cc724b13d1838026f6a5ad178510953302a49e6d647f6e1de82bc34", size = 1718518 }, + { url = "https://files.pythonhosted.org/packages/e5/18/dda87cbad29472a51fa058d6d8257dfce168289adaeb358b86bd93af3b20/aiohttp-3.11.16-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3b4e6db8dc4879015b9955778cfb9881897339c8fab7b3676f8433f849425913", size = 1675254 }, + { url = "https://files.pythonhosted.org/packages/32/d9/d2fb08c614df401d92c12fcbc60e6e879608d5e8909ef75c5ad8d4ad8aa7/aiohttp-3.11.16-cp313-cp313-win32.whl", hash = "sha256:493910ceb2764f792db4dc6e8e4b375dae1b08f72e18e8f10f18b34ca17d0979", size = 410698 }, + { url = "https://files.pythonhosted.org/packages/ce/ed/853e36d5a33c24544cfa46585895547de152dfef0b5c79fa675f6e4b7b87/aiohttp-3.11.16-cp313-cp313-win_amd64.whl", hash = "sha256:42864e70a248f5f6a49fdaf417d9bc62d6e4d8ee9695b24c5916cb4bb666c802", size = 436395 }, +] + +[[package]] +name = "aiosignal" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ba/b5/6d55e80f6d8a08ce22b982eafa278d823b541c925f11ee774b0b9c43473d/aiosignal-1.3.2.tar.gz", hash = "sha256:a8c255c66fafb1e499c9351d0bf32ff2d8a0321595ebac3b93713656d2436f54", size = 19424 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/6a/bc7e17a3e87a2985d3e8f4da4cd0f481060eb78fb08596c42be62c90a4d9/aiosignal-1.3.2-py2.py3-none-any.whl", hash = "sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5", size = 7597 }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, +] + +[[package]] +name = "antlr4-python3-runtime" +version = "4.9.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3e/38/7859ff46355f76f8d19459005ca000b6e7012f2f1ca597746cbcd1fbfe5e/antlr4-python3-runtime-4.9.3.tar.gz", hash = "sha256:f224469b4168294902bb1efa80a8bf7855f24c99aef99cbefc1bcd3cce77881b", size = 117034 } + +[[package]] +name = "anyio" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "sniffio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916 }, +] + +[[package]] +name = "appnope" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/35/5d/752690df9ef5b76e169e68d6a129fa6d08a7100ca7f754c89495db3c6019/appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee", size = 4170 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321 }, +] + +[[package]] +name = "argon2-cffi" +version = "23.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "argon2-cffi-bindings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/31/fa/57ec2c6d16ecd2ba0cf15f3c7d1c3c2e7b5fcb83555ff56d7ab10888ec8f/argon2_cffi-23.1.0.tar.gz", hash = "sha256:879c3e79a2729ce768ebb7d36d4609e3a78a4ca2ec3a9f12286ca057e3d0db08", size = 42798 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/6a/e8a041599e78b6b3752da48000b14c8d1e8a04ded09c88c714ba047f34f5/argon2_cffi-23.1.0-py3-none-any.whl", hash = "sha256:c670642b78ba29641818ab2e68bd4e6a78ba53b7eff7b4c3815ae16abf91c7ea", size = 15124 }, +] + +[[package]] +name = "argon2-cffi-bindings" +version = "21.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/e9/184b8ccce6683b0aa2fbb7ba5683ea4b9c5763f1356347f1312c32e3c66e/argon2-cffi-bindings-21.2.0.tar.gz", hash = "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3", size = 1779911 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/13/838ce2620025e9666aa8f686431f67a29052241692a3dd1ae9d3692a89d3/argon2_cffi_bindings-21.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367", size = 29658 }, + { url = "https://files.pythonhosted.org/packages/b3/02/f7f7bb6b6af6031edb11037639c697b912e1dea2db94d436e681aea2f495/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d", size = 80583 }, + { url = "https://files.pythonhosted.org/packages/ec/f7/378254e6dd7ae6f31fe40c8649eea7d4832a42243acaf0f1fff9083b2bed/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae", size = 86168 }, + { url = "https://files.pythonhosted.org/packages/74/f6/4a34a37a98311ed73bb80efe422fed95f2ac25a4cacc5ae1d7ae6a144505/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c", size = 82709 }, + { url = "https://files.pythonhosted.org/packages/74/2b/73d767bfdaab25484f7e7901379d5f8793cccbb86c6e0cbc4c1b96f63896/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86", size = 83613 }, + { url = "https://files.pythonhosted.org/packages/4f/fd/37f86deef67ff57c76f137a67181949c2d408077e2e3dd70c6c42912c9bf/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_i686.whl", hash = "sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f", size = 84583 }, + { url = "https://files.pythonhosted.org/packages/6f/52/5a60085a3dae8fded8327a4f564223029f5f54b0cb0455a31131b5363a01/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e", size = 88475 }, + { url = "https://files.pythonhosted.org/packages/8b/95/143cd64feb24a15fa4b189a3e1e7efbaeeb00f39a51e99b26fc62fbacabd/argon2_cffi_bindings-21.2.0-cp36-abi3-win32.whl", hash = "sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082", size = 27698 }, + { url = "https://files.pythonhosted.org/packages/37/2c/e34e47c7dee97ba6f01a6203e0383e15b60fb85d78ac9a15cd066f6fe28b/argon2_cffi_bindings-21.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f", size = 30817 }, + { url = "https://files.pythonhosted.org/packages/5a/e4/bf8034d25edaa495da3c8a3405627d2e35758e44ff6eaa7948092646fdcc/argon2_cffi_bindings-21.2.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93", size = 53104 }, +] + +[[package]] +name = "arrow" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, + { name = "types-python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2e/00/0f6e8fcdb23ea632c866620cc872729ff43ed91d284c866b515c6342b173/arrow-1.3.0.tar.gz", hash = "sha256:d4540617648cb5f895730f1ad8c82a65f2dad0166f57b75f3ca54759c4d67a85", size = 131960 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl", hash = "sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80", size = 66419 }, +] + +[[package]] +name = "asttokens" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4a/e7/82da0a03e7ba5141f05cce0d302e6eed121ae055e0456ca228bf693984bc/asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7", size = 61978 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2", size = 26918 }, +] + +[[package]] +name = "async-lru" +version = "2.0.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/4d/71ec4d3939dc755264f680f6c2b4906423a304c3d18e96853f0a595dfe97/async_lru-2.0.5.tar.gz", hash = "sha256:481d52ccdd27275f42c43a928b4a50c3bfb2d67af4e78b170e3e0bb39c66e5bb", size = 10380 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-py3-none-any.whl", hash = "sha256:ab95404d8d2605310d345932697371a5f40def0487c03d6d0ad9138de52c9943", size = 6069 }, +] + +[[package]] +name = "attrs" +version = "25.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815 }, +] + +[[package]] +name = "babel" +version = "2.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537 }, +] + +[[package]] +name = "beautifulsoup4" +version = "4.13.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f0/3c/adaf39ce1fb4afdd21b611e3d530b183bb7759c9b673d60db0e347fd4439/beautifulsoup4-4.13.3.tar.gz", hash = "sha256:1bd32405dacc920b42b83ba01644747ed77456a65760e285fbc47633ceddaf8b", size = 619516 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/49/6abb616eb3cbab6a7cca303dc02fdf3836de2e0b834bf966a7f5271a34d8/beautifulsoup4-4.13.3-py3-none-any.whl", hash = "sha256:99045d7d3f08f91f0d656bc9b7efbae189426cd913d830294a15eefa0ea4df16", size = 186015 }, +] + +[[package]] +name = "black" +version = "25.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "mypy-extensions" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/94/49/26a7b0f3f35da4b5a65f081943b7bcd22d7002f5f0fb8098ec1ff21cb6ef/black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666", size = 649449 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/4f/87f596aca05c3ce5b94b8663dbfe242a12843caaa82dd3f85f1ffdc3f177/black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0", size = 1614372 }, + { url = "https://files.pythonhosted.org/packages/e7/d0/2c34c36190b741c59c901e56ab7f6e54dad8df05a6272a9747ecef7c6036/black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299", size = 1442865 }, + { url = "https://files.pythonhosted.org/packages/21/d4/7518c72262468430ead45cf22bd86c883a6448b9eb43672765d69a8f1248/black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096", size = 1749699 }, + { url = "https://files.pythonhosted.org/packages/58/db/4f5beb989b547f79096e035c4981ceb36ac2b552d0ac5f2620e941501c99/black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2", size = 1428028 }, + { url = "https://files.pythonhosted.org/packages/83/71/3fe4741df7adf015ad8dfa082dd36c94ca86bb21f25608eb247b4afb15b2/black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b", size = 1650988 }, + { url = "https://files.pythonhosted.org/packages/13/f3/89aac8a83d73937ccd39bbe8fc6ac8860c11cfa0af5b1c96d081facac844/black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc", size = 1453985 }, + { url = "https://files.pythonhosted.org/packages/6f/22/b99efca33f1f3a1d2552c714b1e1b5ae92efac6c43e790ad539a163d1754/black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f", size = 1783816 }, + { url = "https://files.pythonhosted.org/packages/18/7e/a27c3ad3822b6f2e0e00d63d58ff6299a99a5b3aee69fa77cd4b0076b261/black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba", size = 1440860 }, + { url = "https://files.pythonhosted.org/packages/98/87/0edf98916640efa5d0696e1abb0a8357b52e69e82322628f25bf14d263d1/black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f", size = 1650673 }, + { url = "https://files.pythonhosted.org/packages/52/e5/f7bf17207cf87fa6e9b676576749c6b6ed0d70f179a3d812c997870291c3/black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3", size = 1453190 }, + { url = "https://files.pythonhosted.org/packages/e3/ee/adda3d46d4a9120772fae6de454c8495603c37c4c3b9c60f25b1ab6401fe/black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171", size = 1782926 }, + { url = "https://files.pythonhosted.org/packages/cc/64/94eb5f45dcb997d2082f097a3944cfc7fe87e071907f677e80788a2d7b7a/black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18", size = 1442613 }, + { url = "https://files.pythonhosted.org/packages/09/71/54e999902aed72baf26bca0d50781b01838251a462612966e9fc4891eadd/black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717", size = 207646 }, +] + +[[package]] +name = "bleach" +version = "6.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "webencodings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/9a/0e33f5054c54d349ea62c277191c020c2d6ef1d65ab2cb1993f91ec846d1/bleach-6.2.0.tar.gz", hash = "sha256:123e894118b8a599fd80d3ec1a6d4cc7ce4e5882b1317a7e1ba69b56e95f991f", size = 203083 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/55/96142937f66150805c25c4d0f31ee4132fd33497753400734f9dfdcbdc66/bleach-6.2.0-py3-none-any.whl", hash = "sha256:117d9c6097a7c3d22fd578fcd8d35ff1e125df6736f554da4e432fdd63f31e5e", size = 163406 }, +] + +[package.optional-dependencies] +css = [ + { name = "tinycss2" }, +] + +[[package]] +name = "certifi" +version = "2025.1.31" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 }, +] + +[[package]] +name = "cffi" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264 }, + { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651 }, + { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259 }, + { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200 }, + { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235 }, + { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721 }, + { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242 }, + { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999 }, + { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242 }, + { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604 }, + { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727 }, + { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400 }, + { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178 }, + { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840 }, + { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803 }, + { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850 }, + { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729 }, + { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256 }, + { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424 }, + { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568 }, + { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736 }, + { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448 }, + { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976 }, + { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989 }, + { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802 }, + { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792 }, + { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893 }, + { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810 }, + { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200 }, + { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447 }, + { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358 }, + { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469 }, + { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475 }, + { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009 }, +] + +[[package]] +name = "cfgv" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249 }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/80/41ef5d5a7935d2d3a773e3eaebf0a9350542f2cab4eac59a7a4741fbbbbe/charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", size = 194995 }, + { url = "https://files.pythonhosted.org/packages/7a/28/0b9fefa7b8b080ec492110af6d88aa3dea91c464b17d53474b6e9ba5d2c5/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", size = 139471 }, + { url = "https://files.pythonhosted.org/packages/71/64/d24ab1a997efb06402e3fc07317e94da358e2585165930d9d59ad45fcae2/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", size = 149831 }, + { url = "https://files.pythonhosted.org/packages/37/ed/be39e5258e198655240db5e19e0b11379163ad7070962d6b0c87ed2c4d39/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd", size = 142335 }, + { url = "https://files.pythonhosted.org/packages/88/83/489e9504711fa05d8dde1574996408026bdbdbd938f23be67deebb5eca92/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00", size = 143862 }, + { url = "https://files.pythonhosted.org/packages/c6/c7/32da20821cf387b759ad24627a9aca289d2822de929b8a41b6241767b461/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12", size = 145673 }, + { url = "https://files.pythonhosted.org/packages/68/85/f4288e96039abdd5aeb5c546fa20a37b50da71b5cf01e75e87f16cd43304/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77", size = 140211 }, + { url = "https://files.pythonhosted.org/packages/28/a3/a42e70d03cbdabc18997baf4f0227c73591a08041c149e710045c281f97b/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146", size = 148039 }, + { url = "https://files.pythonhosted.org/packages/85/e4/65699e8ab3014ecbe6f5c71d1a55d810fb716bbfd74f6283d5c2aa87febf/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd", size = 151939 }, + { url = "https://files.pythonhosted.org/packages/b1/82/8e9fe624cc5374193de6860aba3ea8070f584c8565ee77c168ec13274bd2/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6", size = 149075 }, + { url = "https://files.pythonhosted.org/packages/3d/7b/82865ba54c765560c8433f65e8acb9217cb839a9e32b42af4aa8e945870f/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8", size = 144340 }, + { url = "https://files.pythonhosted.org/packages/b5/b6/9674a4b7d4d99a0d2df9b215da766ee682718f88055751e1e5e753c82db0/charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b", size = 95205 }, + { url = "https://files.pythonhosted.org/packages/1e/ab/45b180e175de4402dcf7547e4fb617283bae54ce35c27930a6f35b6bef15/charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76", size = 102441 }, + { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105 }, + { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404 }, + { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423 }, + { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184 }, + { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268 }, + { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601 }, + { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098 }, + { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520 }, + { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852 }, + { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488 }, + { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192 }, + { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550 }, + { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785 }, + { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 }, + { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 }, + { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 }, + { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 }, + { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 }, + { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 }, + { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 }, + { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 }, + { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 }, + { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 }, + { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 }, + { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 }, + { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 }, + { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 }, +] + +[[package]] +name = "click" +version = "8.1.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "comm" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/a8/fb783cb0abe2b5fded9f55e5703015cdf1c9c85b3669087c538dd15a6a86/comm-0.2.2.tar.gz", hash = "sha256:3fd7a84065306e07bea1773df6eb8282de51ba82f77c72f9c85716ab11fe980e", size = 6210 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/75/49e5bfe642f71f272236b5b2d2691cf915a7283cc0ceda56357b61daa538/comm-0.2.2-py3-none-any.whl", hash = "sha256:e6fb86cb70ff661ee8c9c14e7d36d6de3b4066f1441be4063df9c5009f0a64d3", size = 7180 }, +] + +[[package]] +name = "contourpy" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/54/eb9bfc647b19f2009dd5c7f5ec51c4e6ca831725f1aea7a993034f483147/contourpy-1.3.2.tar.gz", hash = "sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54", size = 13466130 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/b9/ede788a0b56fc5b071639d06c33cb893f68b1178938f3425debebe2dab78/contourpy-1.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a37a2fb93d4df3fc4c0e363ea4d16f83195fc09c891bc8ce072b9d084853445", size = 269636 }, + { url = "https://files.pythonhosted.org/packages/e6/75/3469f011d64b8bbfa04f709bfc23e1dd71be54d05b1b083be9f5b22750d1/contourpy-1.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b7cd50c38f500bbcc9b6a46643a40e0913673f869315d8e70de0438817cb7773", size = 254636 }, + { url = "https://files.pythonhosted.org/packages/8d/2f/95adb8dae08ce0ebca4fd8e7ad653159565d9739128b2d5977806656fcd2/contourpy-1.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6658ccc7251a4433eebd89ed2672c2ed96fba367fd25ca9512aa92a4b46c4f1", size = 313053 }, + { url = "https://files.pythonhosted.org/packages/c3/a6/8ccf97a50f31adfa36917707fe39c9a0cbc24b3bbb58185577f119736cc9/contourpy-1.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:70771a461aaeb335df14deb6c97439973d253ae70660ca085eec25241137ef43", size = 352985 }, + { url = "https://files.pythonhosted.org/packages/1d/b6/7925ab9b77386143f39d9c3243fdd101621b4532eb126743201160ffa7e6/contourpy-1.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65a887a6e8c4cd0897507d814b14c54a8c2e2aa4ac9f7686292f9769fcf9a6ab", size = 323750 }, + { url = "https://files.pythonhosted.org/packages/c2/f3/20c5d1ef4f4748e52d60771b8560cf00b69d5c6368b5c2e9311bcfa2a08b/contourpy-1.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3859783aefa2b8355697f16642695a5b9792e7a46ab86da1118a4a23a51a33d7", size = 326246 }, + { url = "https://files.pythonhosted.org/packages/8c/e5/9dae809e7e0b2d9d70c52b3d24cba134dd3dad979eb3e5e71f5df22ed1f5/contourpy-1.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:eab0f6db315fa4d70f1d8ab514e527f0366ec021ff853d7ed6a2d33605cf4b83", size = 1308728 }, + { url = "https://files.pythonhosted.org/packages/e2/4a/0058ba34aeea35c0b442ae61a4f4d4ca84d6df8f91309bc2d43bb8dd248f/contourpy-1.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d91a3ccc7fea94ca0acab82ceb77f396d50a1f67412efe4c526f5d20264e6ecd", size = 1375762 }, + { url = "https://files.pythonhosted.org/packages/09/33/7174bdfc8b7767ef2c08ed81244762d93d5c579336fc0b51ca57b33d1b80/contourpy-1.3.2-cp311-cp311-win32.whl", hash = "sha256:1c48188778d4d2f3d48e4643fb15d8608b1d01e4b4d6b0548d9b336c28fc9b6f", size = 178196 }, + { url = "https://files.pythonhosted.org/packages/5e/fe/4029038b4e1c4485cef18e480b0e2cd2d755448bb071eb9977caac80b77b/contourpy-1.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:5ebac872ba09cb8f2131c46b8739a7ff71de28a24c869bcad554477eb089a878", size = 222017 }, + { url = "https://files.pythonhosted.org/packages/34/f7/44785876384eff370c251d58fd65f6ad7f39adce4a093c934d4a67a7c6b6/contourpy-1.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4caf2bcd2969402bf77edc4cb6034c7dd7c0803213b3523f111eb7460a51b8d2", size = 271580 }, + { url = "https://files.pythonhosted.org/packages/93/3b/0004767622a9826ea3d95f0e9d98cd8729015768075d61f9fea8eeca42a8/contourpy-1.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:82199cb78276249796419fe36b7386bd8d2cc3f28b3bc19fe2454fe2e26c4c15", size = 255530 }, + { url = "https://files.pythonhosted.org/packages/e7/bb/7bd49e1f4fa805772d9fd130e0d375554ebc771ed7172f48dfcd4ca61549/contourpy-1.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:106fab697af11456fcba3e352ad50effe493a90f893fca6c2ca5c033820cea92", size = 307688 }, + { url = "https://files.pythonhosted.org/packages/fc/97/e1d5dbbfa170725ef78357a9a0edc996b09ae4af170927ba8ce977e60a5f/contourpy-1.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d14f12932a8d620e307f715857107b1d1845cc44fdb5da2bc8e850f5ceba9f87", size = 347331 }, + { url = "https://files.pythonhosted.org/packages/6f/66/e69e6e904f5ecf6901be3dd16e7e54d41b6ec6ae3405a535286d4418ffb4/contourpy-1.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:532fd26e715560721bb0d5fc7610fce279b3699b018600ab999d1be895b09415", size = 318963 }, + { url = "https://files.pythonhosted.org/packages/a8/32/b8a1c8965e4f72482ff2d1ac2cd670ce0b542f203c8e1d34e7c3e6925da7/contourpy-1.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b383144cf2d2c29f01a1e8170f50dacf0eac02d64139dcd709a8ac4eb3cfe", size = 323681 }, + { url = "https://files.pythonhosted.org/packages/30/c6/12a7e6811d08757c7162a541ca4c5c6a34c0f4e98ef2b338791093518e40/contourpy-1.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c49f73e61f1f774650a55d221803b101d966ca0c5a2d6d5e4320ec3997489441", size = 1308674 }, + { url = "https://files.pythonhosted.org/packages/2a/8a/bebe5a3f68b484d3a2b8ffaf84704b3e343ef1addea528132ef148e22b3b/contourpy-1.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3d80b2c0300583228ac98d0a927a1ba6a2ba6b8a742463c564f1d419ee5b211e", size = 1380480 }, + { url = "https://files.pythonhosted.org/packages/34/db/fcd325f19b5978fb509a7d55e06d99f5f856294c1991097534360b307cf1/contourpy-1.3.2-cp312-cp312-win32.whl", hash = "sha256:90df94c89a91b7362e1142cbee7568f86514412ab8a2c0d0fca72d7e91b62912", size = 178489 }, + { url = "https://files.pythonhosted.org/packages/01/c8/fadd0b92ffa7b5eb5949bf340a63a4a496a6930a6c37a7ba0f12acb076d6/contourpy-1.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:8c942a01d9163e2e5cfb05cb66110121b8d07ad438a17f9e766317bcb62abf73", size = 223042 }, + { url = "https://files.pythonhosted.org/packages/2e/61/5673f7e364b31e4e7ef6f61a4b5121c5f170f941895912f773d95270f3a2/contourpy-1.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:de39db2604ae755316cb5967728f4bea92685884b1e767b7c24e983ef5f771cb", size = 271630 }, + { url = "https://files.pythonhosted.org/packages/ff/66/a40badddd1223822c95798c55292844b7e871e50f6bfd9f158cb25e0bd39/contourpy-1.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3f9e896f447c5c8618f1edb2bafa9a4030f22a575ec418ad70611450720b5b08", size = 255670 }, + { url = "https://files.pythonhosted.org/packages/1e/c7/cf9fdee8200805c9bc3b148f49cb9482a4e3ea2719e772602a425c9b09f8/contourpy-1.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71e2bd4a1c4188f5c2b8d274da78faab884b59df20df63c34f74aa1813c4427c", size = 306694 }, + { url = "https://files.pythonhosted.org/packages/dd/e7/ccb9bec80e1ba121efbffad7f38021021cda5be87532ec16fd96533bb2e0/contourpy-1.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de425af81b6cea33101ae95ece1f696af39446db9682a0b56daaa48cfc29f38f", size = 345986 }, + { url = "https://files.pythonhosted.org/packages/dc/49/ca13bb2da90391fa4219fdb23b078d6065ada886658ac7818e5441448b78/contourpy-1.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:977e98a0e0480d3fe292246417239d2d45435904afd6d7332d8455981c408b85", size = 318060 }, + { url = "https://files.pythonhosted.org/packages/c8/65/5245ce8c548a8422236c13ffcdcdada6a2a812c361e9e0c70548bb40b661/contourpy-1.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:434f0adf84911c924519d2b08fc10491dd282b20bdd3fa8f60fd816ea0b48841", size = 322747 }, + { url = "https://files.pythonhosted.org/packages/72/30/669b8eb48e0a01c660ead3752a25b44fdb2e5ebc13a55782f639170772f9/contourpy-1.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c66c4906cdbc50e9cba65978823e6e00b45682eb09adbb78c9775b74eb222422", size = 1308895 }, + { url = "https://files.pythonhosted.org/packages/05/5a/b569f4250decee6e8d54498be7bdf29021a4c256e77fe8138c8319ef8eb3/contourpy-1.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8b7fc0cd78ba2f4695fd0a6ad81a19e7e3ab825c31b577f384aa9d7817dc3bef", size = 1379098 }, + { url = "https://files.pythonhosted.org/packages/19/ba/b227c3886d120e60e41b28740ac3617b2f2b971b9f601c835661194579f1/contourpy-1.3.2-cp313-cp313-win32.whl", hash = "sha256:15ce6ab60957ca74cff444fe66d9045c1fd3e92c8936894ebd1f3eef2fff075f", size = 178535 }, + { url = "https://files.pythonhosted.org/packages/12/6e/2fed56cd47ca739b43e892707ae9a13790a486a3173be063681ca67d2262/contourpy-1.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e1578f7eafce927b168752ed7e22646dad6cd9bca673c60bff55889fa236ebf9", size = 223096 }, + { url = "https://files.pythonhosted.org/packages/54/4c/e76fe2a03014a7c767d79ea35c86a747e9325537a8b7627e0e5b3ba266b4/contourpy-1.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0475b1f6604896bc7c53bb070e355e9321e1bc0d381735421a2d2068ec56531f", size = 285090 }, + { url = "https://files.pythonhosted.org/packages/7b/e2/5aba47debd55d668e00baf9651b721e7733975dc9fc27264a62b0dd26eb8/contourpy-1.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c85bb486e9be652314bb5b9e2e3b0d1b2e643d5eec4992c0fbe8ac71775da739", size = 268643 }, + { url = "https://files.pythonhosted.org/packages/a1/37/cd45f1f051fe6230f751cc5cdd2728bb3a203f5619510ef11e732109593c/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:745b57db7758f3ffc05a10254edd3182a2a83402a89c00957a8e8a22f5582823", size = 310443 }, + { url = "https://files.pythonhosted.org/packages/8b/a2/36ea6140c306c9ff6dd38e3bcec80b3b018474ef4d17eb68ceecd26675f4/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:970e9173dbd7eba9b4e01aab19215a48ee5dd3f43cef736eebde064a171f89a5", size = 349865 }, + { url = "https://files.pythonhosted.org/packages/95/b7/2fc76bc539693180488f7b6cc518da7acbbb9e3b931fd9280504128bf956/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6c4639a9c22230276b7bffb6a850dfc8258a2521305e1faefe804d006b2e532", size = 321162 }, + { url = "https://files.pythonhosted.org/packages/f4/10/76d4f778458b0aa83f96e59d65ece72a060bacb20cfbee46cf6cd5ceba41/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc829960f34ba36aad4302e78eabf3ef16a3a100863f0d4eeddf30e8a485a03b", size = 327355 }, + { url = "https://files.pythonhosted.org/packages/43/a3/10cf483ea683f9f8ab096c24bad3cce20e0d1dd9a4baa0e2093c1c962d9d/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d32530b534e986374fc19eaa77fcb87e8a99e5431499949b828312bdcd20ac52", size = 1307935 }, + { url = "https://files.pythonhosted.org/packages/78/73/69dd9a024444489e22d86108e7b913f3528f56cfc312b5c5727a44188471/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e298e7e70cf4eb179cc1077be1c725b5fd131ebc81181bf0c03525c8abc297fd", size = 1372168 }, + { url = "https://files.pythonhosted.org/packages/0f/1b/96d586ccf1b1a9d2004dd519b25fbf104a11589abfd05484ff12199cca21/contourpy-1.3.2-cp313-cp313t-win32.whl", hash = "sha256:d0e589ae0d55204991450bb5c23f571c64fe43adaa53f93fc902a84c96f52fe1", size = 189550 }, + { url = "https://files.pythonhosted.org/packages/b0/e6/6000d0094e8a5e32ad62591c8609e269febb6e4db83a1c75ff8868b42731/contourpy-1.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:78e9253c3de756b3f6a5174d024c4835acd59eb3f8e2ca13e775dbffe1558f69", size = 238214 }, + { url = "https://files.pythonhosted.org/packages/ff/c0/91f1215d0d9f9f343e4773ba6c9b89e8c0cc7a64a6263f21139da639d848/contourpy-1.3.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5f5964cdad279256c084b69c3f412b7801e15356b16efa9d78aa974041903da0", size = 266807 }, + { url = "https://files.pythonhosted.org/packages/d4/79/6be7e90c955c0487e7712660d6cead01fa17bff98e0ea275737cc2bc8e71/contourpy-1.3.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49b65a95d642d4efa8f64ba12558fcb83407e58a2dfba9d796d77b63ccfcaff5", size = 318729 }, + { url = "https://files.pythonhosted.org/packages/87/68/7f46fb537958e87427d98a4074bcde4b67a70b04900cfc5ce29bc2f556c1/contourpy-1.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8c5acb8dddb0752bf252e01a3035b21443158910ac16a3b0d20e7fed7d534ce5", size = 221791 }, +] + +[[package]] +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321 }, +] + +[[package]] +name = "datasets" +version = "3.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dill" }, + { name = "filelock" }, + { name = "fsspec", extra = ["http"] }, + { name = "huggingface-hub" }, + { name = "multiprocess" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "pyarrow" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "tqdm" }, + { name = "xxhash" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1a/89/d3d6fef58a488f8569c82fd293ab7cbd4250244d67f425dcae64c63800ea/datasets-3.6.0.tar.gz", hash = "sha256:1b2bf43b19776e2787e181cfd329cb0ca1a358ea014780c3581e0f276375e041", size = 569336 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/34/a08b0ee99715eaba118cbe19a71f7b5e2425c2718ef96007c325944a1152/datasets-3.6.0-py3-none-any.whl", hash = "sha256:25000c4a2c0873a710df127d08a202a06eab7bf42441a6bc278b499c2f72cd1b", size = 491546 }, +] + +[[package]] +name = "debugpy" +version = "1.8.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/51/d4/f35f539e11c9344652f362c22413ec5078f677ac71229dc9b4f6f85ccaa3/debugpy-1.8.13.tar.gz", hash = "sha256:837e7bef95bdefba426ae38b9a94821ebdc5bea55627879cd48165c90b9e50ce", size = 1641193 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/90/dd2fcad8364f0964f476537481985198ce6e879760281ad1cec289f1aa71/debugpy-1.8.13-cp311-cp311-macosx_14_0_universal2.whl", hash = "sha256:eee02b2ed52a563126c97bf04194af48f2fe1f68bb522a312b05935798e922ff", size = 2174802 }, + { url = "https://files.pythonhosted.org/packages/5c/c9/06ff65f15eb30dbdafd45d1575770b842ce3869ad5580a77f4e5590f1be7/debugpy-1.8.13-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4caca674206e97c85c034c1efab4483f33971d4e02e73081265ecb612af65377", size = 3133620 }, + { url = "https://files.pythonhosted.org/packages/3b/49/798a4092bde16a4650f17ac5f2301d4d37e1972d65462fb25c80a83b4790/debugpy-1.8.13-cp311-cp311-win32.whl", hash = "sha256:7d9a05efc6973b5aaf076d779cf3a6bbb1199e059a17738a2aa9d27a53bcc888", size = 5104764 }, + { url = "https://files.pythonhosted.org/packages/cd/d5/3684d7561c8ba2797305cf8259619acccb8d6ebe2117bb33a6897c235eee/debugpy-1.8.13-cp311-cp311-win_amd64.whl", hash = "sha256:62f9b4a861c256f37e163ada8cf5a81f4c8d5148fc17ee31fb46813bd658cdcc", size = 5129670 }, + { url = "https://files.pythonhosted.org/packages/79/ad/dff929b6b5403feaab0af0e5bb460fd723f9c62538b718a9af819b8fff20/debugpy-1.8.13-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:2b8de94c5c78aa0d0ed79023eb27c7c56a64c68217d881bee2ffbcb13951d0c1", size = 2501004 }, + { url = "https://files.pythonhosted.org/packages/d6/4f/b7d42e6679f0bb525888c278b0c0d2b6dff26ed42795230bb46eaae4f9b3/debugpy-1.8.13-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:887d54276cefbe7290a754424b077e41efa405a3e07122d8897de54709dbe522", size = 4222346 }, + { url = "https://files.pythonhosted.org/packages/ec/18/d9b3e88e85d41f68f77235112adc31012a784e45a3fcdbb039777d570a0f/debugpy-1.8.13-cp312-cp312-win32.whl", hash = "sha256:3872ce5453b17837ef47fb9f3edc25085ff998ce63543f45ba7af41e7f7d370f", size = 5226639 }, + { url = "https://files.pythonhosted.org/packages/c9/f7/0df18a4f530ed3cc06f0060f548efe9e3316102101e311739d906f5650be/debugpy-1.8.13-cp312-cp312-win_amd64.whl", hash = "sha256:63ca7670563c320503fea26ac688988d9d6b9c6a12abc8a8cf2e7dd8e5f6b6ea", size = 5268735 }, + { url = "https://files.pythonhosted.org/packages/b1/db/ae7cd645c1826aae557cebccbc448f0cc9a818d364efb88f8d80e7a03f41/debugpy-1.8.13-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:31abc9618be4edad0b3e3a85277bc9ab51a2d9f708ead0d99ffb5bb750e18503", size = 2485416 }, + { url = "https://files.pythonhosted.org/packages/ec/ed/db4b10ff3b5bb30fe41d9e86444a08bb6448e4d8265e7768450b8408dd36/debugpy-1.8.13-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0bd87557f97bced5513a74088af0b84982b6ccb2e254b9312e29e8a5c4270eb", size = 4218784 }, + { url = "https://files.pythonhosted.org/packages/82/82/ed81852a8d94086f51664d032d83c7f87cd2b087c6ea70dabec7c1ba813d/debugpy-1.8.13-cp313-cp313-win32.whl", hash = "sha256:5268ae7fdca75f526d04465931cb0bd24577477ff50e8bb03dab90983f4ebd02", size = 5226270 }, + { url = "https://files.pythonhosted.org/packages/15/63/aa92fb341a78ec40f1c414ec7a7885c2ee17032eee00d12cee0cdc502af4/debugpy-1.8.13-cp313-cp313-win_amd64.whl", hash = "sha256:79ce4ed40966c4c1631d0131606b055a5a2f8e430e3f7bf8fd3744b09943e8e8", size = 5268621 }, + { url = "https://files.pythonhosted.org/packages/37/4f/0b65410a08b6452bfd3f7ed6f3610f1a31fb127f46836e82d31797065dcb/debugpy-1.8.13-py2.py3-none-any.whl", hash = "sha256:d4ba115cdd0e3a70942bd562adba9ec8c651fe69ddde2298a1be296fc331906f", size = 5229306 }, +] + +[[package]] +name = "decorator" +version = "5.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190 }, +] + +[[package]] +name = "defusedxml" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604 }, +] + +[[package]] +name = "dill" +version = "0.3.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/17/4d/ac7ffa80c69ea1df30a8aa11b3578692a5118e7cd1aa157e3ef73b092d15/dill-0.3.8.tar.gz", hash = "sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca", size = 184847 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/7a/cef76fd8438a42f96db64ddaa85280485a9c395e7df3db8158cfec1eee34/dill-0.3.8-py3-none-any.whl", hash = "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7", size = 116252 }, +] + +[[package]] +name = "distlib" +version = "0.3.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/dd/1bec4c5ddb504ca60fc29472f3d27e8d4da1257a854e1d96742f15c1d02d/distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403", size = 613923 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973 }, +] + +[[package]] +name = "einops" +version = "0.8.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/81/df4fbe24dff8ba3934af99044188e20a98ed441ad17a274539b74e82e126/einops-0.8.1.tar.gz", hash = "sha256:de5d960a7a761225532e0f1959e5315ebeafc0cd43394732f103ca44b9837e84", size = 54805 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/62/9773de14fe6c45c23649e98b83231fffd7b9892b6cf863251dc2afa73643/einops-0.8.1-py3-none-any.whl", hash = "sha256:919387eb55330f5757c6bea9165c5ff5cfe63a642682ea788a6d472576d81737", size = 64359 }, +] + +[[package]] +name = "executing" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/91/50/a9d80c47ff289c611ff12e63f7c5d13942c65d68125160cefd768c73e6e4/executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755", size = 978693 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/8f/c4d9bafc34ad7ad5d8dc16dd1347ee0e507a52c3adb6bfa8887e1c6a26ba/executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa", size = 26702 }, +] + +[[package]] +name = "faiss-cpu" +version = "1.11.0.post1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5c/f4/7c2136f4660ca504266cc08b38df2aa1db14fea93393b82e099ff34d7290/faiss_cpu-1.11.0.post1.tar.gz", hash = "sha256:06b1ea9ddec9e4d9a41c8ef7478d493b08d770e9a89475056e963081eed757d1", size = 70543 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/20/9c7b72089fc00da380d66af2025f28f8665b7e5034573f81a10408837096/faiss_cpu-1.11.0.post1-cp311-cp311-macosx_13_0_x86_64.whl", hash = "sha256:2c8c384e65cc1b118d2903d9f3a27cd35f6c45337696fc0437f71e05f732dbc0", size = 7886449 }, + { url = "https://files.pythonhosted.org/packages/74/45/6b21bebea3e13f5e2b07741c6e5bda0b8ad07e852b3b68e4ae8e7ba53ab5/faiss_cpu-1.11.0.post1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:36af46945274ed14751b788673125a8a4900408e4837a92371b0cad5708619ea", size = 3308146 }, + { url = "https://files.pythonhosted.org/packages/b9/d6/2ff9ee33e63bd37a2d38eda7da051322cb652dd04dd73d560500f266b201/faiss_cpu-1.11.0.post1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b15412b22a05865433aecfdebf7664b9565bd49b600d23a0a27c74a5526893e", size = 3778592 }, + { url = "https://files.pythonhosted.org/packages/84/30/e06cfcedf4664907f39a93f21988149f05ae7fef62e988abb9e99940beeb/faiss_cpu-1.11.0.post1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:81c169ea74213b2c055b8240befe7e9b42a1f3d97cda5238b3b401035ce1a18b", size = 31295285 }, + { url = "https://files.pythonhosted.org/packages/71/7d/9cd6ac869ec062c79ef1dc62ff62e2c22b7572bf15a9454af2fdc7dc98a0/faiss_cpu-1.11.0.post1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0794eb035c6075e931996cf2b2703fbb3f47c8c34bc2d727819ddc3e5e486a31", size = 9700900 }, + { url = "https://files.pythonhosted.org/packages/3e/96/f0159b274331db9ae6fbc85531e8ec6f69c83b28c24d16a555437af5da35/faiss_cpu-1.11.0.post1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:18d2221014813dc9a4236e47f9c4097a71273fbf17c3fe66243e724e2018a67a", size = 24018792 }, + { url = "https://files.pythonhosted.org/packages/61/84/69dbb244cf592be8532f7a91577d765a6b663275599e636190d9745b3cc5/faiss_cpu-1.11.0.post1-cp311-cp311-win_amd64.whl", hash = "sha256:3ce8a8984a7dcc689fd192c69a476ecd0b2611c61f96fe0799ff432aa73ff79c", size = 14880665 }, + { url = "https://files.pythonhosted.org/packages/a3/c4/1873ced6e44f07cfaade55e60860f84443fa94a263ec6b355cb6ae026ca4/faiss_cpu-1.11.0.post1-cp311-cp311-win_arm64.whl", hash = "sha256:8384e05afb7c7968e93b81566759f862e744c0667b175086efb3d8b20949b39f", size = 7849518 }, + { url = "https://files.pythonhosted.org/packages/30/1e/9980758efa55b4e7a5d6df1ae17c9ddbe5a636bfbf7d22d47c67f7a530f4/faiss_cpu-1.11.0.post1-cp312-cp312-macosx_13_0_x86_64.whl", hash = "sha256:68f6ce2d9c510a5765af2f5711bd76c2c37bd598af747f3300224bdccf45378c", size = 7913676 }, + { url = "https://files.pythonhosted.org/packages/05/d1/bd785887085faa02916c52320527b8bb54288835b0a3138df89a0e323cc8/faiss_cpu-1.11.0.post1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:b940c530a8236cc0b9fd9d6e87b3d70b9c6c216bc2baf2649356c908902e52c9", size = 3313952 }, + { url = "https://files.pythonhosted.org/packages/89/13/d62ee83c5a0db24e9c4fc0a446949f9c8feca18659f4c17caca6c3d02867/faiss_cpu-1.11.0.post1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fafae1dcbcba3856a0bb82ffb0c3cae5922bdd6566fdd3b7feb2425cf4fca247", size = 3785328 }, + { url = "https://files.pythonhosted.org/packages/db/a9/acfdd5bd63eff99188d0587fa6de4c30092ce952a1c7229e2fd5c84499d4/faiss_cpu-1.11.0.post1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5d1262702c19aba2d23144b73f4b5730ca988c1f4e43ecec87edf25171cafe3d", size = 31287778 }, + { url = "https://files.pythonhosted.org/packages/88/96/195aecb139db223824a6b2faf647fbe622732659c100cdeca172679cc621/faiss_cpu-1.11.0.post1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:925feb69c06bfcc7f28869c99ab172f123e4b9d97a7e1353316fcc2748696f5b", size = 9714469 }, + { url = "https://files.pythonhosted.org/packages/ca/0c/483d5233c41f753da6710e7026c0f7963649f6ecd1877d63c88cb204c8dc/faiss_cpu-1.11.0.post1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:00a837581b675f099c80c8c46908648dcf944a8992dd21e3887c61c6b110fe5f", size = 24012806 }, + { url = "https://files.pythonhosted.org/packages/1c/17/4384518de0c58f49e4483c6dfdd1bc54540c9d0d71ccfcc87f6b52adfcb9/faiss_cpu-1.11.0.post1-cp312-cp312-win_amd64.whl", hash = "sha256:8bbaef5b56d1b0c01357ee6449d464ea4e52732fdb53a40bb5b9d77923af905f", size = 14882869 }, + { url = "https://files.pythonhosted.org/packages/56/64/ec3823d4703fa704c5e8821a5990fd0485e024d80d813231df0c65b3e18f/faiss_cpu-1.11.0.post1-cp312-cp312-win_arm64.whl", hash = "sha256:57f85dbefe590f8399a95c07e839ee64373cfcc6db5dd35232a41137e3deefeb", size = 7852194 }, + { url = "https://files.pythonhosted.org/packages/ef/c2/28c147fec80609b6ce8578df27d7fafe02d97726df2d261c446176e6ceda/faiss_cpu-1.11.0.post1-cp313-cp313-macosx_13_0_x86_64.whl", hash = "sha256:caedaddfbfe365e3f1a57d5151cf94ea7b73c0e4789caf68eae05e0e10ca9fbf", size = 7913678 }, + { url = "https://files.pythonhosted.org/packages/ff/71/7b06a5294e1d597f721016c6286a0c6e9912ed235d5e5d3600d4fd100ba8/faiss_cpu-1.11.0.post1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:202d11f1d973224ca0bde13e7ee8b862b6de74287e626f9f8820b360e6253d12", size = 3313956 }, + { url = "https://files.pythonhosted.org/packages/ad/15/ae1db1c42c8bef2cfc27b9d5a032b7723aafcc9420c656c19a7eaafd717b/faiss_cpu-1.11.0.post1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f6086e25ef680301350d6db72db7315e3531582cf896a7ee3f26295b1da73c44", size = 3785332 }, + { url = "https://files.pythonhosted.org/packages/41/0d/4538dfccb6e28fdfafd536b6f9c565ca6f5495272ae0c3f872259b29afc8/faiss_cpu-1.11.0.post1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b93131842996efbbf76f07dba1775d3a5f355f74b9ba34334f1149aef046b37f", size = 31287781 }, + { url = "https://files.pythonhosted.org/packages/13/e5/82e3cf427f11380aae54706168974724409fdf9a8caa0894d2c1f454c627/faiss_cpu-1.11.0.post1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f26e3e93f537b2e1633212a1b0a7dab74d77825366ed575ca434dac2fa14cea6", size = 9714472 }, + { url = "https://files.pythonhosted.org/packages/b4/f9/f518bd45a247fe241dc6196f3b96aef7270b3f1e1a98ebee35d8d66cc389/faiss_cpu-1.11.0.post1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7f4b0e03cd758d03012d88aa4a70e673d10b66f31f7c122adc0c8c323cad2e33", size = 24012805 }, + { url = "https://files.pythonhosted.org/packages/43/0a/7394ba0220d0e13be48d7c4c4d8ddd6a2a98f7960a38359157c88e045fe3/faiss_cpu-1.11.0.post1-cp313-cp313-win_amd64.whl", hash = "sha256:bc53fe59b546dbab63144dc19dcee534ad7a213db617b37aa4d0e33c26f9bbaf", size = 14882903 }, + { url = "https://files.pythonhosted.org/packages/18/50/acc117b601da14f1a79f7deda3fad49509265d6b14c2221687cabc378dad/faiss_cpu-1.11.0.post1-cp313-cp313-win_arm64.whl", hash = "sha256:9cebb720cd57afdbe9dd7ed8a689c65dc5cf1bad475c5aa6fa0d0daea890beb6", size = 7852193 }, +] + +[[package]] +name = "fastjsonschema" +version = "2.21.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8b/50/4b769ce1ac4071a1ef6d86b1a3fb56cdc3a37615e8c5519e1af96cdac366/fastjsonschema-2.21.1.tar.gz", hash = "sha256:794d4f0a58f848961ba16af7b9c85a3e88cd360df008c59aac6fc5ae9323b5d4", size = 373939 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/2b/0817a2b257fe88725c25589d89aec060581aabf668707a8d03b2e9e0cb2a/fastjsonschema-2.21.1-py3-none-any.whl", hash = "sha256:c9e5b7e908310918cf494a434eeb31384dd84a98b57a30bcb1f535015b554667", size = 23924 }, +] + +[[package]] +name = "filelock" +version = "3.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215 }, +] + +[[package]] +name = "flash-attn" +version = "2.7.4.post1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "einops" }, + { name = "torch" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/11/34/9bf60e736ed7bbe15055ac2dab48ec67d9dbd088d2b4ae318fd77190ab4e/flash_attn-2.7.4.post1.tar.gz", hash = "sha256:f03485c9a49a4d68d0733acdcad80ab0e72afa025a777fdc2966ceccf9d51765", size = 5986610 } + +[[package]] +name = "fonttools" +version = "4.58.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/5a/1124b2c8cb3a8015faf552e92714040bcdbc145dfa29928891b02d147a18/fonttools-4.58.4.tar.gz", hash = "sha256:928a8009b9884ed3aae17724b960987575155ca23c6f0b8146e400cc9e0d44ba", size = 3525026 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/7b/cc6e9bb41bab223bd2dc70ba0b21386b85f604e27f4c3206b4205085a2ab/fonttools-4.58.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a3841991c9ee2dc0562eb7f23d333d34ce81e8e27c903846f0487da21e0028eb", size = 2768901 }, + { url = "https://files.pythonhosted.org/packages/3d/15/98d75df9f2b4e7605f3260359ad6e18e027c11fa549f74fce567270ac891/fonttools-4.58.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c98f91b6a9604e7ffb5ece6ea346fa617f967c2c0944228801246ed56084664", size = 2328696 }, + { url = "https://files.pythonhosted.org/packages/a8/c8/dc92b80f5452c9c40164e01b3f78f04b835a00e673bd9355ca257008ff61/fonttools-4.58.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab9f891eb687ddf6a4e5f82901e00f992e18012ca97ab7acd15f13632acd14c1", size = 5018830 }, + { url = "https://files.pythonhosted.org/packages/19/48/8322cf177680505d6b0b6062e204f01860cb573466a88077a9b795cb70e8/fonttools-4.58.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:891c5771e8f0094b7c0dc90eda8fc75e72930b32581418f2c285a9feedfd9a68", size = 4960922 }, + { url = "https://files.pythonhosted.org/packages/14/e0/2aff149ed7eb0916de36da513d473c6fff574a7146891ce42de914899395/fonttools-4.58.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:43ba4d9646045c375d22e3473b7d82b18b31ee2ac715cd94220ffab7bc2d5c1d", size = 4997135 }, + { url = "https://files.pythonhosted.org/packages/e6/6f/4d9829b29a64a2e63a121cb11ecb1b6a9524086eef3e35470949837a1692/fonttools-4.58.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33d19f16e6d2ffd6669bda574a6589941f6c99a8d5cfb9f464038244c71555de", size = 5108701 }, + { url = "https://files.pythonhosted.org/packages/6f/1e/2d656ddd1b0cd0d222f44b2d008052c2689e66b702b9af1cd8903ddce319/fonttools-4.58.4-cp311-cp311-win32.whl", hash = "sha256:b59e5109b907da19dc9df1287454821a34a75f2632a491dd406e46ff432c2a24", size = 2200177 }, + { url = "https://files.pythonhosted.org/packages/fb/83/ba71ad053fddf4157cb0697c8da8eff6718d059f2a22986fa5f312b49c92/fonttools-4.58.4-cp311-cp311-win_amd64.whl", hash = "sha256:3d471a5b567a0d1648f2e148c9a8bcf00d9ac76eb89e976d9976582044cc2509", size = 2247892 }, + { url = "https://files.pythonhosted.org/packages/04/3c/1d1792bfe91ef46f22a3d23b4deb514c325e73c17d4f196b385b5e2faf1c/fonttools-4.58.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:462211c0f37a278494e74267a994f6be9a2023d0557aaa9ecbcbfce0f403b5a6", size = 2754082 }, + { url = "https://files.pythonhosted.org/packages/2a/1f/2b261689c901a1c3bc57a6690b0b9fc21a9a93a8b0c83aae911d3149f34e/fonttools-4.58.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0c7a12fb6f769165547f00fcaa8d0df9517603ae7e04b625e5acb8639809b82d", size = 2321677 }, + { url = "https://files.pythonhosted.org/packages/fe/6b/4607add1755a1e6581ae1fc0c9a640648e0d9cdd6591cc2d581c2e07b8c3/fonttools-4.58.4-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2d42c63020a922154add0a326388a60a55504629edc3274bc273cd3806b4659f", size = 4896354 }, + { url = "https://files.pythonhosted.org/packages/cd/95/34b4f483643d0cb11a1f830b72c03fdd18dbd3792d77a2eb2e130a96fada/fonttools-4.58.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f2b4e6fd45edc6805f5f2c355590b092ffc7e10a945bd6a569fc66c1d2ae7aa", size = 4941633 }, + { url = "https://files.pythonhosted.org/packages/81/ac/9bafbdb7694059c960de523e643fa5a61dd2f698f3f72c0ca18ae99257c7/fonttools-4.58.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f155b927f6efb1213a79334e4cb9904d1e18973376ffc17a0d7cd43d31981f1e", size = 4886170 }, + { url = "https://files.pythonhosted.org/packages/ae/44/a3a3b70d5709405f7525bb7cb497b4e46151e0c02e3c8a0e40e5e9fe030b/fonttools-4.58.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e38f687d5de97c7fb7da3e58169fb5ba349e464e141f83c3c2e2beb91d317816", size = 5037851 }, + { url = "https://files.pythonhosted.org/packages/21/cb/e8923d197c78969454eb876a4a55a07b59c9c4c46598f02b02411dc3b45c/fonttools-4.58.4-cp312-cp312-win32.whl", hash = "sha256:636c073b4da9db053aa683db99580cac0f7c213a953b678f69acbca3443c12cc", size = 2187428 }, + { url = "https://files.pythonhosted.org/packages/46/e6/fe50183b1a0e1018e7487ee740fa8bb127b9f5075a41e20d017201e8ab14/fonttools-4.58.4-cp312-cp312-win_amd64.whl", hash = "sha256:82e8470535743409b30913ba2822e20077acf9ea70acec40b10fcf5671dceb58", size = 2236649 }, + { url = "https://files.pythonhosted.org/packages/d4/4f/c05cab5fc1a4293e6bc535c6cb272607155a0517700f5418a4165b7f9ec8/fonttools-4.58.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5f4a64846495c543796fa59b90b7a7a9dff6839bd852741ab35a71994d685c6d", size = 2745197 }, + { url = "https://files.pythonhosted.org/packages/3e/d3/49211b1f96ae49308f4f78ca7664742377a6867f00f704cdb31b57e4b432/fonttools-4.58.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e80661793a5d4d7ad132a2aa1eae2e160fbdbb50831a0edf37c7c63b2ed36574", size = 2317272 }, + { url = "https://files.pythonhosted.org/packages/b2/11/c9972e46a6abd752a40a46960e431c795ad1f306775fc1f9e8c3081a1274/fonttools-4.58.4-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fe5807fc64e4ba5130f1974c045a6e8d795f3b7fb6debfa511d1773290dbb76b", size = 4877184 }, + { url = "https://files.pythonhosted.org/packages/ea/24/5017c01c9ef8df572cc9eaf9f12be83ad8ed722ff6dc67991d3d752956e4/fonttools-4.58.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b610b9bef841cb8f4b50472494158b1e347d15cad56eac414c722eda695a6cfd", size = 4939445 }, + { url = "https://files.pythonhosted.org/packages/79/b0/538cc4d0284b5a8826b4abed93a69db52e358525d4b55c47c8cef3669767/fonttools-4.58.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2daa7f0e213c38f05f054eb5e1730bd0424aebddbeac094489ea1585807dd187", size = 4878800 }, + { url = "https://files.pythonhosted.org/packages/5a/9b/a891446b7a8250e65bffceb248508587958a94db467ffd33972723ab86c9/fonttools-4.58.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:66cccb6c0b944496b7f26450e9a66e997739c513ffaac728d24930df2fd9d35b", size = 5021259 }, + { url = "https://files.pythonhosted.org/packages/17/b2/c4d2872cff3ace3ddd1388bf15b76a1d8d5313f0a61f234e9aed287e674d/fonttools-4.58.4-cp313-cp313-win32.whl", hash = "sha256:94d2aebb5ca59a5107825520fde596e344652c1f18170ef01dacbe48fa60c889", size = 2185824 }, + { url = "https://files.pythonhosted.org/packages/98/57/cddf8bcc911d4f47dfca1956c1e3aeeb9f7c9b8e88b2a312fe8c22714e0b/fonttools-4.58.4-cp313-cp313-win_amd64.whl", hash = "sha256:b554bd6e80bba582fd326ddab296e563c20c64dca816d5e30489760e0c41529f", size = 2236382 }, + { url = "https://files.pythonhosted.org/packages/0b/2f/c536b5b9bb3c071e91d536a4d11f969e911dbb6b227939f4c5b0bca090df/fonttools-4.58.4-py3-none-any.whl", hash = "sha256:a10ce13a13f26cbb9f37512a4346bb437ad7e002ff6fa966a7ce7ff5ac3528bd", size = 1114660 }, +] + +[[package]] +name = "fqdn" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/3e/a80a8c077fd798951169626cde3e239adeba7dab75deb3555716415bd9b0/fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f", size = 6015 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl", hash = "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014", size = 9121 }, +] + +[[package]] +name = "frozenlist" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8f/ed/0f4cec13a93c02c47ec32d81d11c0c1efbadf4a471e3f3ce7cad366cbbd3/frozenlist-1.5.0.tar.gz", hash = "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817", size = 39930 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/43/0bed28bf5eb1c9e4301003b74453b8e7aa85fb293b31dde352aac528dafc/frozenlist-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fd74520371c3c4175142d02a976aee0b4cb4a7cc912a60586ffd8d5929979b30", size = 94987 }, + { url = "https://files.pythonhosted.org/packages/bb/bf/b74e38f09a246e8abbe1e90eb65787ed745ccab6eaa58b9c9308e052323d/frozenlist-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2f3f7a0fbc219fb4455264cae4d9f01ad41ae6ee8524500f381de64ffaa077d5", size = 54584 }, + { url = "https://files.pythonhosted.org/packages/2c/31/ab01375682f14f7613a1ade30149f684c84f9b8823a4391ed950c8285656/frozenlist-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f47c9c9028f55a04ac254346e92977bf0f166c483c74b4232bee19a6697e4778", size = 52499 }, + { url = "https://files.pythonhosted.org/packages/98/a8/d0ac0b9276e1404f58fec3ab6e90a4f76b778a49373ccaf6a563f100dfbc/frozenlist-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0996c66760924da6e88922756d99b47512a71cfd45215f3570bf1e0b694c206a", size = 276357 }, + { url = "https://files.pythonhosted.org/packages/ad/c9/c7761084fa822f07dac38ac29f841d4587570dd211e2262544aa0b791d21/frozenlist-1.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2fe128eb4edeabe11896cb6af88fca5346059f6c8d807e3b910069f39157869", size = 287516 }, + { url = "https://files.pythonhosted.org/packages/a1/ff/cd7479e703c39df7bdab431798cef89dc75010d8aa0ca2514c5b9321db27/frozenlist-1.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a8ea951bbb6cacd492e3948b8da8c502a3f814f5d20935aae74b5df2b19cf3d", size = 283131 }, + { url = "https://files.pythonhosted.org/packages/59/a0/370941beb47d237eca4fbf27e4e91389fd68699e6f4b0ebcc95da463835b/frozenlist-1.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de537c11e4aa01d37db0d403b57bd6f0546e71a82347a97c6a9f0dcc532b3a45", size = 261320 }, + { url = "https://files.pythonhosted.org/packages/b8/5f/c10123e8d64867bc9b4f2f510a32042a306ff5fcd7e2e09e5ae5100ee333/frozenlist-1.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c2623347b933fcb9095841f1cc5d4ff0b278addd743e0e966cb3d460278840d", size = 274877 }, + { url = "https://files.pythonhosted.org/packages/fa/79/38c505601ae29d4348f21706c5d89755ceded02a745016ba2f58bd5f1ea6/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cee6798eaf8b1416ef6909b06f7dc04b60755206bddc599f52232606e18179d3", size = 269592 }, + { url = "https://files.pythonhosted.org/packages/19/e2/39f3a53191b8204ba9f0bb574b926b73dd2efba2a2b9d2d730517e8f7622/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f5f9da7f5dbc00a604fe74aa02ae7c98bcede8a3b8b9666f9f86fc13993bc71a", size = 265934 }, + { url = "https://files.pythonhosted.org/packages/d5/c9/3075eb7f7f3a91f1a6b00284af4de0a65a9ae47084930916f5528144c9dd/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:90646abbc7a5d5c7c19461d2e3eeb76eb0b204919e6ece342feb6032c9325ae9", size = 283859 }, + { url = "https://files.pythonhosted.org/packages/05/f5/549f44d314c29408b962fa2b0e69a1a67c59379fb143b92a0a065ffd1f0f/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:bdac3c7d9b705d253b2ce370fde941836a5f8b3c5c2b8fd70940a3ea3af7f4f2", size = 287560 }, + { url = "https://files.pythonhosted.org/packages/9d/f8/cb09b3c24a3eac02c4c07a9558e11e9e244fb02bf62c85ac2106d1eb0c0b/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03d33c2ddbc1816237a67f66336616416e2bbb6beb306e5f890f2eb22b959cdf", size = 277150 }, + { url = "https://files.pythonhosted.org/packages/37/48/38c2db3f54d1501e692d6fe058f45b6ad1b358d82cd19436efab80cfc965/frozenlist-1.5.0-cp311-cp311-win32.whl", hash = "sha256:237f6b23ee0f44066219dae14c70ae38a63f0440ce6750f868ee08775073f942", size = 45244 }, + { url = "https://files.pythonhosted.org/packages/ca/8c/2ddffeb8b60a4bce3b196c32fcc30d8830d4615e7b492ec2071da801b8ad/frozenlist-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:0cc974cc93d32c42e7b0f6cf242a6bd941c57c61b618e78b6c0a96cb72788c1d", size = 51634 }, + { url = "https://files.pythonhosted.org/packages/79/73/fa6d1a96ab7fd6e6d1c3500700963eab46813847f01ef0ccbaa726181dd5/frozenlist-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:31115ba75889723431aa9a4e77d5f398f5cf976eea3bdf61749731f62d4a4a21", size = 94026 }, + { url = "https://files.pythonhosted.org/packages/ab/04/ea8bf62c8868b8eada363f20ff1b647cf2e93377a7b284d36062d21d81d1/frozenlist-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7437601c4d89d070eac8323f121fcf25f88674627505334654fd027b091db09d", size = 54150 }, + { url = "https://files.pythonhosted.org/packages/d0/9a/8e479b482a6f2070b26bda572c5e6889bb3ba48977e81beea35b5ae13ece/frozenlist-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7948140d9f8ece1745be806f2bfdf390127cf1a763b925c4a805c603df5e697e", size = 51927 }, + { url = "https://files.pythonhosted.org/packages/e3/12/2aad87deb08a4e7ccfb33600871bbe8f0e08cb6d8224371387f3303654d7/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feeb64bc9bcc6b45c6311c9e9b99406660a9c05ca8a5b30d14a78555088b0b3a", size = 282647 }, + { url = "https://files.pythonhosted.org/packages/77/f2/07f06b05d8a427ea0060a9cef6e63405ea9e0d761846b95ef3fb3be57111/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:683173d371daad49cffb8309779e886e59c2f369430ad28fe715f66d08d4ab1a", size = 289052 }, + { url = "https://files.pythonhosted.org/packages/bd/9f/8bf45a2f1cd4aa401acd271b077989c9267ae8463e7c8b1eb0d3f561b65e/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d57d8f702221405a9d9b40f9da8ac2e4a1a8b5285aac6100f3393675f0a85ee", size = 291719 }, + { url = "https://files.pythonhosted.org/packages/41/d1/1f20fd05a6c42d3868709b7604c9f15538a29e4f734c694c6bcfc3d3b935/frozenlist-1.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c72000fbcc35b129cb09956836c7d7abf78ab5416595e4857d1cae8d6251a6", size = 267433 }, + { url = "https://files.pythonhosted.org/packages/af/f2/64b73a9bb86f5a89fb55450e97cd5c1f84a862d4ff90d9fd1a73ab0f64a5/frozenlist-1.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000a77d6034fbad9b6bb880f7ec073027908f1b40254b5d6f26210d2dab1240e", size = 283591 }, + { url = "https://files.pythonhosted.org/packages/29/e2/ffbb1fae55a791fd6c2938dd9ea779509c977435ba3940b9f2e8dc9d5316/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d7f5a50342475962eb18b740f3beecc685a15b52c91f7d975257e13e029eca9", size = 273249 }, + { url = "https://files.pythonhosted.org/packages/2e/6e/008136a30798bb63618a114b9321b5971172a5abddff44a100c7edc5ad4f/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:87f724d055eb4785d9be84e9ebf0f24e392ddfad00b3fe036e43f489fafc9039", size = 271075 }, + { url = "https://files.pythonhosted.org/packages/ae/f0/4e71e54a026b06724cec9b6c54f0b13a4e9e298cc8db0f82ec70e151f5ce/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6e9080bb2fb195a046e5177f10d9d82b8a204c0736a97a153c2466127de87784", size = 285398 }, + { url = "https://files.pythonhosted.org/packages/4d/36/70ec246851478b1c0b59f11ef8ade9c482ff447c1363c2bd5fad45098b12/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b93d7aaa36c966fa42efcaf716e6b3900438632a626fb09c049f6a2f09fc631", size = 294445 }, + { url = "https://files.pythonhosted.org/packages/37/e0/47f87544055b3349b633a03c4d94b405956cf2437f4ab46d0928b74b7526/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:52ef692a4bc60a6dd57f507429636c2af8b6046db8b31b18dac02cbc8f507f7f", size = 280569 }, + { url = "https://files.pythonhosted.org/packages/f9/7c/490133c160fb6b84ed374c266f42800e33b50c3bbab1652764e6e1fc498a/frozenlist-1.5.0-cp312-cp312-win32.whl", hash = "sha256:29d94c256679247b33a3dc96cce0f93cbc69c23bf75ff715919332fdbb6a32b8", size = 44721 }, + { url = "https://files.pythonhosted.org/packages/b1/56/4e45136ffc6bdbfa68c29ca56ef53783ef4c2fd395f7cbf99a2624aa9aaa/frozenlist-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:8969190d709e7c48ea386db202d708eb94bdb29207a1f269bab1196ce0dcca1f", size = 51329 }, + { url = "https://files.pythonhosted.org/packages/da/3b/915f0bca8a7ea04483622e84a9bd90033bab54bdf485479556c74fd5eaf5/frozenlist-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a1a048f9215c90973402e26c01d1cff8a209e1f1b53f72b95c13db61b00f953", size = 91538 }, + { url = "https://files.pythonhosted.org/packages/c7/d1/a7c98aad7e44afe5306a2b068434a5830f1470675f0e715abb86eb15f15b/frozenlist-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dd47a5181ce5fcb463b5d9e17ecfdb02b678cca31280639255ce9d0e5aa67af0", size = 52849 }, + { url = "https://files.pythonhosted.org/packages/3a/c8/76f23bf9ab15d5f760eb48701909645f686f9c64fbb8982674c241fbef14/frozenlist-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1431d60b36d15cda188ea222033eec8e0eab488f39a272461f2e6d9e1a8e63c2", size = 50583 }, + { url = "https://files.pythonhosted.org/packages/1f/22/462a3dd093d11df623179d7754a3b3269de3b42de2808cddef50ee0f4f48/frozenlist-1.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6482a5851f5d72767fbd0e507e80737f9c8646ae7fd303def99bfe813f76cf7f", size = 265636 }, + { url = "https://files.pythonhosted.org/packages/80/cf/e075e407fc2ae7328155a1cd7e22f932773c8073c1fc78016607d19cc3e5/frozenlist-1.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44c49271a937625619e862baacbd037a7ef86dd1ee215afc298a417ff3270608", size = 270214 }, + { url = "https://files.pythonhosted.org/packages/a1/58/0642d061d5de779f39c50cbb00df49682832923f3d2ebfb0fedf02d05f7f/frozenlist-1.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12f78f98c2f1c2429d42e6a485f433722b0061d5c0b0139efa64f396efb5886b", size = 273905 }, + { url = "https://files.pythonhosted.org/packages/ab/66/3fe0f5f8f2add5b4ab7aa4e199f767fd3b55da26e3ca4ce2cc36698e50c4/frozenlist-1.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce3aa154c452d2467487765e3adc730a8c153af77ad84096bc19ce19a2400840", size = 250542 }, + { url = "https://files.pythonhosted.org/packages/f6/b8/260791bde9198c87a465224e0e2bb62c4e716f5d198fc3a1dacc4895dbd1/frozenlist-1.5.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b7dc0c4338e6b8b091e8faf0db3168a37101943e687f373dce00959583f7439", size = 267026 }, + { url = "https://files.pythonhosted.org/packages/2e/a4/3d24f88c527f08f8d44ade24eaee83b2627793fa62fa07cbb7ff7a2f7d42/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45e0896250900b5aa25180f9aec243e84e92ac84bd4a74d9ad4138ef3f5c97de", size = 257690 }, + { url = "https://files.pythonhosted.org/packages/de/9a/d311d660420b2beeff3459b6626f2ab4fb236d07afbdac034a4371fe696e/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:561eb1c9579d495fddb6da8959fd2a1fca2c6d060d4113f5844b433fc02f2641", size = 253893 }, + { url = "https://files.pythonhosted.org/packages/c6/23/e491aadc25b56eabd0f18c53bb19f3cdc6de30b2129ee0bc39cd387cd560/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:df6e2f325bfee1f49f81aaac97d2aa757c7646534a06f8f577ce184afe2f0a9e", size = 267006 }, + { url = "https://files.pythonhosted.org/packages/08/c4/ab918ce636a35fb974d13d666dcbe03969592aeca6c3ab3835acff01f79c/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:140228863501b44b809fb39ec56b5d4071f4d0aa6d216c19cbb08b8c5a7eadb9", size = 276157 }, + { url = "https://files.pythonhosted.org/packages/c0/29/3b7a0bbbbe5a34833ba26f686aabfe982924adbdcafdc294a7a129c31688/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7707a25d6a77f5d27ea7dc7d1fc608aa0a478193823f88511ef5e6b8a48f9d03", size = 264642 }, + { url = "https://files.pythonhosted.org/packages/ab/42/0595b3dbffc2e82d7fe658c12d5a5bafcd7516c6bf2d1d1feb5387caa9c1/frozenlist-1.5.0-cp313-cp313-win32.whl", hash = "sha256:31a9ac2b38ab9b5a8933b693db4939764ad3f299fcaa931a3e605bc3460e693c", size = 44914 }, + { url = "https://files.pythonhosted.org/packages/17/c4/b7db1206a3fea44bf3b838ca61deb6f74424a8a5db1dd53ecb21da669be6/frozenlist-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:11aabdd62b8b9c4b84081a3c246506d1cddd2dd93ff0ad53ede5defec7886b28", size = 51167 }, + { url = "https://files.pythonhosted.org/packages/c6/c8/a5be5b7550c10858fcf9b0ea054baccab474da77d37f1e828ce043a3a5d4/frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3", size = 11901 }, +] + +[[package]] +name = "fsspec" +version = "2025.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/34/f4/5721faf47b8c499e776bc34c6a8fc17efdf7fdef0b00f398128bc5dcb4ac/fsspec-2025.3.0.tar.gz", hash = "sha256:a935fd1ea872591f2b5148907d103488fc523295e6c64b835cfad8c3eca44972", size = 298491 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/53/eb690efa8513166adef3e0669afd31e95ffde69fb3c52ec2ac7223ed6018/fsspec-2025.3.0-py3-none-any.whl", hash = "sha256:efb87af3efa9103f94ca91a7f8cb7a4df91af9f74fc106c9c7ea0efd7277c1b3", size = 193615 }, +] + +[package.optional-dependencies] +http = [ + { name = "aiohttp" }, +] + +[[package]] +name = "gitdb" +version = "4.0.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "smmap" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794 }, +] + +[[package]] +name = "gitpython" +version = "3.1.44" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "gitdb" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/89/37df0b71473153574a5cdef8f242de422a0f5d26d7a9e231e6f169b4ad14/gitpython-3.1.44.tar.gz", hash = "sha256:c87e30b26253bf5418b01b0660f818967f3c503193838337fe5e573331249269", size = 214196 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/9a/4114a9057db2f1462d5c8f8390ab7383925fe1ac012eaa42402ad65c2963/GitPython-3.1.44-py3-none-any.whl", hash = "sha256:9e0e10cda9bed1ee64bc9a6de50e7e38a9c9943241cd7f585f6df3ed28011110", size = 207599 }, +] + +[[package]] +name = "h11" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 }, +] + +[[package]] +name = "hf-transfer" +version = "0.1.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1a/eb/8fc64f40388c29ce8ce3b2b180a089d4d6b25b1d0d232d016704cb852104/hf_transfer-0.1.9.tar.gz", hash = "sha256:035572865dab29d17e783fbf1e84cf1cb24f3fcf8f1b17db1cfc7fdf139f02bf", size = 25201 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/78/0dce00208f585fae675f40033ef9a30dedfa83665d5ac79f16beb4a0a6c2/hf_transfer-0.1.9-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:6e94e8822da79573c9b6ae4d6b2f847c59a7a06c5327d7db20751b68538dc4f6", size = 1386084 }, + { url = "https://files.pythonhosted.org/packages/ea/2e/3d60b1a9e9f29a2152aa66c823bf5e399ae7be3fef310ff0de86779c5d2d/hf_transfer-0.1.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3ebc4ab9023414880c8b1d3c38174d1c9989eb5022d37e814fa91a3060123eb0", size = 1343558 }, + { url = "https://files.pythonhosted.org/packages/fb/38/130a5ac3747f104033591bcac1c961cb1faadfdc91704f59b09c0b465ff2/hf_transfer-0.1.9-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8674026f21ed369aa2a0a4b46000aca850fc44cd2b54af33a172ce5325b4fc82", size = 3726676 }, + { url = "https://files.pythonhosted.org/packages/15/a1/f4e27c5ad17aac616ae0849e2aede5aae31db8267a948c6b3eeb9fd96446/hf_transfer-0.1.9-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a736dfbb2c84f5a2c975478ad200c0c8bfcb58a25a35db402678fb87ce17fa4", size = 3062920 }, + { url = "https://files.pythonhosted.org/packages/8d/0d/727abdfba39bc3f1132cfa4c970588c2c0bb0d82fe2d645cc10f4e2f8e0b/hf_transfer-0.1.9-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:504b8427fd785dd8546d53b9fafe6e436bd7a3adf76b9dce556507650a7b4567", size = 3578681 }, + { url = "https://files.pythonhosted.org/packages/50/d0/2b213eb1ea8b1252ccaf1a6c804d0aba03fea38aae4124df6a3acb70511a/hf_transfer-0.1.9-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2c7fc1b85f4d0f76e452765d7648c9f4bfd0aedb9ced2ae1ebfece2d8cfaf8e2", size = 3398837 }, + { url = "https://files.pythonhosted.org/packages/8c/8a/79dbce9006e0bd6b74516f97451a7b7c64dbbb426df15d901dd438cfeee3/hf_transfer-0.1.9-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d991376f0eac70a60f0cbc95602aa708a6f7c8617f28b4945c1431d67b8e3c8", size = 3546986 }, + { url = "https://files.pythonhosted.org/packages/a9/f7/9ac239b6ee6fe0bad130325d987a93ea58c4118e50479f0786f1733b37e8/hf_transfer-0.1.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e6ac4eddcd99575ed3735ed911ddf9d1697e2bd13aa3f0ad7e3904dd4863842e", size = 4071715 }, + { url = "https://files.pythonhosted.org/packages/d8/a3/0ed697279f5eeb7a40f279bd783cf50e6d0b91f24120dcf66ef2cf8822b4/hf_transfer-0.1.9-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:57fd9880da1ee0f47250f735f791fab788f0aa1ee36afc49f761349869c8b4d9", size = 3388081 }, + { url = "https://files.pythonhosted.org/packages/dc/eb/47e477bdf1d784f31c7540db6cc8c354b777e51a186897a7abda34517f36/hf_transfer-0.1.9-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:5d561f0520f493c66b016d99ceabe69c23289aa90be38dd802d2aef279f15751", size = 3658654 }, + { url = "https://files.pythonhosted.org/packages/45/07/6661e43fbee09594a8a5e9bb778107d95fe38dac4c653982afe03d32bd4d/hf_transfer-0.1.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a5b366d34cd449fe9b20ef25941e6eef0460a2f74e7389f02e673e1f88ebd538", size = 3690551 }, + { url = "https://files.pythonhosted.org/packages/81/f5/461d2e5f307e5048289b1168d5c642ae3bb2504e88dff1a38b92ed990a21/hf_transfer-0.1.9-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:e66acf91df4a8b72f60223059df3003062a5ae111757187ed1a06750a30e911b", size = 1393046 }, + { url = "https://files.pythonhosted.org/packages/41/ba/8d9fd9f1083525edfcb389c93738c802f3559cb749324090d7109c8bf4c2/hf_transfer-0.1.9-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:8669dbcc7a3e2e8d61d42cd24da9c50d57770bd74b445c65123291ca842a7e7a", size = 1348126 }, + { url = "https://files.pythonhosted.org/packages/8e/a2/cd7885bc9959421065a6fae0fe67b6c55becdeda4e69b873e52976f9a9f0/hf_transfer-0.1.9-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8fd0167c4407a3bc4cdd0307e65ada2294ec04f1813d8a69a5243e379b22e9d8", size = 3728604 }, + { url = "https://files.pythonhosted.org/packages/f6/2e/a072cf196edfeda3310c9a5ade0a0fdd785e6154b3ce24fc738c818da2a7/hf_transfer-0.1.9-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ee8b10afedcb75f71091bcc197c526a6ebf5c58bbbadb34fdeee6160f55f619f", size = 3064995 }, + { url = "https://files.pythonhosted.org/packages/c2/84/aec9ef4c0fab93c1ea2b1badff38c78b4b2f86f0555b26d2051dbc920cde/hf_transfer-0.1.9-cp38-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5828057e313de59300dd1abb489444bc452efe3f479d3c55b31a8f680936ba42", size = 3580908 }, + { url = "https://files.pythonhosted.org/packages/29/63/b560d39651a56603d64f1a0212d0472a44cbd965db2fa62b99d99cb981bf/hf_transfer-0.1.9-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc6bd19e1cc177c66bdef15ef8636ad3bde79d5a4f608c158021153b4573509d", size = 3400839 }, + { url = "https://files.pythonhosted.org/packages/d6/d8/f87ea6f42456254b48915970ed98e993110521e9263472840174d32c880d/hf_transfer-0.1.9-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdca9bfb89e6f8f281890cc61a8aff2d3cecaff7e1a4d275574d96ca70098557", size = 3552664 }, + { url = "https://files.pythonhosted.org/packages/d6/56/1267c39b65fc8f4e2113b36297320f102718bf5799b544a6cbe22013aa1d/hf_transfer-0.1.9-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:89a23f58b7b7effbc047b8ca286f131b17728c99a9f972723323003ffd1bb916", size = 4073732 }, + { url = "https://files.pythonhosted.org/packages/82/1a/9c748befbe3decf7cb415e34f8a0c3789a0a9c55910dea73d581e48c0ce5/hf_transfer-0.1.9-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:dc7fff1345980d6c0ebb92c811d24afa4b98b3e07ed070c8e38cc91fd80478c5", size = 3390096 }, + { url = "https://files.pythonhosted.org/packages/72/85/4c03da147b6b4b7cb12e074d3d44eee28604a387ed0eaf7eaaead5069c57/hf_transfer-0.1.9-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:1a6bd16c667ebe89a069ca163060127a794fa3a3525292c900b8c8cc47985b0d", size = 3664743 }, + { url = "https://files.pythonhosted.org/packages/e7/6e/e597b04f753f1b09e6893075d53a82a30c13855cbaa791402695b01e369f/hf_transfer-0.1.9-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d2fde99d502093ade3ab1b53f80da18480e9902aa960dab7f74fb1b9e5bc5746", size = 3695243 }, + { url = "https://files.pythonhosted.org/packages/09/89/d4e234727a26b2546c8fb70a276cd924260d60135f2165bf8b9ed67bb9a4/hf_transfer-0.1.9-cp38-abi3-win32.whl", hash = "sha256:435cc3cdc8524ce57b074032b8fd76eed70a4224d2091232fa6a8cef8fd6803e", size = 1086605 }, + { url = "https://files.pythonhosted.org/packages/a1/14/f1e15b851d1c2af5b0b1a82bf8eb10bda2da62d98180220ba6fd8879bb5b/hf_transfer-0.1.9-cp38-abi3-win_amd64.whl", hash = "sha256:16f208fc678911c37e11aa7b586bc66a37d02e636208f18b6bc53d29b5df40ad", size = 1160240 }, +] + +[[package]] +name = "httpcore" +version = "1.0.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6a/41/d7d0a89eb493922c37d343b607bc1b5da7f5be7e383740b4753ad8943e90/httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c", size = 85196 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/f5/72347bc88306acb359581ac4d52f23c0ef445b57157adedb9aee0cd689d2/httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd", size = 78551 }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 }, +] + +[[package]] +name = "huggingface" +version = "0.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/a4/168b574a23c1841fab5b24ecac98a88ea626ea3c746c481f79eb360c81f2/huggingface-0.0.1.tar.gz", hash = "sha256:0a2f228fd956801d68b7c6a8bef478dfa60c4b7d7eba572ea7de39ecf87e505a", size = 2320 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/8c/e61fbc39c0a37140e1d4941c4af29e2d53bacf9f4559e3de24d8f4e484f0/huggingface-0.0.1-py3-none-any.whl", hash = "sha256:98a3409537557cd2fd768997ef94cab08529f86c5e106e6d54bbabdd5ee03910", size = 2455 }, +] + +[[package]] +name = "huggingface-hub" +version = "0.30.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "fsspec" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/22/8eb91736b1dcb83d879bd49050a09df29a57cc5cd9f38e48a4b1c45ee890/huggingface_hub-0.30.2.tar.gz", hash = "sha256:9a7897c5b6fd9dad3168a794a8998d6378210f5b9688d0dfc180b1a228dc2466", size = 400868 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/27/1fb384a841e9661faad1c31cbfa62864f59632e876df5d795234da51c395/huggingface_hub-0.30.2-py3-none-any.whl", hash = "sha256:68ff05969927058cfa41df4f2155d4bb48f5f54f719dd0390103eefa9b191e28", size = 481433 }, +] + +[package.optional-dependencies] +hf-transfer = [ + { name = "hf-transfer" }, +] + +[[package]] +name = "hydra-core" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "antlr4-python3-runtime" }, + { name = "omegaconf" }, + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/8e/07e42bc434a847154083b315779b0a81d567154504624e181caf2c71cd98/hydra-core-1.3.2.tar.gz", hash = "sha256:8a878ed67216997c3e9d88a8e72e7b4767e81af37afb4ea3334b269a4390a824", size = 3263494 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/50/e0edd38dcd63fb26a8547f13d28f7a008bc4a3fd4eb4ff030673f22ad41a/hydra_core-1.3.2-py3-none-any.whl", hash = "sha256:fa0238a9e31df3373b35b0bfb672c34cc92718d21f81311d8996a16de1141d8b", size = 154547 }, +] + +[[package]] +name = "identify" +version = "2.6.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0c/83/b6ea0334e2e7327084a46aaaf71f2146fc061a192d6518c0d020120cd0aa/identify-2.6.10.tar.gz", hash = "sha256:45e92fd704f3da71cc3880036633f48b4b7265fd4de2b57627cb157216eb7eb8", size = 99201 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/d3/85feeba1d097b81a44bcffa6a0beab7b4dfffe78e82fc54978d3ac380736/identify-2.6.10-py2.py3-none-any.whl", hash = "sha256:5f34248f54136beed1a7ba6a6b5c4b6cf21ff495aac7c359e1ef831ae3b8ab25", size = 99101 }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, +] + +[[package]] +name = "interpretable-flow" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "black" }, + { name = "datasets" }, + { name = "einops" }, + { name = "flash-attn" }, + { name = "huggingface" }, + { name = "huggingface-hub", extra = ["hf-transfer"] }, + { name = "hydra-core" }, + { name = "ipykernel" }, + { name = "matplotlib" }, + { name = "mauve-text" }, + { name = "omegaconf" }, + { name = "pre-commit" }, + { name = "pytorch-lightning" }, + { name = "ruff" }, + { name = "sentencepiece" }, + { name = "torch" }, + { name = "torchaudio" }, + { name = "torchvision" }, + { name = "transformers" }, + { name = "wandb" }, + { name = "zstandard" }, + { name = "zstd" }, +] + +[package.dev-dependencies] +dev = [ + { name = "ipykernel" }, + { name = "jupyter" }, +] + +[package.metadata] +requires-dist = [ + { name = "black", specifier = ">=25.1.0" }, + { name = "datasets", specifier = ">=3.6.0" }, + { name = "einops", specifier = ">=0.8.1" }, + { name = "flash-attn", specifier = ">=2.7.4.post1" }, + { name = "huggingface", specifier = ">=0.0.1" }, + { name = "huggingface-hub", extras = ["hf-transfer"], specifier = ">=0.30.2" }, + { name = "hydra-core", specifier = ">=1.3.2" }, + { name = "ipykernel", specifier = ">=6.29.5" }, + { name = "matplotlib", specifier = ">=3.10.3" }, + { name = "mauve-text", specifier = ">=0.4.0" }, + { name = "omegaconf", specifier = ">=2.3.0" }, + { name = "pre-commit", specifier = ">=4.2.0" }, + { name = "pytorch-lightning", specifier = ">=2.5.1" }, + { name = "ruff", specifier = ">=0.11.10" }, + { name = "sentencepiece", specifier = ">=0.2.0" }, + { name = "torch", specifier = ">=2.6.0" }, + { name = "torchaudio", specifier = ">=2.6.0" }, + { name = "torchvision", specifier = ">=0.21.0" }, + { name = "transformers", specifier = ">=4.52.3" }, + { name = "wandb", specifier = ">=0.20.1" }, + { name = "zstandard", specifier = ">=0.23.0" }, + { name = "zstd", specifier = ">=1.5.7.1" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "ipykernel", specifier = ">=6.29.5" }, + { name = "jupyter", specifier = ">=1.1.1" }, +] + +[[package]] +name = "ipykernel" +version = "6.29.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "appnope", marker = "sys_platform == 'darwin'" }, + { name = "comm" }, + { name = "debugpy" }, + { name = "ipython" }, + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "matplotlib-inline" }, + { name = "nest-asyncio" }, + { name = "packaging" }, + { name = "psutil" }, + { name = "pyzmq" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/5c/67594cb0c7055dc50814b21731c22a601101ea3b1b50a9a1b090e11f5d0f/ipykernel-6.29.5.tar.gz", hash = "sha256:f093a22c4a40f8828f8e330a9c297cb93dcab13bd9678ded6de8e5cf81c56215", size = 163367 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/5c/368ae6c01c7628438358e6d337c19b05425727fbb221d2a3c4303c372f42/ipykernel-6.29.5-py3-none-any.whl", hash = "sha256:afdb66ba5aa354b09b91379bac28ae4afebbb30e8b39510c9690afb7a10421b5", size = 117173 }, +] + +[[package]] +name = "ipython" +version = "9.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "decorator" }, + { name = "ipython-pygments-lexers" }, + { name = "jedi" }, + { name = "matplotlib-inline" }, + { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit" }, + { name = "pygments" }, + { name = "stack-data" }, + { name = "traitlets" }, + { name = "typing-extensions", marker = "python_full_version < '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/70/9a/6b8984bedc990f3a4aa40ba8436dea27e23d26a64527de7c2e5e12e76841/ipython-9.1.0.tar.gz", hash = "sha256:a47e13a5e05e02f3b8e1e7a0f9db372199fe8c3763532fe7a1e0379e4e135f16", size = 4373688 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/9d/4ff2adf55d1b6e3777b0303fdbe5b723f76e46cba4a53a32fe82260d2077/ipython-9.1.0-py3-none-any.whl", hash = "sha256:2df07257ec2f84a6b346b8d83100bcf8fa501c6e01ab75cd3799b0bb253b3d2a", size = 604053 }, +] + +[[package]] +name = "ipython-pygments-lexers" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074 }, +] + +[[package]] +name = "ipywidgets" +version = "8.1.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "comm" }, + { name = "ipython" }, + { name = "jupyterlab-widgets" }, + { name = "traitlets" }, + { name = "widgetsnbextension" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/4c/dab2a281b07596a5fc220d49827fe6c794c66f1493d7a74f1df0640f2cc5/ipywidgets-8.1.5.tar.gz", hash = "sha256:870e43b1a35656a80c18c9503bbf2d16802db1cb487eec6fab27d683381dde17", size = 116723 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/2d/9c0b76f2f9cc0ebede1b9371b6f317243028ed60b90705863d493bae622e/ipywidgets-8.1.5-py3-none-any.whl", hash = "sha256:3290f526f87ae6e77655555baba4f36681c555b8bdbbff430b70e52c34c86245", size = 139767 }, +] + +[[package]] +name = "isoduration" +version = "20.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "arrow" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7c/1a/3c8edc664e06e6bd06cce40c6b22da5f1429aa4224d0c590f3be21c91ead/isoduration-20.11.0.tar.gz", hash = "sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9", size = 11649 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl", hash = "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042", size = 11321 }, +] + +[[package]] +name = "jedi" +version = "0.19.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "parso" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278 }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, +] + +[[package]] +name = "joblib" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/fe/0f5a938c54105553436dbff7a61dc4fed4b1b2c98852f8833beaf4d5968f/joblib-1.5.1.tar.gz", hash = "sha256:f4f86e351f39fe3d0d32a9f2c3d8af1ee4cec285aafcb27003dda5205576b444", size = 330475 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/4f/1195bbac8e0c2acc5f740661631d8d750dc38d4a32b23ee5df3cde6f4e0d/joblib-1.5.1-py3-none-any.whl", hash = "sha256:4719a31f054c7d766948dcd83e9613686b27114f190f717cec7eaa2084f8a74a", size = 307746 }, +] + +[[package]] +name = "json5" +version = "0.12.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/12/be/c6c745ec4c4539b25a278b70e29793f10382947df0d9efba2fa09120895d/json5-0.12.0.tar.gz", hash = "sha256:0b4b6ff56801a1c7dc817b0241bca4ce474a0e6a163bfef3fc594d3fd263ff3a", size = 51907 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/9f/3500910d5a98549e3098807493851eeef2b89cdd3032227558a104dfe926/json5-0.12.0-py3-none-any.whl", hash = "sha256:6d37aa6c08b0609f16e1ec5ff94697e2cbbfbad5ac112afa05794da9ab7810db", size = 36079 }, +] + +[[package]] +name = "jsonpointer" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/0a/eebeb1fa92507ea94016a2a790b93c2ae41a7e18778f85471dc54475ed25/jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef", size = 9114 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595 }, +] + +[[package]] +name = "jsonschema" +version = "4.23.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/2e/03362ee4034a4c917f697890ccd4aec0800ccf9ded7f511971c75451deec/jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4", size = 325778 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/4a/4f9dbeb84e8850557c02365a0eee0649abe5eb1d84af92a25731c6c0f922/jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566", size = 88462 }, +] + +[package.optional-dependencies] +format-nongpl = [ + { name = "fqdn" }, + { name = "idna" }, + { name = "isoduration" }, + { name = "jsonpointer" }, + { name = "rfc3339-validator" }, + { name = "rfc3986-validator" }, + { name = "uri-template" }, + { name = "webcolors" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2024.10.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/10/db/58f950c996c793472e336ff3655b13fbcf1e3b359dcf52dcf3ed3b52c352/jsonschema_specifications-2024.10.1.tar.gz", hash = "sha256:0f38b83639958ce1152d02a7f062902c41c8fd20d558b0c34344292d417ae272", size = 15561 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/0f/8910b19ac0670a0f80ce1008e5e751c4a57e14d2c4c13a482aa6079fa9d6/jsonschema_specifications-2024.10.1-py3-none-any.whl", hash = "sha256:a09a0680616357d9a0ecf05c12ad234479f549239d0f5b55f3deea67475da9bf", size = 18459 }, +] + +[[package]] +name = "jupyter" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ipykernel" }, + { name = "ipywidgets" }, + { name = "jupyter-console" }, + { name = "jupyterlab" }, + { name = "nbconvert" }, + { name = "notebook" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/f3/af28ea964ab8bc1e472dba2e82627d36d470c51f5cd38c37502eeffaa25e/jupyter-1.1.1.tar.gz", hash = "sha256:d55467bceabdea49d7e3624af7e33d59c37fff53ed3a350e1ac957bed731de7a", size = 5714959 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/64/285f20a31679bf547b75602702f7800e74dbabae36ef324f716c02804753/jupyter-1.1.1-py2.py3-none-any.whl", hash = "sha256:7a59533c22af65439b24bbe60373a4e95af8f16ac65a6c00820ad378e3f7cc83", size = 2657 }, +] + +[[package]] +name = "jupyter-client" +version = "8.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-core" }, + { name = "python-dateutil" }, + { name = "pyzmq" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/22/bf9f12fdaeae18019a468b68952a60fe6dbab5d67cd2a103cac7659b41ca/jupyter_client-8.6.3.tar.gz", hash = "sha256:35b3a0947c4a6e9d589eb97d7d4cd5e90f910ee73101611f01283732bd6d9419", size = 342019 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl", hash = "sha256:e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f", size = 106105 }, +] + +[[package]] +name = "jupyter-console" +version = "6.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ipykernel" }, + { name = "ipython" }, + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "prompt-toolkit" }, + { name = "pygments" }, + { name = "pyzmq" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bd/2d/e2fd31e2fc41c14e2bcb6c976ab732597e907523f6b2420305f9fc7fdbdb/jupyter_console-6.6.3.tar.gz", hash = "sha256:566a4bf31c87adbfadf22cdf846e3069b59a71ed5da71d6ba4d8aaad14a53539", size = 34363 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/77/71d78d58f15c22db16328a476426f7ac4a60d3a5a7ba3b9627ee2f7903d4/jupyter_console-6.6.3-py3-none-any.whl", hash = "sha256:309d33409fcc92ffdad25f0bcdf9a4a9daa61b6f341177570fdac03de5352485", size = 24510 }, +] + +[[package]] +name = "jupyter-core" +version = "5.7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "platformdirs" }, + { name = "pywin32", marker = "platform_python_implementation != 'PyPy' and sys_platform == 'win32'" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/11/b56381fa6c3f4cc5d2cf54a7dbf98ad9aa0b339ef7a601d6053538b079a7/jupyter_core-5.7.2.tar.gz", hash = "sha256:aa5f8d32bbf6b431ac830496da7392035d6f61b4f54872f15c4bd2a9c3f536d9", size = 87629 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/fb/108ecd1fe961941959ad0ee4e12ee7b8b1477247f30b1fdfd83ceaf017f0/jupyter_core-5.7.2-py3-none-any.whl", hash = "sha256:4f7315d2f6b4bcf2e3e7cb6e46772eba760ae459cd1f59d29eb57b0a01bd7409", size = 28965 }, +] + +[[package]] +name = "jupyter-events" +version = "0.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonschema", extra = ["format-nongpl"] }, + { name = "packaging" }, + { name = "python-json-logger" }, + { name = "pyyaml" }, + { name = "referencing" }, + { name = "rfc3339-validator" }, + { name = "rfc3986-validator" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/c3/306d090461e4cf3cd91eceaff84bede12a8e52cd821c2d20c9a4fd728385/jupyter_events-0.12.0.tar.gz", hash = "sha256:fc3fce98865f6784c9cd0a56a20644fc6098f21c8c33834a8d9fe383c17e554b", size = 62196 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl", hash = "sha256:6464b2fa5ad10451c3d35fabc75eab39556ae1e2853ad0c0cc31b656731a97fb", size = 19430 }, +] + +[[package]] +name = "jupyter-lsp" +version = "2.2.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-server" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/b4/3200b0b09c12bc3b72d943d923323c398eff382d1dcc7c0dbc8b74630e40/jupyter-lsp-2.2.5.tar.gz", hash = "sha256:793147a05ad446f809fd53ef1cd19a9f5256fd0a2d6b7ce943a982cb4f545001", size = 48741 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/e0/7bd7cff65594fd9936e2f9385701e44574fc7d721331ff676ce440b14100/jupyter_lsp-2.2.5-py3-none-any.whl", hash = "sha256:45fbddbd505f3fbfb0b6cb2f1bc5e15e83ab7c79cd6e89416b248cb3c00c11da", size = 69146 }, +] + +[[package]] +name = "jupyter-server" +version = "2.15.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "argon2-cffi" }, + { name = "jinja2" }, + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "jupyter-events" }, + { name = "jupyter-server-terminals" }, + { name = "nbconvert" }, + { name = "nbformat" }, + { name = "overrides" }, + { name = "packaging" }, + { name = "prometheus-client" }, + { name = "pywinpty", marker = "os_name == 'nt' and sys_platform != 'linux'" }, + { name = "pyzmq" }, + { name = "send2trash" }, + { name = "terminado" }, + { name = "tornado" }, + { name = "traitlets" }, + { name = "websocket-client" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/8c/df09d4ab646141f130f9977b32b206ba8615d1969b2eba6a2e84b7f89137/jupyter_server-2.15.0.tar.gz", hash = "sha256:9d446b8697b4f7337a1b7cdcac40778babdd93ba614b6d68ab1c0c918f1c4084", size = 725227 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/a2/89eeaf0bb954a123a909859fa507fa86f96eb61b62dc30667b60dbd5fdaf/jupyter_server-2.15.0-py3-none-any.whl", hash = "sha256:872d989becf83517012ee669f09604aa4a28097c0bd90b2f424310156c2cdae3", size = 385826 }, +] + +[[package]] +name = "jupyter-server-terminals" +version = "0.5.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pywinpty", marker = "os_name == 'nt' and sys_platform != 'linux'" }, + { name = "terminado" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/d5/562469734f476159e99a55426d697cbf8e7eb5efe89fb0e0b4f83a3d3459/jupyter_server_terminals-0.5.3.tar.gz", hash = "sha256:5ae0295167220e9ace0edcfdb212afd2b01ee8d179fe6f23c899590e9b8a5269", size = 31430 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl", hash = "sha256:41ee0d7dc0ebf2809c668e0fc726dfaf258fcd3e769568996ca731b6194ae9aa", size = 13656 }, +] + +[[package]] +name = "jupyterlab" +version = "4.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "async-lru" }, + { name = "httpx" }, + { name = "ipykernel" }, + { name = "jinja2" }, + { name = "jupyter-core" }, + { name = "jupyter-lsp" }, + { name = "jupyter-server" }, + { name = "jupyterlab-server" }, + { name = "notebook-shim" }, + { name = "packaging" }, + { name = "setuptools" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/49/0beaab21155e5f7438032f3da920abbcf46159b28adafbdf596dd33c57a6/jupyterlab-4.4.0.tar.gz", hash = "sha256:f1767d5f0104e40f3b4a63bf6892bbef8e4704dcabf0c78408a3bdc411792f04", size = 22996521 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/64/1a559e1b945c3d424c1ac9f333bfd6f595d5819efde3a6d8b036e6b0585f/jupyterlab-4.4.0-py3-none-any.whl", hash = "sha256:61d33991fbb352cc7caac08bd0c34577fea86d8d5d9772600d9d5a6bcbc882c0", size = 12291918 }, +] + +[[package]] +name = "jupyterlab-pygments" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/90/51/9187be60d989df97f5f0aba133fa54e7300f17616e065d1ada7d7646b6d6/jupyterlab_pygments-0.3.0.tar.gz", hash = "sha256:721aca4d9029252b11cfa9d185e5b5af4d54772bb8072f9b7036f4170054d35d", size = 512900 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl", hash = "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780", size = 15884 }, +] + +[[package]] +name = "jupyterlab-server" +version = "2.27.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "babel" }, + { name = "jinja2" }, + { name = "json5" }, + { name = "jsonschema" }, + { name = "jupyter-server" }, + { name = "packaging" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0a/c9/a883ce65eb27905ce77ace410d83587c82ea64dc85a48d1f7ed52bcfa68d/jupyterlab_server-2.27.3.tar.gz", hash = "sha256:eb36caca59e74471988f0ae25c77945610b887f777255aa21f8065def9e51ed4", size = 76173 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl", hash = "sha256:e697488f66c3db49df675158a77b3b017520d772c6e1548c7d9bcc5df7944ee4", size = 59700 }, +] + +[[package]] +name = "jupyterlab-widgets" +version = "3.0.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/59/73/fa26bbb747a9ea4fca6b01453aa22990d52ab62dd61384f1ac0dc9d4e7ba/jupyterlab_widgets-3.0.13.tar.gz", hash = "sha256:a2966d385328c1942b683a8cd96b89b8dd82c8b8f81dda902bb2bc06d46f5bed", size = 203556 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/93/858e87edc634d628e5d752ba944c2833133a28fa87bb093e6832ced36a3e/jupyterlab_widgets-3.0.13-py3-none-any.whl", hash = "sha256:e3cda2c233ce144192f1e29914ad522b2f4c40e77214b0cc97377ca3d323db54", size = 214392 }, +] + +[[package]] +name = "kiwisolver" +version = "1.4.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/59/7c91426a8ac292e1cdd53a63b6d9439abd573c875c3f92c146767dd33faf/kiwisolver-1.4.8.tar.gz", hash = "sha256:23d5f023bdc8c7e54eb65f03ca5d5bb25b601eac4d7f1a042888a1f45237987e", size = 97538 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/ed/c913ee28936c371418cb167b128066ffb20bbf37771eecc2c97edf8a6e4c/kiwisolver-1.4.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a4d3601908c560bdf880f07d94f31d734afd1bb71e96585cace0e38ef44c6d84", size = 124635 }, + { url = "https://files.pythonhosted.org/packages/4c/45/4a7f896f7467aaf5f56ef093d1f329346f3b594e77c6a3c327b2d415f521/kiwisolver-1.4.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:856b269c4d28a5c0d5e6c1955ec36ebfd1651ac00e1ce0afa3e28da95293b561", size = 66717 }, + { url = "https://files.pythonhosted.org/packages/5f/b4/c12b3ac0852a3a68f94598d4c8d569f55361beef6159dce4e7b624160da2/kiwisolver-1.4.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c2b9a96e0f326205af81a15718a9073328df1173a2619a68553decb7097fd5d7", size = 65413 }, + { url = "https://files.pythonhosted.org/packages/a9/98/1df4089b1ed23d83d410adfdc5947245c753bddfbe06541c4aae330e9e70/kiwisolver-1.4.8-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5020c83e8553f770cb3b5fc13faac40f17e0b205bd237aebd21d53d733adb03", size = 1343994 }, + { url = "https://files.pythonhosted.org/packages/8d/bf/b4b169b050c8421a7c53ea1ea74e4ef9c335ee9013216c558a047f162d20/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dace81d28c787956bfbfbbfd72fdcef014f37d9b48830829e488fdb32b49d954", size = 1434804 }, + { url = "https://files.pythonhosted.org/packages/66/5a/e13bd341fbcf73325ea60fdc8af752addf75c5079867af2e04cc41f34434/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:11e1022b524bd48ae56c9b4f9296bce77e15a2e42a502cceba602f804b32bb79", size = 1450690 }, + { url = "https://files.pythonhosted.org/packages/9b/4f/5955dcb376ba4a830384cc6fab7d7547bd6759fe75a09564910e9e3bb8ea/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b9b4d2892fefc886f30301cdd80debd8bb01ecdf165a449eb6e78f79f0fabd6", size = 1376839 }, + { url = "https://files.pythonhosted.org/packages/3a/97/5edbed69a9d0caa2e4aa616ae7df8127e10f6586940aa683a496c2c280b9/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a96c0e790ee875d65e340ab383700e2b4891677b7fcd30a699146f9384a2bb0", size = 1435109 }, + { url = "https://files.pythonhosted.org/packages/13/fc/e756382cb64e556af6c1809a1bbb22c141bbc2445049f2da06b420fe52bf/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:23454ff084b07ac54ca8be535f4174170c1094a4cff78fbae4f73a4bcc0d4dab", size = 2245269 }, + { url = "https://files.pythonhosted.org/packages/76/15/e59e45829d7f41c776d138245cabae6515cb4eb44b418f6d4109c478b481/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:87b287251ad6488e95b4f0b4a79a6d04d3ea35fde6340eb38fbd1ca9cd35bbbc", size = 2393468 }, + { url = "https://files.pythonhosted.org/packages/e9/39/483558c2a913ab8384d6e4b66a932406f87c95a6080112433da5ed668559/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b21dbe165081142b1232a240fc6383fd32cdd877ca6cc89eab93e5f5883e1c25", size = 2355394 }, + { url = "https://files.pythonhosted.org/packages/01/aa/efad1fbca6570a161d29224f14b082960c7e08268a133fe5dc0f6906820e/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:768cade2c2df13db52475bd28d3a3fac8c9eff04b0e9e2fda0f3760f20b3f7fc", size = 2490901 }, + { url = "https://files.pythonhosted.org/packages/c9/4f/15988966ba46bcd5ab9d0c8296914436720dd67fca689ae1a75b4ec1c72f/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d47cfb2650f0e103d4bf68b0b5804c68da97272c84bb12850d877a95c056bd67", size = 2312306 }, + { url = "https://files.pythonhosted.org/packages/2d/27/bdf1c769c83f74d98cbc34483a972f221440703054894a37d174fba8aa68/kiwisolver-1.4.8-cp311-cp311-win_amd64.whl", hash = "sha256:ed33ca2002a779a2e20eeb06aea7721b6e47f2d4b8a8ece979d8ba9e2a167e34", size = 71966 }, + { url = "https://files.pythonhosted.org/packages/4a/c9/9642ea855604aeb2968a8e145fc662edf61db7632ad2e4fb92424be6b6c0/kiwisolver-1.4.8-cp311-cp311-win_arm64.whl", hash = "sha256:16523b40aab60426ffdebe33ac374457cf62863e330a90a0383639ce14bf44b2", size = 65311 }, + { url = "https://files.pythonhosted.org/packages/fc/aa/cea685c4ab647f349c3bc92d2daf7ae34c8e8cf405a6dcd3a497f58a2ac3/kiwisolver-1.4.8-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d6af5e8815fd02997cb6ad9bbed0ee1e60014438ee1a5c2444c96f87b8843502", size = 124152 }, + { url = "https://files.pythonhosted.org/packages/c5/0b/8db6d2e2452d60d5ebc4ce4b204feeb16176a851fd42462f66ade6808084/kiwisolver-1.4.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bade438f86e21d91e0cf5dd7c0ed00cda0f77c8c1616bd83f9fc157fa6760d31", size = 66555 }, + { url = "https://files.pythonhosted.org/packages/60/26/d6a0db6785dd35d3ba5bf2b2df0aedc5af089962c6eb2cbf67a15b81369e/kiwisolver-1.4.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b83dc6769ddbc57613280118fb4ce3cd08899cc3369f7d0e0fab518a7cf37fdb", size = 65067 }, + { url = "https://files.pythonhosted.org/packages/c9/ed/1d97f7e3561e09757a196231edccc1bcf59d55ddccefa2afc9c615abd8e0/kiwisolver-1.4.8-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:111793b232842991be367ed828076b03d96202c19221b5ebab421ce8bcad016f", size = 1378443 }, + { url = "https://files.pythonhosted.org/packages/29/61/39d30b99954e6b46f760e6289c12fede2ab96a254c443639052d1b573fbc/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:257af1622860e51b1a9d0ce387bf5c2c4f36a90594cb9514f55b074bcc787cfc", size = 1472728 }, + { url = "https://files.pythonhosted.org/packages/0c/3e/804163b932f7603ef256e4a715e5843a9600802bb23a68b4e08c8c0ff61d/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69b5637c3f316cab1ec1c9a12b8c5f4750a4c4b71af9157645bf32830e39c03a", size = 1478388 }, + { url = "https://files.pythonhosted.org/packages/8a/9e/60eaa75169a154700be74f875a4d9961b11ba048bef315fbe89cb6999056/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:782bb86f245ec18009890e7cb8d13a5ef54dcf2ebe18ed65f795e635a96a1c6a", size = 1413849 }, + { url = "https://files.pythonhosted.org/packages/bc/b3/9458adb9472e61a998c8c4d95cfdfec91c73c53a375b30b1428310f923e4/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc978a80a0db3a66d25767b03688f1147a69e6237175c0f4ffffaaedf744055a", size = 1475533 }, + { url = "https://files.pythonhosted.org/packages/e4/7a/0a42d9571e35798de80aef4bb43a9b672aa7f8e58643d7bd1950398ffb0a/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:36dbbfd34838500a31f52c9786990d00150860e46cd5041386f217101350f0d3", size = 2268898 }, + { url = "https://files.pythonhosted.org/packages/d9/07/1255dc8d80271400126ed8db35a1795b1a2c098ac3a72645075d06fe5c5d/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:eaa973f1e05131de5ff3569bbba7f5fd07ea0595d3870ed4a526d486fe57fa1b", size = 2425605 }, + { url = "https://files.pythonhosted.org/packages/84/df/5a3b4cf13780ef6f6942df67b138b03b7e79e9f1f08f57c49957d5867f6e/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a66f60f8d0c87ab7f59b6fb80e642ebb29fec354a4dfad687ca4092ae69d04f4", size = 2375801 }, + { url = "https://files.pythonhosted.org/packages/8f/10/2348d068e8b0f635c8c86892788dac7a6b5c0cb12356620ab575775aad89/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858416b7fb777a53f0c59ca08190ce24e9abbd3cffa18886a5781b8e3e26f65d", size = 2520077 }, + { url = "https://files.pythonhosted.org/packages/32/d8/014b89fee5d4dce157d814303b0fce4d31385a2af4c41fed194b173b81ac/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:085940635c62697391baafaaeabdf3dd7a6c3643577dde337f4d66eba021b2b8", size = 2338410 }, + { url = "https://files.pythonhosted.org/packages/bd/72/dfff0cc97f2a0776e1c9eb5bef1ddfd45f46246c6533b0191887a427bca5/kiwisolver-1.4.8-cp312-cp312-win_amd64.whl", hash = "sha256:01c3d31902c7db5fb6182832713d3b4122ad9317c2c5877d0539227d96bb2e50", size = 71853 }, + { url = "https://files.pythonhosted.org/packages/dc/85/220d13d914485c0948a00f0b9eb419efaf6da81b7d72e88ce2391f7aed8d/kiwisolver-1.4.8-cp312-cp312-win_arm64.whl", hash = "sha256:a3c44cb68861de93f0c4a8175fbaa691f0aa22550c331fefef02b618a9dcb476", size = 65424 }, + { url = "https://files.pythonhosted.org/packages/79/b3/e62464a652f4f8cd9006e13d07abad844a47df1e6537f73ddfbf1bc997ec/kiwisolver-1.4.8-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1c8ceb754339793c24aee1c9fb2485b5b1f5bb1c2c214ff13368431e51fc9a09", size = 124156 }, + { url = "https://files.pythonhosted.org/packages/8d/2d/f13d06998b546a2ad4f48607a146e045bbe48030774de29f90bdc573df15/kiwisolver-1.4.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a62808ac74b5e55a04a408cda6156f986cefbcf0ada13572696b507cc92fa1", size = 66555 }, + { url = "https://files.pythonhosted.org/packages/59/e3/b8bd14b0a54998a9fd1e8da591c60998dc003618cb19a3f94cb233ec1511/kiwisolver-1.4.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:68269e60ee4929893aad82666821aaacbd455284124817af45c11e50a4b42e3c", size = 65071 }, + { url = "https://files.pythonhosted.org/packages/f0/1c/6c86f6d85ffe4d0ce04228d976f00674f1df5dc893bf2dd4f1928748f187/kiwisolver-1.4.8-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34d142fba9c464bc3bbfeff15c96eab0e7310343d6aefb62a79d51421fcc5f1b", size = 1378053 }, + { url = "https://files.pythonhosted.org/packages/4e/b9/1c6e9f6dcb103ac5cf87cb695845f5fa71379021500153566d8a8a9fc291/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc373e0eef45b59197de815b1b28ef89ae3955e7722cc9710fb91cd77b7f47", size = 1472278 }, + { url = "https://files.pythonhosted.org/packages/ee/81/aca1eb176de671f8bda479b11acdc42c132b61a2ac861c883907dde6debb/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:77e6f57a20b9bd4e1e2cedda4d0b986ebd0216236f0106e55c28aea3d3d69b16", size = 1478139 }, + { url = "https://files.pythonhosted.org/packages/49/f4/e081522473671c97b2687d380e9e4c26f748a86363ce5af48b4a28e48d06/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08e77738ed7538f036cd1170cbed942ef749137b1311fa2bbe2a7fda2f6bf3cc", size = 1413517 }, + { url = "https://files.pythonhosted.org/packages/8f/e9/6a7d025d8da8c4931522922cd706105aa32b3291d1add8c5427cdcd66e63/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5ce1e481a74b44dd5e92ff03ea0cb371ae7a0268318e202be06c8f04f4f1246", size = 1474952 }, + { url = "https://files.pythonhosted.org/packages/82/13/13fa685ae167bee5d94b415991c4fc7bb0a1b6ebea6e753a87044b209678/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fc2ace710ba7c1dfd1a3b42530b62b9ceed115f19a1656adefce7b1782a37794", size = 2269132 }, + { url = "https://files.pythonhosted.org/packages/ef/92/bb7c9395489b99a6cb41d502d3686bac692586db2045adc19e45ee64ed23/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3452046c37c7692bd52b0e752b87954ef86ee2224e624ef7ce6cb21e8c41cc1b", size = 2425997 }, + { url = "https://files.pythonhosted.org/packages/ed/12/87f0e9271e2b63d35d0d8524954145837dd1a6c15b62a2d8c1ebe0f182b4/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7e9a60b50fe8b2ec6f448fe8d81b07e40141bfced7f896309df271a0b92f80f3", size = 2376060 }, + { url = "https://files.pythonhosted.org/packages/02/6e/c8af39288edbce8bf0fa35dee427b082758a4b71e9c91ef18fa667782138/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:918139571133f366e8362fa4a297aeba86c7816b7ecf0bc79168080e2bd79957", size = 2520471 }, + { url = "https://files.pythonhosted.org/packages/13/78/df381bc7b26e535c91469f77f16adcd073beb3e2dd25042efd064af82323/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e063ef9f89885a1d68dd8b2e18f5ead48653176d10a0e324e3b0030e3a69adeb", size = 2338793 }, + { url = "https://files.pythonhosted.org/packages/d0/dc/c1abe38c37c071d0fc71c9a474fd0b9ede05d42f5a458d584619cfd2371a/kiwisolver-1.4.8-cp313-cp313-win_amd64.whl", hash = "sha256:a17b7c4f5b2c51bb68ed379defd608a03954a1845dfed7cc0117f1cc8a9b7fd2", size = 71855 }, + { url = "https://files.pythonhosted.org/packages/a0/b6/21529d595b126ac298fdd90b705d87d4c5693de60023e0efcb4f387ed99e/kiwisolver-1.4.8-cp313-cp313-win_arm64.whl", hash = "sha256:3cd3bc628b25f74aedc6d374d5babf0166a92ff1317f46267f12d2ed54bc1d30", size = 65430 }, + { url = "https://files.pythonhosted.org/packages/34/bd/b89380b7298e3af9b39f49334e3e2a4af0e04819789f04b43d560516c0c8/kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:370fd2df41660ed4e26b8c9d6bbcad668fbe2560462cba151a721d49e5b6628c", size = 126294 }, + { url = "https://files.pythonhosted.org/packages/83/41/5857dc72e5e4148eaac5aa76e0703e594e4465f8ab7ec0fc60e3a9bb8fea/kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:84a2f830d42707de1d191b9490ac186bf7997a9495d4e9072210a1296345f7dc", size = 67736 }, + { url = "https://files.pythonhosted.org/packages/e1/d1/be059b8db56ac270489fb0b3297fd1e53d195ba76e9bbb30e5401fa6b759/kiwisolver-1.4.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7a3ad337add5148cf51ce0b55642dc551c0b9d6248458a757f98796ca7348712", size = 66194 }, + { url = "https://files.pythonhosted.org/packages/e1/83/4b73975f149819eb7dcf9299ed467eba068ecb16439a98990dcb12e63fdd/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7506488470f41169b86d8c9aeff587293f530a23a23a49d6bc64dab66bedc71e", size = 1465942 }, + { url = "https://files.pythonhosted.org/packages/c7/2c/30a5cdde5102958e602c07466bce058b9d7cb48734aa7a4327261ac8e002/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f0121b07b356a22fb0414cec4666bbe36fd6d0d759db3d37228f496ed67c880", size = 1595341 }, + { url = "https://files.pythonhosted.org/packages/ff/9b/1e71db1c000385aa069704f5990574b8244cce854ecd83119c19e83c9586/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d6d6bd87df62c27d4185de7c511c6248040afae67028a8a22012b010bc7ad062", size = 1598455 }, + { url = "https://files.pythonhosted.org/packages/85/92/c8fec52ddf06231b31cbb779af77e99b8253cd96bd135250b9498144c78b/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:291331973c64bb9cce50bbe871fb2e675c4331dab4f31abe89f175ad7679a4d7", size = 1522138 }, + { url = "https://files.pythonhosted.org/packages/0b/51/9eb7e2cd07a15d8bdd976f6190c0164f92ce1904e5c0c79198c4972926b7/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:893f5525bb92d3d735878ec00f781b2de998333659507d29ea4466208df37bed", size = 1582857 }, + { url = "https://files.pythonhosted.org/packages/0f/95/c5a00387a5405e68ba32cc64af65ce881a39b98d73cc394b24143bebc5b8/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b47a465040146981dc9db8647981b8cb96366fbc8d452b031e4f8fdffec3f26d", size = 2293129 }, + { url = "https://files.pythonhosted.org/packages/44/83/eeb7af7d706b8347548313fa3a3a15931f404533cc54fe01f39e830dd231/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:99cea8b9dd34ff80c521aef46a1dddb0dcc0283cf18bde6d756f1e6f31772165", size = 2421538 }, + { url = "https://files.pythonhosted.org/packages/05/f9/27e94c1b3eb29e6933b6986ffc5fa1177d2cd1f0c8efc5f02c91c9ac61de/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:151dffc4865e5fe6dafce5480fab84f950d14566c480c08a53c663a0020504b6", size = 2390661 }, + { url = "https://files.pythonhosted.org/packages/d9/d4/3c9735faa36ac591a4afcc2980d2691000506050b7a7e80bcfe44048daa7/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:577facaa411c10421314598b50413aa1ebcf5126f704f1e5d72d7e4e9f020d90", size = 2546710 }, + { url = "https://files.pythonhosted.org/packages/4c/fa/be89a49c640930180657482a74970cdcf6f7072c8d2471e1babe17a222dc/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:be4816dc51c8a471749d664161b434912eee82f2ea66bd7628bd14583a833e85", size = 2349213 }, +] + +[[package]] +name = "lightning-utilities" +version = "0.14.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "setuptools" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/bb/63a6a8c9e7a96b6ba92647fa5b1595c2dbee29f8178705adb4704d82ecba/lightning_utilities-0.14.3.tar.gz", hash = "sha256:37e2f83f273890052955a44054382c211a303012ee577619efbaa5df9e65e9f5", size = 30346 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/c1/31b3184cba7b257a4a3b5ca5b88b9204ccb7aa02fe3c992280899293ed54/lightning_utilities-0.14.3-py3-none-any.whl", hash = "sha256:4ab9066aa36cd7b93a05713808901909e96cc3f187ea6fd3052b2fd91313b468", size = 28894 }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353 }, + { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392 }, + { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984 }, + { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120 }, + { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032 }, + { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057 }, + { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359 }, + { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306 }, + { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094 }, + { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521 }, + { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 }, + { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 }, + { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118 }, + { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993 }, + { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178 }, + { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319 }, + { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352 }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 }, + { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 }, + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, +] + +[[package]] +name = "matplotlib" +version = "3.10.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "contourpy" }, + { name = "cycler" }, + { name = "fonttools" }, + { name = "kiwisolver" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/26/91/d49359a21893183ed2a5b6c76bec40e0b1dcbf8ca148f864d134897cfc75/matplotlib-3.10.3.tar.gz", hash = "sha256:2f82d2c5bb7ae93aaaa4cd42aca65d76ce6376f83304fa3a630b569aca274df0", size = 34799811 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/bd/af9f655456f60fe1d575f54fb14704ee299b16e999704817a7645dfce6b0/matplotlib-3.10.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0ef061f74cd488586f552d0c336b2f078d43bc00dc473d2c3e7bfee2272f3fa8", size = 8178873 }, + { url = "https://files.pythonhosted.org/packages/c2/86/e1c86690610661cd716eda5f9d0b35eaf606ae6c9b6736687cfc8f2d0cd8/matplotlib-3.10.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d96985d14dc5f4a736bbea4b9de9afaa735f8a0fc2ca75be2fa9e96b2097369d", size = 8052205 }, + { url = "https://files.pythonhosted.org/packages/54/51/a9f8e49af3883dacddb2da1af5fca1f7468677f1188936452dd9aaaeb9ed/matplotlib-3.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c5f0283da91e9522bdba4d6583ed9d5521566f63729ffb68334f86d0bb98049", size = 8465823 }, + { url = "https://files.pythonhosted.org/packages/e7/e3/c82963a3b86d6e6d5874cbeaa390166458a7f1961bab9feb14d3d1a10f02/matplotlib-3.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdfa07c0ec58035242bc8b2c8aae37037c9a886370eef6850703d7583e19964b", size = 8606464 }, + { url = "https://files.pythonhosted.org/packages/0e/34/24da1027e7fcdd9e82da3194c470143c551852757a4b473a09a012f5b945/matplotlib-3.10.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c0b9849a17bce080a16ebcb80a7b714b5677d0ec32161a2cc0a8e5a6030ae220", size = 9413103 }, + { url = "https://files.pythonhosted.org/packages/a6/da/948a017c3ea13fd4a97afad5fdebe2f5bbc4d28c0654510ce6fd6b06b7bd/matplotlib-3.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:eef6ed6c03717083bc6d69c2d7ee8624205c29a8e6ea5a31cd3492ecdbaee1e1", size = 8065492 }, + { url = "https://files.pythonhosted.org/packages/eb/43/6b80eb47d1071f234ef0c96ca370c2ca621f91c12045f1401b5c9b28a639/matplotlib-3.10.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0ab1affc11d1f495ab9e6362b8174a25afc19c081ba5b0775ef00533a4236eea", size = 8179689 }, + { url = "https://files.pythonhosted.org/packages/0f/70/d61a591958325c357204870b5e7b164f93f2a8cca1dc6ce940f563909a13/matplotlib-3.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2a818d8bdcafa7ed2eed74487fdb071c09c1ae24152d403952adad11fa3c65b4", size = 8050466 }, + { url = "https://files.pythonhosted.org/packages/e7/75/70c9d2306203148cc7902a961240c5927dd8728afedf35e6a77e105a2985/matplotlib-3.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:748ebc3470c253e770b17d8b0557f0aa85cf8c63fd52f1a61af5b27ec0b7ffee", size = 8456252 }, + { url = "https://files.pythonhosted.org/packages/c4/91/ba0ae1ff4b3f30972ad01cd4a8029e70a0ec3b8ea5be04764b128b66f763/matplotlib-3.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed70453fd99733293ace1aec568255bc51c6361cb0da94fa5ebf0649fdb2150a", size = 8601321 }, + { url = "https://files.pythonhosted.org/packages/d2/88/d636041eb54a84b889e11872d91f7cbf036b3b0e194a70fa064eb8b04f7a/matplotlib-3.10.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dbed9917b44070e55640bd13419de83b4c918e52d97561544814ba463811cbc7", size = 9406972 }, + { url = "https://files.pythonhosted.org/packages/b1/79/0d1c165eac44405a86478082e225fce87874f7198300bbebc55faaf6d28d/matplotlib-3.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:cf37d8c6ef1a48829443e8ba5227b44236d7fcaf7647caa3178a4ff9f7a5be05", size = 8067954 }, + { url = "https://files.pythonhosted.org/packages/3b/c1/23cfb566a74c696a3b338d8955c549900d18fe2b898b6e94d682ca21e7c2/matplotlib-3.10.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9f2efccc8dcf2b86fc4ee849eea5dcaecedd0773b30f47980dc0cbeabf26ec84", size = 8180318 }, + { url = "https://files.pythonhosted.org/packages/6c/0c/02f1c3b66b30da9ee343c343acbb6251bef5b01d34fad732446eaadcd108/matplotlib-3.10.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3ddbba06a6c126e3301c3d272a99dcbe7f6c24c14024e80307ff03791a5f294e", size = 8051132 }, + { url = "https://files.pythonhosted.org/packages/b4/ab/8db1a5ac9b3a7352fb914133001dae889f9fcecb3146541be46bed41339c/matplotlib-3.10.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:748302b33ae9326995b238f606e9ed840bf5886ebafcb233775d946aa8107a15", size = 8457633 }, + { url = "https://files.pythonhosted.org/packages/f5/64/41c4367bcaecbc03ef0d2a3ecee58a7065d0a36ae1aa817fe573a2da66d4/matplotlib-3.10.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a80fcccbef63302c0efd78042ea3c2436104c5b1a4d3ae20f864593696364ac7", size = 8601031 }, + { url = "https://files.pythonhosted.org/packages/12/6f/6cc79e9e5ab89d13ed64da28898e40fe5b105a9ab9c98f83abd24e46d7d7/matplotlib-3.10.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:55e46cbfe1f8586adb34f7587c3e4f7dedc59d5226719faf6cb54fc24f2fd52d", size = 9406988 }, + { url = "https://files.pythonhosted.org/packages/b1/0f/eed564407bd4d935ffabf561ed31099ed609e19287409a27b6d336848653/matplotlib-3.10.3-cp313-cp313-win_amd64.whl", hash = "sha256:151d89cb8d33cb23345cd12490c76fd5d18a56581a16d950b48c6ff19bb2ab93", size = 8068034 }, + { url = "https://files.pythonhosted.org/packages/3e/e5/2f14791ff69b12b09e9975e1d116d9578ac684460860ce542c2588cb7a1c/matplotlib-3.10.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c26dd9834e74d164d06433dc7be5d75a1e9890b926b3e57e74fa446e1a62c3e2", size = 8218223 }, + { url = "https://files.pythonhosted.org/packages/5c/08/30a94afd828b6e02d0a52cae4a29d6e9ccfcf4c8b56cc28b021d3588873e/matplotlib-3.10.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:24853dad5b8c84c8c2390fc31ce4858b6df504156893292ce8092d190ef8151d", size = 8094985 }, + { url = "https://files.pythonhosted.org/packages/89/44/f3bc6b53066c889d7a1a3ea8094c13af6a667c5ca6220ec60ecceec2dabe/matplotlib-3.10.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68f7878214d369d7d4215e2a9075fef743be38fa401d32e6020bab2dfabaa566", size = 8483109 }, + { url = "https://files.pythonhosted.org/packages/ba/c7/473bc559beec08ebee9f86ca77a844b65747e1a6c2691e8c92e40b9f42a8/matplotlib-3.10.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6929fc618cb6db9cb75086f73b3219bbb25920cb24cee2ea7a12b04971a4158", size = 8618082 }, + { url = "https://files.pythonhosted.org/packages/d8/e9/6ce8edd264c8819e37bbed8172e0ccdc7107fe86999b76ab5752276357a4/matplotlib-3.10.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6c7818292a5cc372a2dc4c795e5c356942eb8350b98ef913f7fda51fe175ac5d", size = 9413699 }, + { url = "https://files.pythonhosted.org/packages/1b/92/9a45c91089c3cf690b5badd4be81e392ff086ccca8a1d4e3a08463d8a966/matplotlib-3.10.3-cp313-cp313t-win_amd64.whl", hash = "sha256:4f23ffe95c5667ef8a2b56eea9b53db7f43910fa4a2d5472ae0f72b64deab4d5", size = 8139044 }, +] + +[[package]] +name = "matplotlib-inline" +version = "0.1.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/5b/a36a337438a14116b16480db471ad061c36c3694df7c2084a0da7ba538b7/matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90", size = 8159 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca", size = 9899 }, +] + +[[package]] +name = "mauve-text" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "faiss-cpu" }, + { name = "numpy" }, + { name = "requests" }, + { name = "scikit-learn" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/53/f1/790ce5858951689e17cf2d767a951fdd40dd22b33b8ae01aecee182d2ad2/mauve-text-0.4.0.tar.gz", hash = "sha256:a9cd29587d1acdfeb006274839c44ac65aec378fb89cceb368094f4a264fd94f", size = 22281 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/0a/7fa7d797479b762e800b57064c5fe861743fe12722292c36de220fc964a2/mauve_text-0.4.0-py3-none-any.whl", hash = "sha256:ceafe978fd66adf4a2a8bad8c47be417e7306f43a4a4ab9121de01f81fc7e47b", size = 21534 }, +] + +[[package]] +name = "mistune" +version = "3.1.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/79/bda47f7dd7c3c55770478d6d02c9960c430b0cf1773b72366ff89126ea31/mistune-3.1.3.tar.gz", hash = "sha256:a7035c21782b2becb6be62f8f25d3df81ccb4d6fa477a6525b15af06539f02a0", size = 94347 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/01/4d/23c4e4f09da849e127e9f123241946c23c1e30f45a88366879e064211815/mistune-3.1.3-py3-none-any.whl", hash = "sha256:1a32314113cff28aa6432e99e522677c8587fd83e3d51c29b82a52409c842bd9", size = 53410 }, +] + +[[package]] +name = "mpmath" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198 }, +] + +[[package]] +name = "multidict" +version = "6.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/2d/6e0d6771cadd5ad14d13193cc8326dc0b341cc1659c306cbfce7a5058fff/multidict-6.3.2.tar.gz", hash = "sha256:c1035eea471f759fa853dd6e76aaa1e389f93b3e1403093fa0fd3ab4db490678", size = 88060 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/e3/443e682e42eaddad0b217b7a59627927fa42b6cd7ba7174f0a01eb3fe6b8/multidict-6.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:45a034f41fcd16968c0470d8912d293d7b0d0822fc25739c5c2ff7835b85bc56", size = 62734 }, + { url = "https://files.pythonhosted.org/packages/b1/4f/2126e9bc37f5be2fdfa36cc192e7ef10b3e9c58eec75a4468706aca96891/multidict-6.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:352585cec45f5d83d886fc522955492bb436fca032b11d487b12d31c5a81b9e3", size = 37115 }, + { url = "https://files.pythonhosted.org/packages/6a/af/5aae0c05a66fdf8bf015ee6903d3a250a7d9c6cc75c9478d04995e6ff1e2/multidict-6.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:da9d89d293511fd0a83a90559dc131f8b3292b6975eb80feff19e5f4663647e2", size = 36371 }, + { url = "https://files.pythonhosted.org/packages/94/27/42390b75c20ff63f43fce44f36f9f66be466cd9ee05326051e4caacdb75b/multidict-6.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79fa716592224aa652b9347a586cfe018635229074565663894eb4eb21f8307f", size = 243444 }, + { url = "https://files.pythonhosted.org/packages/21/55/77077af851d7678fe0845c4050a537321d82fb12a04d4f6db334a1cc6ff7/multidict-6.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0326278a44c56e94792475268e5cd3d47fbc0bd41ee56928c3bbb103ba7f58fe", size = 256750 }, + { url = "https://files.pythonhosted.org/packages/f1/09/4c5bfeb2fc8a1e14002239bd6a4d9ba2963fb148889d444b05a20db32a41/multidict-6.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bb1ea87f7fe45e5079f6315e95d64d4ca8b43ef656d98bed63a02e3756853a22", size = 251630 }, + { url = "https://files.pythonhosted.org/packages/24/a9/286756a1afb8648772de851f8f39d2dd4076506f0c0fc2b751259fcbf0dd/multidict-6.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cff3c5a98d037024a9065aafc621a8599fad7b423393685dc83cf7a32f8b691", size = 238522 }, + { url = "https://files.pythonhosted.org/packages/c2/03/4bb17df70742aae786fcbc27e89e2e49c322134698cd0739aec93e91c669/multidict-6.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed99834b053c655d980fb98029003cb24281e47a796052faad4543aa9e01b8e8", size = 230230 }, + { url = "https://files.pythonhosted.org/packages/53/cc/30df95ba07a9f233ae48d0605b3f72457364836b61a8a8e3d333fdcd32c0/multidict-6.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7048440e505d2b4741e5d0b32bd2f427c901f38c7760fc245918be2cf69b3b85", size = 239676 }, + { url = "https://files.pythonhosted.org/packages/25/37/2d9fe2944c2df5b71ba90cf657b90ad65f1542989cdabe4d1bdbf8c51530/multidict-6.3.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:27248c27b563f5889556da8a96e18e98a56ff807ac1a7d56cf4453c2c9e4cd91", size = 238143 }, + { url = "https://files.pythonhosted.org/packages/ce/13/8f833f9f992eae49f4cb1a1ad05b8fbe183721a154d51c2136b177a41bdb/multidict-6.3.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6323b4ba0e018bd266f776c35f3f0943fc4ee77e481593c9f93bd49888f24e94", size = 248817 }, + { url = "https://files.pythonhosted.org/packages/15/d4/4f49c41af6c4cab962ad51436e6c5acfbdab4fa54f5e98faa56f66f89b03/multidict-6.3.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:81f7ce5ec7c27d0b45c10449c8f0fed192b93251e2e98cb0b21fec779ef1dc4d", size = 241268 }, + { url = "https://files.pythonhosted.org/packages/af/60/e723a00f7bb44366eab8d02fe6f076ecfad58331e10f6f0ce94cb989819c/multidict-6.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03bfcf2825b3bed0ba08a9d854acd18b938cab0d2dba3372b51c78e496bac811", size = 238267 }, + { url = "https://files.pythonhosted.org/packages/62/a6/f6b63fc51c8a4e228e6d2105061be3048b02d490d47e67f7ec2de575f1d0/multidict-6.3.2-cp311-cp311-win32.whl", hash = "sha256:f32c2790512cae6ca886920e58cdc8c784bdc4bb2a5ec74127c71980369d18dc", size = 34986 }, + { url = "https://files.pythonhosted.org/packages/85/56/ea976a5e3ebe0e871e004d9cacfe4c803f8ade353eaf4a247580e9dd7b9d/multidict-6.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:0b0c15e58e038a2cd75ef7cf7e072bc39b5e0488b165902efb27978984bbad70", size = 38427 }, + { url = "https://files.pythonhosted.org/packages/83/ae/bd7518193b4374484c04ba0f6522d0572dc17fcd53d238deb3cb3643c858/multidict-6.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d1e0ba1ce1b8cc79117196642d95f4365e118eaf5fb85f57cdbcc5a25640b2a4", size = 62680 }, + { url = "https://files.pythonhosted.org/packages/59/e0/a0a9247c32f385ac4c1afefe9c3f2271fb8e235aad72332d42384c41b9cb/multidict-6.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:029bbd7d782251a78975214b78ee632672310f9233d49531fc93e8e99154af25", size = 37366 }, + { url = "https://files.pythonhosted.org/packages/c3/fa/8c23cdd4492d59bea0e762662285f2163766e69e5ea715fe6a03a8670660/multidict-6.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d7db41e3b56817d9175264e5fe00192fbcb8e1265307a59f53dede86161b150e", size = 36103 }, + { url = "https://files.pythonhosted.org/packages/87/35/3bcc3616cb54d3a327b1d26dbec284c3eb7b179e8a78a6075852dbb51dac/multidict-6.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fcab18e65cc555ac29981a581518c23311f2b1e72d8f658f9891590465383be", size = 248231 }, + { url = "https://files.pythonhosted.org/packages/b8/c3/17ddbfd6fc3eed9ab7326a43651e1a97da73f7acc69b78a7bb04b59c073d/multidict-6.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0d50eff89aa4d145a5486b171a2177042d08ea5105f813027eb1050abe91839f", size = 259423 }, + { url = "https://files.pythonhosted.org/packages/1f/67/64b18180e8f559cc93efaaaac2fe0746b9c978560866b6fdd626d3237129/multidict-6.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:643e57b403d3e240045a3681f9e6a04d35a33eddc501b4cbbbdbc9c70122e7bc", size = 256204 }, + { url = "https://files.pythonhosted.org/packages/21/f6/e81a8e4817c2d32787b33ae58c72dc3fe08e0ba8e56e660a225df3cb8619/multidict-6.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d17b37b9715b30605b5bab1460569742d0c309e5c20079263b440f5d7746e7e", size = 249663 }, + { url = "https://files.pythonhosted.org/packages/3e/e8/44ca66758df031a8119483cf5385e2ff3b09b9c6df8f3396d626c325b553/multidict-6.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68acd51fa94e63312b8ddf84bfc9c3d3442fe1f9988bbe1b6c703043af8867fe", size = 232236 }, + { url = "https://files.pythonhosted.org/packages/93/76/d2faabbac582dc100a4d7ecf7d0ab8dd2aadf7f10d5d5a19e9932cf63a2e/multidict-6.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:347eea2852ab7f697cc5ed9b1aae96b08f8529cca0c6468f747f0781b1842898", size = 252638 }, + { url = "https://files.pythonhosted.org/packages/63/37/f5a6ea10dab96491b7300be940f86a5490dc474d18473c438f2550b78da3/multidict-6.3.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4d3f8e57027dcda84a1aa181501c15c45eab9566eb6fcc274cbd1e7561224f8", size = 247917 }, + { url = "https://files.pythonhosted.org/packages/d4/b1/2c32b684763b69becbaaa61b7af8a45a6f757fc82d9b4b123ca90cb69f75/multidict-6.3.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9ca57a841ffcf712e47875d026aa49d6e67f9560624d54b51628603700d5d287", size = 261754 }, + { url = "https://files.pythonhosted.org/packages/cd/f2/badedad94e1731debe56d076c9e61a1658c5e9d65dfa9c1ee74d1e3d31d7/multidict-6.3.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7cafdafb44c4e646118410368307693e49d19167e5f119cbe3a88697d2d1a636", size = 256389 }, + { url = "https://files.pythonhosted.org/packages/c6/3a/0a3488be2e5a6499f512e748d31e8fb90b753eb35793ecf390b9d8548e66/multidict-6.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:430120c6ce3715a9c6075cabcee557daccbcca8ba25a9fedf05c7bf564532f2d", size = 251902 }, + { url = "https://files.pythonhosted.org/packages/fe/44/62f76d0a5d836b96168f39a402a75dd3114d0df3cbb5669e0310034b71be/multidict-6.3.2-cp312-cp312-win32.whl", hash = "sha256:13bec31375235a68457ab887ce1bbf4f59d5810d838ae5d7e5b416242e1f3ed4", size = 35101 }, + { url = "https://files.pythonhosted.org/packages/8f/a4/7aaf2313e1766710010c35f9d738fd6309fb71a758f8c0e81853b90afb3d/multidict-6.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:c3b6d7620e6e90c6d97eaf3a63bf7fbd2ba253aab89120a4a9c660bf2d675391", size = 38479 }, + { url = "https://files.pythonhosted.org/packages/b1/b2/15db2b1bec1fe8ab5e7c210e3cd247ed902ef86b58b9f39b0a75476d0e8d/multidict-6.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:b9ca24700322816ae0d426aa33671cf68242f8cc85cee0d0e936465ddaee90b5", size = 62345 }, + { url = "https://files.pythonhosted.org/packages/5f/91/22ea27da2c3ffb8266a92f91f17a84dec2cbdd0f91aa7e5f7d514534dd92/multidict-6.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d9fbbe23667d596ff4f9f74d44b06e40ebb0ab6b262cf14a284f859a66f86457", size = 37205 }, + { url = "https://files.pythonhosted.org/packages/23/cb/563a7481ae677531da84aad86c2de7ebc23446d856d2f6d9794ad4fff375/multidict-6.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9cb602c5bea0589570ad3a4a6f2649c4f13cc7a1e97b4c616e5e9ff8dc490987", size = 35931 }, + { url = "https://files.pythonhosted.org/packages/7c/b7/98fe4f4cd7a0b77a4a48fd3f619848b9e8af4e692eb681f9df9f58d86456/multidict-6.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93ca81dd4d1542e20000ed90f4cc84b7713776f620d04c2b75b8efbe61106c99", size = 246946 }, + { url = "https://files.pythonhosted.org/packages/7e/a3/22dcbd0b58d253719acaf0257a2f35bf609bfd6b73690fcc9e7bdbd3b392/multidict-6.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:18b6310b5454c62242577a128c87df8897f39dd913311cf2e1298e47dfc089eb", size = 260559 }, + { url = "https://files.pythonhosted.org/packages/1c/d4/25eb076f0c2c28d73e7959f3fcc8371e7a029815b5d06e79ea3a265500d2/multidict-6.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7a6dda57de1fc9aedfdb600a8640c99385cdab59a5716cb714b52b6005797f77", size = 257122 }, + { url = "https://files.pythonhosted.org/packages/28/f8/18c81f5c5b7453dd8d15dc61ceca23d03c55e69f1937842039be2d8c4428/multidict-6.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d8ec42d03cc6b29845552a68151f9e623c541f1708328353220af571e24a247", size = 248535 }, + { url = "https://files.pythonhosted.org/packages/9b/17/c175fab75ecfe1c2dd4f28382dd7e80da6d6f0d73c68036f64b6dce9aeeb/multidict-6.3.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80681969cee2fa84dafeb53615d51d24246849984e3e87fbe4fe39956f2e23bf", size = 234013 }, + { url = "https://files.pythonhosted.org/packages/2f/03/1611ecf91d7d6249633cb1dd3fb26d456e0dc0dc80cecccfeb89931a126b/multidict-6.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:01489b0c3592bb9d238e5690e9566db7f77a5380f054b57077d2c4deeaade0eb", size = 249222 }, + { url = "https://files.pythonhosted.org/packages/66/04/0035b77bbffb55f276f00b427e45870194002f9f42e1e3de785d45880372/multidict-6.3.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:522d9f1fd995d04dfedc0a40bca7e2591bc577d920079df50b56245a4a252c1c", size = 245594 }, + { url = "https://files.pythonhosted.org/packages/fe/4c/b52ebcd8ff13a3c833b07cfffa0f50f736b061954a151ee5fe6669bb1bd8/multidict-6.3.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:2014e9cf0b4e9c75bbad49c1758e5a9bf967a56184fc5fcc51527425baf5abba", size = 258709 }, + { url = "https://files.pythonhosted.org/packages/fd/78/9c4433517e8f09035a14aba469617c9cf41a214ca987d9127b84b3de4848/multidict-6.3.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:78ced9fcbee79e446ff4bb3018ac7ba1670703de7873d9c1f6f9883db53c71bc", size = 254015 }, + { url = "https://files.pythonhosted.org/packages/6d/76/8464b4d2e9980bd754aa1850919caef9854453f0400c60f84c79947b799d/multidict-6.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1faf01af972bd01216a107c195f5294f9f393531bc3e4faddc9b333581255d4d", size = 249475 }, + { url = "https://files.pythonhosted.org/packages/c4/e2/2b35b7ce226a2ca8c38125f702090faa8d0a35050461fb111fbaa2e023c4/multidict-6.3.2-cp313-cp313-win32.whl", hash = "sha256:7a699ab13d8d8e1f885de1535b4f477fb93836c87168318244c2685da7b7f655", size = 35204 }, + { url = "https://files.pythonhosted.org/packages/c6/c7/09b85dc11cfa83c9a1e3f8367402d56157624e31a05eecd40d5feed1eed1/multidict-6.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:8666bb0d883310c83be01676e302587834dfd185b52758caeab32ef0eb387bc6", size = 38436 }, + { url = "https://files.pythonhosted.org/packages/63/d6/b27f9db9a8dcca95b50911436c9f187047911be0d78ade3352a6bcabb87a/multidict-6.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:d82c95aabee29612b1c4f48b98be98181686eb7d6c0152301f72715705cc787b", size = 67526 }, + { url = "https://files.pythonhosted.org/packages/2d/23/bbf220b0fa6378526890f37fd9a63d4e2ea990a4a344b221618adc3fb8b0/multidict-6.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f47709173ea9e87a7fd05cd7e5cf1e5d4158924ff988a9a8e0fbd853705f0e68", size = 39390 }, + { url = "https://files.pythonhosted.org/packages/0d/a9/4d1b795b50e6b54609fd7a63db8df30fa0480405b9a46cf8e336f5f28560/multidict-6.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0c7f9d0276ceaab41b8ae78534ff28ea33d5de85db551cbf80c44371f2b55d13", size = 38869 }, + { url = "https://files.pythonhosted.org/packages/e4/8c/854ee8ad8921335d0b4e740f373390d85d23f6b3956387562de5891ac503/multidict-6.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6eab22df44a25acab2e738f882f5ec551282ab45b2bbda5301e6d2cfb323036", size = 246911 }, + { url = "https://files.pythonhosted.org/packages/40/65/d6ae9fecb61d1c2fa86a2889f8b58dbfb91fa6a6d7754597e472c8523f6c/multidict-6.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a947cb7c657f57874021b9b70c7aac049c877fb576955a40afa8df71d01a1390", size = 251680 }, + { url = "https://files.pythonhosted.org/packages/a3/6c/098304889a699f5fbad8e74b723847a38d22547743baacdfcc8a17777b5b/multidict-6.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5faa346e8e1c371187cf345ab1e02a75889f9f510c9cbc575c31b779f7df084d", size = 246706 }, + { url = "https://files.pythonhosted.org/packages/da/9f/a58a04ac1d18f0a2431c48763a8948d0ce65f5911000cc425f8778eb6611/multidict-6.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc6e08d977aebf1718540533b4ba5b351ccec2db093370958a653b1f7f9219cc", size = 242359 }, + { url = "https://files.pythonhosted.org/packages/40/fd/3a76265f2748f718cc05f313c44440658ecd1939fa2b5e66087a5edd605f/multidict-6.3.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:98eab7acf55275b5bf09834125fa3a80b143a9f241cdcdd3f1295ffdc3c6d097", size = 229881 }, + { url = "https://files.pythonhosted.org/packages/22/a9/5780f71e34adf93443ec0660591d877367991badadab9cc6ac02d7a64760/multidict-6.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:36863655630becc224375c0b99364978a0f95aebfb27fb6dd500f7fb5fb36e79", size = 248520 }, + { url = "https://files.pythonhosted.org/packages/f3/72/10988db397e1e819b669213c76a41fde670ba60ecec2c05d5ecdea05526c/multidict-6.3.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d9c0979c096c0d46a963331b0e400d3a9e560e41219df4b35f0d7a2f28f39710", size = 237649 }, + { url = "https://files.pythonhosted.org/packages/29/75/52a7d3d1c0ffb2e8367f72845f309850113ea9201a50e4d4cdf8ac9f7d72/multidict-6.3.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:0efc04f70f05e70e5945890767e8874da5953a196f5b07c552d305afae0f3bf6", size = 251467 }, + { url = "https://files.pythonhosted.org/packages/82/24/e42400008eff60d4af53a2ff313abf0b2715fdd3a71b845d85025844f198/multidict-6.3.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:2c519b3b82c34539fae3e22e4ea965869ac6b628794b1eb487780dde37637ab7", size = 245310 }, + { url = "https://files.pythonhosted.org/packages/91/32/8b2e247539d4fdcc6cee36aa71c8898e0acd70e5d0e8a2ce9796a60790e5/multidict-6.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:329160e301f2afd7b43725d3dda8a7ef8ee41d4ceac2083fc0d8c1cc8a4bd56b", size = 243574 }, + { url = "https://files.pythonhosted.org/packages/d2/86/cc42cfa9b85b7d174948a17f828ebcacb0247e727fbedf06506ba93387ef/multidict-6.3.2-cp313-cp313t-win32.whl", hash = "sha256:420e5144a5f598dad8db3128f1695cd42a38a0026c2991091dab91697832f8cc", size = 41908 }, + { url = "https://files.pythonhosted.org/packages/2a/36/5c015523a7650fb5c55380d1c779b938379bd091968ee822d719e4264ab7/multidict-6.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:875faded2861c7af2682c67088e6313fec35ede811e071c96d36b081873cea14", size = 45635 }, + { url = "https://files.pythonhosted.org/packages/aa/c1/7832c95a50641148b567b5366dd3354489950dcfd01c8fc28472bec63b9a/multidict-6.3.2-py3-none-any.whl", hash = "sha256:71409d4579f716217f23be2f5e7afca5ca926aaeb398aa11b72d793bff637a1f", size = 10347 }, +] + +[[package]] +name = "multiprocess" +version = "0.70.16" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dill" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b5/ae/04f39c5d0d0def03247c2893d6f2b83c136bf3320a2154d7b8858f2ba72d/multiprocess-0.70.16.tar.gz", hash = "sha256:161af703d4652a0e1410be6abccecde4a7ddffd19341be0a7011b94aeb171ac1", size = 1772603 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/f7/7ec7fddc92e50714ea3745631f79bd9c96424cb2702632521028e57d3a36/multiprocess-0.70.16-py310-none-any.whl", hash = "sha256:c4a9944c67bd49f823687463660a2d6daae94c289adff97e0f9d696ba6371d02", size = 134824 }, + { url = "https://files.pythonhosted.org/packages/50/15/b56e50e8debaf439f44befec5b2af11db85f6e0f344c3113ae0be0593a91/multiprocess-0.70.16-py311-none-any.whl", hash = "sha256:af4cabb0dac72abfb1e794fa7855c325fd2b55a10a44628a3c1ad3311c04127a", size = 143519 }, + { url = "https://files.pythonhosted.org/packages/0a/7d/a988f258104dcd2ccf1ed40fdc97e26c4ac351eeaf81d76e266c52d84e2f/multiprocess-0.70.16-py312-none-any.whl", hash = "sha256:fc0544c531920dde3b00c29863377f87e1632601092ea2daca74e4beb40faa2e", size = 146741 }, + { url = "https://files.pythonhosted.org/packages/ea/89/38df130f2c799090c978b366cfdf5b96d08de5b29a4a293df7f7429fa50b/multiprocess-0.70.16-py38-none-any.whl", hash = "sha256:a71d82033454891091a226dfc319d0cfa8019a4e888ef9ca910372a446de4435", size = 132628 }, + { url = "https://files.pythonhosted.org/packages/da/d9/f7f9379981e39b8c2511c9e0326d212accacb82f12fbfdc1aa2ce2a7b2b6/multiprocess-0.70.16-py39-none-any.whl", hash = "sha256:a0bafd3ae1b732eac64be2e72038231c1ba97724b60b09400d68f229fcc2fbf3", size = 133351 }, +] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 }, +] + +[[package]] +name = "nbclient" +version = "0.10.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "nbformat" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/87/66/7ffd18d58eae90d5721f9f39212327695b749e23ad44b3881744eaf4d9e8/nbclient-0.10.2.tar.gz", hash = "sha256:90b7fc6b810630db87a6d0c2250b1f0ab4cf4d3c27a299b0cde78a4ed3fd9193", size = 62424 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl", hash = "sha256:4ffee11e788b4a27fabeb7955547e4318a5298f34342a4bfd01f2e1faaeadc3d", size = 25434 }, +] + +[[package]] +name = "nbconvert" +version = "7.16.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "bleach", extra = ["css"] }, + { name = "defusedxml" }, + { name = "jinja2" }, + { name = "jupyter-core" }, + { name = "jupyterlab-pygments" }, + { name = "markupsafe" }, + { name = "mistune" }, + { name = "nbclient" }, + { name = "nbformat" }, + { name = "packaging" }, + { name = "pandocfilters" }, + { name = "pygments" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/59/f28e15fc47ffb73af68a8d9b47367a8630d76e97ae85ad18271b9db96fdf/nbconvert-7.16.6.tar.gz", hash = "sha256:576a7e37c6480da7b8465eefa66c17844243816ce1ccc372633c6b71c3c0f582", size = 857715 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl", hash = "sha256:1375a7b67e0c2883678c48e506dc320febb57685e5ee67faa51b18a90f3a712b", size = 258525 }, +] + +[[package]] +name = "nbformat" +version = "5.10.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fastjsonschema" }, + { name = "jsonschema" }, + { name = "jupyter-core" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/fd/91545e604bc3dad7dca9ed03284086039b294c6b3d75c0d2fa45f9e9caf3/nbformat-5.10.4.tar.gz", hash = "sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a", size = 142749 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl", hash = "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b", size = 78454 }, +] + +[[package]] +name = "nest-asyncio" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195 }, +] + +[[package]] +name = "networkx" +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/1d/06475e1cd5264c0b870ea2cc6fdb3e37177c1e565c43f56ff17a10e3937f/networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1", size = 2151368 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263 }, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 }, +] + +[[package]] +name = "notebook" +version = "7.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-server" }, + { name = "jupyterlab" }, + { name = "jupyterlab-server" }, + { name = "notebook-shim" }, + { name = "tornado" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/7c/2dd051638502268da7a6784ae18bb40c2d7fcbea3581bf14078a168f9960/notebook-7.4.0.tar.gz", hash = "sha256:581d88f83709d90ce738dfd1d759892b96e3cbbc9c4a989912ed6c6a08f0d3e8", size = 13880491 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/d1/a8897aa74ac54409c4679e96e6d8b31d7187b2ce31596ae3ee95bee20e87/notebook-7.4.0-py3-none-any.whl", hash = "sha256:005fd21f4db6093a7b739b17df5fe60597811adb07e8255f458db4035d208e3a", size = 14281255 }, +] + +[[package]] +name = "notebook-shim" +version = "0.2.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-server" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/54/d2/92fa3243712b9a3e8bafaf60aac366da1cada3639ca767ff4b5b3654ec28/notebook_shim-0.2.4.tar.gz", hash = "sha256:b4b2cfa1b65d98307ca24361f5b30fe785b53c3fd07b7a47e89acb5e6ac638cb", size = 13167 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl", hash = "sha256:411a5be4e9dc882a074ccbcae671eda64cceb068767e9a3419096986560e1cef", size = 13307 }, +] + +[[package]] +name = "numpy" +version = "2.2.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/78/31103410a57bc2c2b93a3597340a8119588571f6a4539067546cb9a0bfac/numpy-2.2.4.tar.gz", hash = "sha256:9ba03692a45d3eef66559efe1d1096c4b9b75c0986b5dff5530c378fb8331d4f", size = 20270701 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/fb/09e778ee3a8ea0d4dc8329cca0a9c9e65fed847d08e37eba74cb7ed4b252/numpy-2.2.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e9e0a277bb2eb5d8a7407e14688b85fd8ad628ee4e0c7930415687b6564207a4", size = 21254989 }, + { url = "https://files.pythonhosted.org/packages/a2/0a/1212befdbecab5d80eca3cde47d304cad986ad4eec7d85a42e0b6d2cc2ef/numpy-2.2.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9eeea959168ea555e556b8188da5fa7831e21d91ce031e95ce23747b7609f8a4", size = 14425910 }, + { url = "https://files.pythonhosted.org/packages/2b/3e/e7247c1d4f15086bb106c8d43c925b0b2ea20270224f5186fa48d4fb5cbd/numpy-2.2.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:bd3ad3b0a40e713fc68f99ecfd07124195333f1e689387c180813f0e94309d6f", size = 5426490 }, + { url = "https://files.pythonhosted.org/packages/5d/fa/aa7cd6be51419b894c5787a8a93c3302a1ed4f82d35beb0613ec15bdd0e2/numpy-2.2.4-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cf28633d64294969c019c6df4ff37f5698e8326db68cc2b66576a51fad634880", size = 6967754 }, + { url = "https://files.pythonhosted.org/packages/d5/ee/96457c943265de9fadeb3d2ffdbab003f7fba13d971084a9876affcda095/numpy-2.2.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fa8fa7697ad1646b5c93de1719965844e004fcad23c91228aca1cf0800044a1", size = 14373079 }, + { url = "https://files.pythonhosted.org/packages/c5/5c/ceefca458559f0ccc7a982319f37ed07b0d7b526964ae6cc61f8ad1b6119/numpy-2.2.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4162988a360a29af158aeb4a2f4f09ffed6a969c9776f8f3bdee9b06a8ab7e5", size = 16428819 }, + { url = "https://files.pythonhosted.org/packages/22/31/9b2ac8eee99e001eb6add9fa27514ef5e9faf176169057a12860af52704c/numpy-2.2.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:892c10d6a73e0f14935c31229e03325a7b3093fafd6ce0af704be7f894d95687", size = 15881470 }, + { url = "https://files.pythonhosted.org/packages/f0/dc/8569b5f25ff30484b555ad8a3f537e0225d091abec386c9420cf5f7a2976/numpy-2.2.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db1f1c22173ac1c58db249ae48aa7ead29f534b9a948bc56828337aa84a32ed6", size = 18218144 }, + { url = "https://files.pythonhosted.org/packages/5e/05/463c023a39bdeb9bb43a99e7dee2c664cb68d5bb87d14f92482b9f6011cc/numpy-2.2.4-cp311-cp311-win32.whl", hash = "sha256:ea2bb7e2ae9e37d96835b3576a4fa4b3a97592fbea8ef7c3587078b0068b8f09", size = 6606368 }, + { url = "https://files.pythonhosted.org/packages/8b/72/10c1d2d82101c468a28adc35de6c77b308f288cfd0b88e1070f15b98e00c/numpy-2.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:f7de08cbe5551911886d1ab60de58448c6df0f67d9feb7d1fb21e9875ef95e91", size = 12947526 }, + { url = "https://files.pythonhosted.org/packages/a2/30/182db21d4f2a95904cec1a6f779479ea1ac07c0647f064dea454ec650c42/numpy-2.2.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a7b9084668aa0f64e64bd00d27ba5146ef1c3a8835f3bd912e7a9e01326804c4", size = 20947156 }, + { url = "https://files.pythonhosted.org/packages/24/6d/9483566acfbda6c62c6bc74b6e981c777229d2af93c8eb2469b26ac1b7bc/numpy-2.2.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dbe512c511956b893d2dacd007d955a3f03d555ae05cfa3ff1c1ff6df8851854", size = 14133092 }, + { url = "https://files.pythonhosted.org/packages/27/f6/dba8a258acbf9d2bed2525cdcbb9493ef9bae5199d7a9cb92ee7e9b2aea6/numpy-2.2.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:bb649f8b207ab07caebba230d851b579a3c8711a851d29efe15008e31bb4de24", size = 5163515 }, + { url = "https://files.pythonhosted.org/packages/62/30/82116199d1c249446723c68f2c9da40d7f062551036f50b8c4caa42ae252/numpy-2.2.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:f34dc300df798742b3d06515aa2a0aee20941c13579d7a2f2e10af01ae4901ee", size = 6696558 }, + { url = "https://files.pythonhosted.org/packages/0e/b2/54122b3c6df5df3e87582b2e9430f1bdb63af4023c739ba300164c9ae503/numpy-2.2.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3f7ac96b16955634e223b579a3e5798df59007ca43e8d451a0e6a50f6bfdfba", size = 14084742 }, + { url = "https://files.pythonhosted.org/packages/02/e2/e2cbb8d634151aab9528ef7b8bab52ee4ab10e076509285602c2a3a686e0/numpy-2.2.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f92084defa704deadd4e0a5ab1dc52d8ac9e8a8ef617f3fbb853e79b0ea3592", size = 16134051 }, + { url = "https://files.pythonhosted.org/packages/8e/21/efd47800e4affc993e8be50c1b768de038363dd88865920439ef7b422c60/numpy-2.2.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4e84a6283b36632e2a5b56e121961f6542ab886bc9e12f8f9818b3c266bfbb", size = 15578972 }, + { url = "https://files.pythonhosted.org/packages/04/1e/f8bb88f6157045dd5d9b27ccf433d016981032690969aa5c19e332b138c0/numpy-2.2.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:11c43995255eb4127115956495f43e9343736edb7fcdb0d973defd9de14cd84f", size = 17898106 }, + { url = "https://files.pythonhosted.org/packages/2b/93/df59a5a3897c1f036ae8ff845e45f4081bb06943039ae28a3c1c7c780f22/numpy-2.2.4-cp312-cp312-win32.whl", hash = "sha256:65ef3468b53269eb5fdb3a5c09508c032b793da03251d5f8722b1194f1790c00", size = 6311190 }, + { url = "https://files.pythonhosted.org/packages/46/69/8c4f928741c2a8efa255fdc7e9097527c6dc4e4df147e3cadc5d9357ce85/numpy-2.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:2aad3c17ed2ff455b8eaafe06bcdae0062a1db77cb99f4b9cbb5f4ecb13c5146", size = 12644305 }, + { url = "https://files.pythonhosted.org/packages/2a/d0/bd5ad792e78017f5decfb2ecc947422a3669a34f775679a76317af671ffc/numpy-2.2.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cf4e5c6a278d620dee9ddeb487dc6a860f9b199eadeecc567f777daace1e9e7", size = 20933623 }, + { url = "https://files.pythonhosted.org/packages/c3/bc/2b3545766337b95409868f8e62053135bdc7fa2ce630aba983a2aa60b559/numpy-2.2.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1974afec0b479e50438fc3648974268f972e2d908ddb6d7fb634598cdb8260a0", size = 14148681 }, + { url = "https://files.pythonhosted.org/packages/6a/70/67b24d68a56551d43a6ec9fe8c5f91b526d4c1a46a6387b956bf2d64744e/numpy-2.2.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:79bd5f0a02aa16808fcbc79a9a376a147cc1045f7dfe44c6e7d53fa8b8a79392", size = 5148759 }, + { url = "https://files.pythonhosted.org/packages/1c/8b/e2fc8a75fcb7be12d90b31477c9356c0cbb44abce7ffb36be39a0017afad/numpy-2.2.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:3387dd7232804b341165cedcb90694565a6015433ee076c6754775e85d86f1fc", size = 6683092 }, + { url = "https://files.pythonhosted.org/packages/13/73/41b7b27f169ecf368b52533edb72e56a133f9e86256e809e169362553b49/numpy-2.2.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f527d8fdb0286fd2fd97a2a96c6be17ba4232da346931d967a0630050dfd298", size = 14081422 }, + { url = "https://files.pythonhosted.org/packages/4b/04/e208ff3ae3ddfbafc05910f89546382f15a3f10186b1f56bd99f159689c2/numpy-2.2.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bce43e386c16898b91e162e5baaad90c4b06f9dcbe36282490032cec98dc8ae7", size = 16132202 }, + { url = "https://files.pythonhosted.org/packages/fe/bc/2218160574d862d5e55f803d88ddcad88beff94791f9c5f86d67bd8fbf1c/numpy-2.2.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:31504f970f563d99f71a3512d0c01a645b692b12a63630d6aafa0939e52361e6", size = 15573131 }, + { url = "https://files.pythonhosted.org/packages/a5/78/97c775bc4f05abc8a8426436b7cb1be806a02a2994b195945600855e3a25/numpy-2.2.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:81413336ef121a6ba746892fad881a83351ee3e1e4011f52e97fba79233611fd", size = 17894270 }, + { url = "https://files.pythonhosted.org/packages/b9/eb/38c06217a5f6de27dcb41524ca95a44e395e6a1decdc0c99fec0832ce6ae/numpy-2.2.4-cp313-cp313-win32.whl", hash = "sha256:f486038e44caa08dbd97275a9a35a283a8f1d2f0ee60ac260a1790e76660833c", size = 6308141 }, + { url = "https://files.pythonhosted.org/packages/52/17/d0dd10ab6d125c6d11ffb6dfa3423c3571befab8358d4f85cd4471964fcd/numpy-2.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:207a2b8441cc8b6a2a78c9ddc64d00d20c303d79fba08c577752f080c4007ee3", size = 12636885 }, + { url = "https://files.pythonhosted.org/packages/fa/e2/793288ede17a0fdc921172916efb40f3cbc2aa97e76c5c84aba6dc7e8747/numpy-2.2.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8120575cb4882318c791f839a4fd66161a6fa46f3f0a5e613071aae35b5dd8f8", size = 20961829 }, + { url = "https://files.pythonhosted.org/packages/3a/75/bb4573f6c462afd1ea5cbedcc362fe3e9bdbcc57aefd37c681be1155fbaa/numpy-2.2.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a761ba0fa886a7bb33c6c8f6f20213735cb19642c580a931c625ee377ee8bd39", size = 14161419 }, + { url = "https://files.pythonhosted.org/packages/03/68/07b4cd01090ca46c7a336958b413cdbe75002286295f2addea767b7f16c9/numpy-2.2.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:ac0280f1ba4a4bfff363a99a6aceed4f8e123f8a9b234c89140f5e894e452ecd", size = 5196414 }, + { url = "https://files.pythonhosted.org/packages/a5/fd/d4a29478d622fedff5c4b4b4cedfc37a00691079623c0575978d2446db9e/numpy-2.2.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:879cf3a9a2b53a4672a168c21375166171bc3932b7e21f622201811c43cdd3b0", size = 6709379 }, + { url = "https://files.pythonhosted.org/packages/41/78/96dddb75bb9be730b87c72f30ffdd62611aba234e4e460576a068c98eff6/numpy-2.2.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f05d4198c1bacc9124018109c5fba2f3201dbe7ab6e92ff100494f236209c960", size = 14051725 }, + { url = "https://files.pythonhosted.org/packages/00/06/5306b8199bffac2a29d9119c11f457f6c7d41115a335b78d3f86fad4dbe8/numpy-2.2.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f085ce2e813a50dfd0e01fbfc0c12bbe5d2063d99f8b29da30e544fb6483b8", size = 16101638 }, + { url = "https://files.pythonhosted.org/packages/fa/03/74c5b631ee1ded596945c12027649e6344614144369fd3ec1aaced782882/numpy-2.2.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:92bda934a791c01d6d9d8e038363c50918ef7c40601552a58ac84c9613a665bc", size = 15571717 }, + { url = "https://files.pythonhosted.org/packages/cb/dc/4fc7c0283abe0981e3b89f9b332a134e237dd476b0c018e1e21083310c31/numpy-2.2.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ee4d528022f4c5ff67332469e10efe06a267e32f4067dc76bb7e2cddf3cd25ff", size = 17879998 }, + { url = "https://files.pythonhosted.org/packages/e5/2b/878576190c5cfa29ed896b518cc516aecc7c98a919e20706c12480465f43/numpy-2.2.4-cp313-cp313t-win32.whl", hash = "sha256:05c076d531e9998e7e694c36e8b349969c56eadd2cdcd07242958489d79a7286", size = 6366896 }, + { url = "https://files.pythonhosted.org/packages/3e/05/eb7eec66b95cf697f08c754ef26c3549d03ebd682819f794cb039574a0a6/numpy-2.2.4-cp313-cp313t-win_amd64.whl", hash = "sha256:188dcbca89834cc2e14eb2f106c96d6d46f200fe0200310fc29089657379c58d", size = 12739119 }, +] + +[[package]] +name = "nvidia-cublas-cu12" +version = "12.4.5.8" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ae/71/1c91302526c45ab494c23f61c7a84aa568b8c1f9d196efa5993957faf906/nvidia_cublas_cu12-12.4.5.8-py3-none-manylinux2014_x86_64.whl", hash = "sha256:2fc8da60df463fdefa81e323eef2e36489e1c94335b5358bcb38360adf75ac9b", size = 363438805 }, +] + +[[package]] +name = "nvidia-cuda-cupti-cu12" +version = "12.4.127" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/42/f4f60238e8194a3106d06a058d494b18e006c10bb2b915655bd9f6ea4cb1/nvidia_cuda_cupti_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:9dec60f5ac126f7bb551c055072b69d85392b13311fcc1bcda2202d172df30fb", size = 13813957 }, +] + +[[package]] +name = "nvidia-cuda-nvrtc-cu12" +version = "12.4.127" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/14/91ae57cd4db3f9ef7aa99f4019cfa8d54cb4caa7e00975df6467e9725a9f/nvidia_cuda_nvrtc_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a178759ebb095827bd30ef56598ec182b85547f1508941a3d560eb7ea1fbf338", size = 24640306 }, +] + +[[package]] +name = "nvidia-cuda-runtime-cu12" +version = "12.4.127" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/27/1795d86fe88ef397885f2e580ac37628ed058a92ed2c39dc8eac3adf0619/nvidia_cuda_runtime_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:64403288fa2136ee8e467cdc9c9427e0434110899d07c779f25b5c068934faa5", size = 883737 }, +] + +[[package]] +name = "nvidia-cudnn-cu12" +version = "9.1.0.70" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas-cu12", marker = "sys_platform == 'linux'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/fd/713452cd72343f682b1c7b9321e23829f00b842ceaedcda96e742ea0b0b3/nvidia_cudnn_cu12-9.1.0.70-py3-none-manylinux2014_x86_64.whl", hash = "sha256:165764f44ef8c61fcdfdfdbe769d687e06374059fbb388b6c89ecb0e28793a6f", size = 664752741 }, +] + +[[package]] +name = "nvidia-cufft-cu12" +version = "11.2.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink-cu12", marker = "sys_platform == 'linux'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/94/3266821f65b92b3138631e9c8e7fe1fb513804ac934485a8d05776e1dd43/nvidia_cufft_cu12-11.2.1.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f083fc24912aa410be21fa16d157fed2055dab1cc4b6934a0e03cba69eb242b9", size = 211459117 }, +] + +[[package]] +name = "nvidia-curand-cu12" +version = "10.3.5.147" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/6d/44ad094874c6f1b9c654f8ed939590bdc408349f137f9b98a3a23ccec411/nvidia_curand_cu12-10.3.5.147-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a88f583d4e0bb643c49743469964103aa59f7f708d862c3ddb0fc07f851e3b8b", size = 56305206 }, +] + +[[package]] +name = "nvidia-cusolver-cu12" +version = "11.6.1.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas-cu12", marker = "sys_platform == 'linux'" }, + { name = "nvidia-cusparse-cu12", marker = "sys_platform == 'linux'" }, + { name = "nvidia-nvjitlink-cu12", marker = "sys_platform == 'linux'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/e1/5b9089a4b2a4790dfdea8b3a006052cfecff58139d5a4e34cb1a51df8d6f/nvidia_cusolver_cu12-11.6.1.9-py3-none-manylinux2014_x86_64.whl", hash = "sha256:19e33fa442bcfd085b3086c4ebf7e8debc07cfe01e11513cc6d332fd918ac260", size = 127936057 }, +] + +[[package]] +name = "nvidia-cusparse-cu12" +version = "12.3.1.170" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink-cu12", marker = "sys_platform == 'linux'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/f7/97a9ea26ed4bbbfc2d470994b8b4f338ef663be97b8f677519ac195e113d/nvidia_cusparse_cu12-12.3.1.170-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ea4f11a2904e2a8dc4b1833cc1b5181cde564edd0d5cd33e3c168eff2d1863f1", size = 207454763 }, +] + +[[package]] +name = "nvidia-cusparselt-cu12" +version = "0.6.2" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/a8/bcbb63b53a4b1234feeafb65544ee55495e1bb37ec31b999b963cbccfd1d/nvidia_cusparselt_cu12-0.6.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:df2c24502fd76ebafe7457dbc4716b2fec071aabaed4fb7691a201cde03704d9", size = 150057751 }, +] + +[[package]] +name = "nvidia-nccl-cu12" +version = "2.21.5" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/99/12cd266d6233f47d00daf3a72739872bdc10267d0383508b0b9c84a18bb6/nvidia_nccl_cu12-2.21.5-py3-none-manylinux2014_x86_64.whl", hash = "sha256:8579076d30a8c24988834445f8d633c697d42397e92ffc3f63fa26766d25e0a0", size = 188654414 }, +] + +[[package]] +name = "nvidia-nvjitlink-cu12" +version = "12.4.127" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/ff/847841bacfbefc97a00036e0fce5a0f086b640756dc38caea5e1bb002655/nvidia_nvjitlink_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:06b3b9b25bf3f8af351d664978ca26a16d2c5127dbd53c0497e28d1fb9611d57", size = 21066810 }, +] + +[[package]] +name = "nvidia-nvtx-cu12" +version = "12.4.127" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/20/199b8713428322a2f22b722c62b8cc278cc53dffa9705d744484b5035ee9/nvidia_nvtx_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:781e950d9b9f60d8241ccea575b32f5105a5baf4c2351cab5256a24869f12a1a", size = 99144 }, +] + +[[package]] +name = "omegaconf" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "antlr4-python3-runtime" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/09/48/6388f1bb9da707110532cb70ec4d2822858ddfb44f1cdf1233c20a80ea4b/omegaconf-2.3.0.tar.gz", hash = "sha256:d5d4b6d29955cc50ad50c46dc269bcd92c6e00f5f90d23ab5fee7bfca4ba4cc7", size = 3298120 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/94/1843518e420fa3ed6919835845df698c7e27e183cb997394e4a670973a65/omegaconf-2.3.0-py3-none-any.whl", hash = "sha256:7b4df175cdb08ba400f45cae3bdcae7ba8365db4d165fc65fd04b050ab63b46b", size = 79500 }, +] + +[[package]] +name = "overrides" +version = "7.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/36/86/b585f53236dec60aba864e050778b25045f857e17f6e5ea0ae95fe80edd2/overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a", size = 22812 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49", size = 17832 }, +] + +[[package]] +name = "packaging" +version = "24.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, +] + +[[package]] +name = "pandas" +version = "2.2.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9c/d6/9f8431bacc2e19dca897724cd097b1bb224a6ad5433784a44b587c7c13af/pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667", size = 4399213 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/44/d9502bf0ed197ba9bf1103c9867d5904ddcaf869e52329787fc54ed70cc8/pandas-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66108071e1b935240e74525006034333f98bcdb87ea116de573a6a0dccb6c039", size = 12602222 }, + { url = "https://files.pythonhosted.org/packages/52/11/9eac327a38834f162b8250aab32a6781339c69afe7574368fffe46387edf/pandas-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c2875855b0ff77b2a64a0365e24455d9990730d6431b9e0ee18ad8acee13dbd", size = 11321274 }, + { url = "https://files.pythonhosted.org/packages/45/fb/c4beeb084718598ba19aa9f5abbc8aed8b42f90930da861fcb1acdb54c3a/pandas-2.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd8d0c3be0515c12fed0bdbae072551c8b54b7192c7b1fda0ba56059a0179698", size = 15579836 }, + { url = "https://files.pythonhosted.org/packages/cd/5f/4dba1d39bb9c38d574a9a22548c540177f78ea47b32f99c0ff2ec499fac5/pandas-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c124333816c3a9b03fbeef3a9f230ba9a737e9e5bb4060aa2107a86cc0a497fc", size = 13058505 }, + { url = "https://files.pythonhosted.org/packages/b9/57/708135b90391995361636634df1f1130d03ba456e95bcf576fada459115a/pandas-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:63cc132e40a2e084cf01adf0775b15ac515ba905d7dcca47e9a251819c575ef3", size = 16744420 }, + { url = "https://files.pythonhosted.org/packages/86/4a/03ed6b7ee323cf30404265c284cee9c65c56a212e0a08d9ee06984ba2240/pandas-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29401dbfa9ad77319367d36940cd8a0b3a11aba16063e39632d98b0e931ddf32", size = 14440457 }, + { url = "https://files.pythonhosted.org/packages/ed/8c/87ddf1fcb55d11f9f847e3c69bb1c6f8e46e2f40ab1a2d2abadb2401b007/pandas-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:3fc6873a41186404dad67245896a6e440baacc92f5b716ccd1bc9ed2995ab2c5", size = 11617166 }, + { url = "https://files.pythonhosted.org/packages/17/a3/fb2734118db0af37ea7433f57f722c0a56687e14b14690edff0cdb4b7e58/pandas-2.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b1d432e8d08679a40e2a6d8b2f9770a5c21793a6f9f47fdd52c5ce1948a5a8a9", size = 12529893 }, + { url = "https://files.pythonhosted.org/packages/e1/0c/ad295fd74bfac85358fd579e271cded3ac969de81f62dd0142c426b9da91/pandas-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5a1595fe639f5988ba6a8e5bc9649af3baf26df3998a0abe56c02609392e0a4", size = 11363475 }, + { url = "https://files.pythonhosted.org/packages/c6/2a/4bba3f03f7d07207481fed47f5b35f556c7441acddc368ec43d6643c5777/pandas-2.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5de54125a92bb4d1c051c0659e6fcb75256bf799a732a87184e5ea503965bce3", size = 15188645 }, + { url = "https://files.pythonhosted.org/packages/38/f8/d8fddee9ed0d0c0f4a2132c1dfcf0e3e53265055da8df952a53e7eaf178c/pandas-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb8ae78d8af97f849404f21411c95062db1496aeb3e56f146f0355c9989319", size = 12739445 }, + { url = "https://files.pythonhosted.org/packages/20/e8/45a05d9c39d2cea61ab175dbe6a2de1d05b679e8de2011da4ee190d7e748/pandas-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfcb5ee8d4d50c06a51c2fffa6cff6272098ad6540aed1a76d15fb9318194d8", size = 16359235 }, + { url = "https://files.pythonhosted.org/packages/1d/99/617d07a6a5e429ff90c90da64d428516605a1ec7d7bea494235e1c3882de/pandas-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:062309c1b9ea12a50e8ce661145c6aab431b1e99530d3cd60640e255778bd43a", size = 14056756 }, + { url = "https://files.pythonhosted.org/packages/29/d4/1244ab8edf173a10fd601f7e13b9566c1b525c4f365d6bee918e68381889/pandas-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:59ef3764d0fe818125a5097d2ae867ca3fa64df032331b7e0917cf5d7bf66b13", size = 11504248 }, + { url = "https://files.pythonhosted.org/packages/64/22/3b8f4e0ed70644e85cfdcd57454686b9057c6c38d2f74fe4b8bc2527214a/pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015", size = 12477643 }, + { url = "https://files.pythonhosted.org/packages/e4/93/b3f5d1838500e22c8d793625da672f3eec046b1a99257666c94446969282/pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28", size = 11281573 }, + { url = "https://files.pythonhosted.org/packages/f5/94/6c79b07f0e5aab1dcfa35a75f4817f5c4f677931d4234afcd75f0e6a66ca/pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0", size = 15196085 }, + { url = "https://files.pythonhosted.org/packages/e8/31/aa8da88ca0eadbabd0a639788a6da13bb2ff6edbbb9f29aa786450a30a91/pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24", size = 12711809 }, + { url = "https://files.pythonhosted.org/packages/ee/7c/c6dbdb0cb2a4344cacfb8de1c5808ca885b2e4dcfde8008266608f9372af/pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659", size = 16356316 }, + { url = "https://files.pythonhosted.org/packages/57/b7/8b757e7d92023b832869fa8881a992696a0bfe2e26f72c9ae9f255988d42/pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb", size = 14022055 }, + { url = "https://files.pythonhosted.org/packages/3b/bc/4b18e2b8c002572c5a441a64826252ce5da2aa738855747247a971988043/pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d", size = 11481175 }, + { url = "https://files.pythonhosted.org/packages/76/a3/a5d88146815e972d40d19247b2c162e88213ef51c7c25993942c39dbf41d/pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468", size = 12615650 }, + { url = "https://files.pythonhosted.org/packages/9c/8c/f0fd18f6140ddafc0c24122c8a964e48294acc579d47def376fef12bcb4a/pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18", size = 11290177 }, + { url = "https://files.pythonhosted.org/packages/ed/f9/e995754eab9c0f14c6777401f7eece0943840b7a9fc932221c19d1abee9f/pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2", size = 14651526 }, + { url = "https://files.pythonhosted.org/packages/25/b0/98d6ae2e1abac4f35230aa756005e8654649d305df9a28b16b9ae4353bff/pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4", size = 11871013 }, + { url = "https://files.pythonhosted.org/packages/cc/57/0f72a10f9db6a4628744c8e8f0df4e6e21de01212c7c981d31e50ffc8328/pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d", size = 15711620 }, + { url = "https://files.pythonhosted.org/packages/ab/5f/b38085618b950b79d2d9164a711c52b10aefc0ae6833b96f626b7021b2ed/pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a", size = 13098436 }, +] + +[[package]] +name = "pandocfilters" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/70/6f/3dd4940bbe001c06a65f88e36bad298bc7a0de5036115639926b0c5c0458/pandocfilters-1.5.1.tar.gz", hash = "sha256:002b4a555ee4ebc03f8b66307e287fa492e4a77b4ea14d3f934328297bb4939e", size = 8454 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl", hash = "sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc", size = 8663 }, +] + +[[package]] +name = "parso" +version = "0.8.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/94/68e2e17afaa9169cf6412ab0f28623903be73d1b32e208d9e8e541bb086d/parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d", size = 400609 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/ac/dac4a63f978e4dcb3c6d3a78c4d8e0192a113d288502a1216950c41b1027/parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", size = 103650 }, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 }, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772 }, +] + +[[package]] +name = "pillow" +version = "11.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/af/c097e544e7bd278333db77933e535098c259609c4eb3b85381109602fb5b/pillow-11.1.0.tar.gz", hash = "sha256:368da70808b36d73b4b390a8ffac11069f8a5c85f29eff1f1b01bcf3ef5b2a20", size = 46742715 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dd/d6/2000bfd8d5414fb70cbbe52c8332f2283ff30ed66a9cde42716c8ecbe22c/pillow-11.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:e06695e0326d05b06833b40b7ef477e475d0b1ba3a6d27da1bb48c23209bf457", size = 3229968 }, + { url = "https://files.pythonhosted.org/packages/d9/45/3fe487010dd9ce0a06adf9b8ff4f273cc0a44536e234b0fad3532a42c15b/pillow-11.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96f82000e12f23e4f29346e42702b6ed9a2f2fea34a740dd5ffffcc8c539eb35", size = 3101806 }, + { url = "https://files.pythonhosted.org/packages/e3/72/776b3629c47d9d5f1c160113158a7a7ad177688d3a1159cd3b62ded5a33a/pillow-11.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3cd561ded2cf2bbae44d4605837221b987c216cff94f49dfeed63488bb228d2", size = 4322283 }, + { url = "https://files.pythonhosted.org/packages/e4/c2/e25199e7e4e71d64eeb869f5b72c7ddec70e0a87926398785ab944d92375/pillow-11.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f189805c8be5ca5add39e6f899e6ce2ed824e65fb45f3c28cb2841911da19070", size = 4402945 }, + { url = "https://files.pythonhosted.org/packages/c1/ed/51d6136c9d5911f78632b1b86c45241c712c5a80ed7fa7f9120a5dff1eba/pillow-11.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:dd0052e9db3474df30433f83a71b9b23bd9e4ef1de13d92df21a52c0303b8ab6", size = 4361228 }, + { url = "https://files.pythonhosted.org/packages/48/a4/fbfe9d5581d7b111b28f1d8c2762dee92e9821bb209af9fa83c940e507a0/pillow-11.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:837060a8599b8f5d402e97197d4924f05a2e0d68756998345c829c33186217b1", size = 4484021 }, + { url = "https://files.pythonhosted.org/packages/39/db/0b3c1a5018117f3c1d4df671fb8e47d08937f27519e8614bbe86153b65a5/pillow-11.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aa8dd43daa836b9a8128dbe7d923423e5ad86f50a7a14dc688194b7be5c0dea2", size = 4287449 }, + { url = "https://files.pythonhosted.org/packages/d9/58/bc128da7fea8c89fc85e09f773c4901e95b5936000e6f303222490c052f3/pillow-11.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0a2f91f8a8b367e7a57c6e91cd25af510168091fb89ec5146003e424e1558a96", size = 4419972 }, + { url = "https://files.pythonhosted.org/packages/5f/bb/58f34379bde9fe197f51841c5bbe8830c28bbb6d3801f16a83b8f2ad37df/pillow-11.1.0-cp311-cp311-win32.whl", hash = "sha256:c12fc111ef090845de2bb15009372175d76ac99969bdf31e2ce9b42e4b8cd88f", size = 2291201 }, + { url = "https://files.pythonhosted.org/packages/3a/c6/fce9255272bcf0c39e15abd2f8fd8429a954cf344469eaceb9d0d1366913/pillow-11.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fbd43429d0d7ed6533b25fc993861b8fd512c42d04514a0dd6337fb3ccf22761", size = 2625686 }, + { url = "https://files.pythonhosted.org/packages/c8/52/8ba066d569d932365509054859f74f2a9abee273edcef5cd75e4bc3e831e/pillow-11.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:f7955ecf5609dee9442cbface754f2c6e541d9e6eda87fad7f7a989b0bdb9d71", size = 2375194 }, + { url = "https://files.pythonhosted.org/packages/95/20/9ce6ed62c91c073fcaa23d216e68289e19d95fb8188b9fb7a63d36771db8/pillow-11.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2062ffb1d36544d42fcaa277b069c88b01bb7298f4efa06731a7fd6cc290b81a", size = 3226818 }, + { url = "https://files.pythonhosted.org/packages/b9/d8/f6004d98579a2596c098d1e30d10b248798cceff82d2b77aa914875bfea1/pillow-11.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a85b653980faad27e88b141348707ceeef8a1186f75ecc600c395dcac19f385b", size = 3101662 }, + { url = "https://files.pythonhosted.org/packages/08/d9/892e705f90051c7a2574d9f24579c9e100c828700d78a63239676f960b74/pillow-11.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9409c080586d1f683df3f184f20e36fb647f2e0bc3988094d4fd8c9f4eb1b3b3", size = 4329317 }, + { url = "https://files.pythonhosted.org/packages/8c/aa/7f29711f26680eab0bcd3ecdd6d23ed6bce180d82e3f6380fb7ae35fcf3b/pillow-11.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fdadc077553621911f27ce206ffcbec7d3f8d7b50e0da39f10997e8e2bb7f6a", size = 4412999 }, + { url = "https://files.pythonhosted.org/packages/c8/c4/8f0fe3b9e0f7196f6d0bbb151f9fba323d72a41da068610c4c960b16632a/pillow-11.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:93a18841d09bcdd774dcdc308e4537e1f867b3dec059c131fde0327899734aa1", size = 4368819 }, + { url = "https://files.pythonhosted.org/packages/38/0d/84200ed6a871ce386ddc82904bfadc0c6b28b0c0ec78176871a4679e40b3/pillow-11.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9aa9aeddeed452b2f616ff5507459e7bab436916ccb10961c4a382cd3e03f47f", size = 4496081 }, + { url = "https://files.pythonhosted.org/packages/84/9c/9bcd66f714d7e25b64118e3952d52841a4babc6d97b6d28e2261c52045d4/pillow-11.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3cdcdb0b896e981678eee140d882b70092dac83ac1cdf6b3a60e2216a73f2b91", size = 4296513 }, + { url = "https://files.pythonhosted.org/packages/db/61/ada2a226e22da011b45f7104c95ebda1b63dcbb0c378ad0f7c2a710f8fd2/pillow-11.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:36ba10b9cb413e7c7dfa3e189aba252deee0602c86c309799da5a74009ac7a1c", size = 4431298 }, + { url = "https://files.pythonhosted.org/packages/e7/c4/fc6e86750523f367923522014b821c11ebc5ad402e659d8c9d09b3c9d70c/pillow-11.1.0-cp312-cp312-win32.whl", hash = "sha256:cfd5cd998c2e36a862d0e27b2df63237e67273f2fc78f47445b14e73a810e7e6", size = 2291630 }, + { url = "https://files.pythonhosted.org/packages/08/5c/2104299949b9d504baf3f4d35f73dbd14ef31bbd1ddc2c1b66a5b7dfda44/pillow-11.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:a697cd8ba0383bba3d2d3ada02b34ed268cb548b369943cd349007730c92bddf", size = 2626369 }, + { url = "https://files.pythonhosted.org/packages/37/f3/9b18362206b244167c958984b57c7f70a0289bfb59a530dd8af5f699b910/pillow-11.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:4dd43a78897793f60766563969442020e90eb7847463eca901e41ba186a7d4a5", size = 2375240 }, + { url = "https://files.pythonhosted.org/packages/b3/31/9ca79cafdce364fd5c980cd3416c20ce1bebd235b470d262f9d24d810184/pillow-11.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ae98e14432d458fc3de11a77ccb3ae65ddce70f730e7c76140653048c71bfcbc", size = 3226640 }, + { url = "https://files.pythonhosted.org/packages/ac/0f/ff07ad45a1f172a497aa393b13a9d81a32e1477ef0e869d030e3c1532521/pillow-11.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cc1331b6d5a6e144aeb5e626f4375f5b7ae9934ba620c0ac6b3e43d5e683a0f0", size = 3101437 }, + { url = "https://files.pythonhosted.org/packages/08/2f/9906fca87a68d29ec4530be1f893149e0cb64a86d1f9f70a7cfcdfe8ae44/pillow-11.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:758e9d4ef15d3560214cddbc97b8ef3ef86ce04d62ddac17ad39ba87e89bd3b1", size = 4326605 }, + { url = "https://files.pythonhosted.org/packages/b0/0f/f3547ee15b145bc5c8b336401b2d4c9d9da67da9dcb572d7c0d4103d2c69/pillow-11.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b523466b1a31d0dcef7c5be1f20b942919b62fd6e9a9be199d035509cbefc0ec", size = 4411173 }, + { url = "https://files.pythonhosted.org/packages/b1/df/bf8176aa5db515c5de584c5e00df9bab0713548fd780c82a86cba2c2fedb/pillow-11.1.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:9044b5e4f7083f209c4e35aa5dd54b1dd5b112b108648f5c902ad586d4f945c5", size = 4369145 }, + { url = "https://files.pythonhosted.org/packages/de/7c/7433122d1cfadc740f577cb55526fdc39129a648ac65ce64db2eb7209277/pillow-11.1.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:3764d53e09cdedd91bee65c2527815d315c6b90d7b8b79759cc48d7bf5d4f114", size = 4496340 }, + { url = "https://files.pythonhosted.org/packages/25/46/dd94b93ca6bd555588835f2504bd90c00d5438fe131cf01cfa0c5131a19d/pillow-11.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:31eba6bbdd27dde97b0174ddf0297d7a9c3a507a8a1480e1e60ef914fe23d352", size = 4296906 }, + { url = "https://files.pythonhosted.org/packages/a8/28/2f9d32014dfc7753e586db9add35b8a41b7a3b46540e965cb6d6bc607bd2/pillow-11.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b5d658fbd9f0d6eea113aea286b21d3cd4d3fd978157cbf2447a6035916506d3", size = 4431759 }, + { url = "https://files.pythonhosted.org/packages/33/48/19c2cbe7403870fbe8b7737d19eb013f46299cdfe4501573367f6396c775/pillow-11.1.0-cp313-cp313-win32.whl", hash = "sha256:f86d3a7a9af5d826744fabf4afd15b9dfef44fe69a98541f666f66fbb8d3fef9", size = 2291657 }, + { url = "https://files.pythonhosted.org/packages/3b/ad/285c556747d34c399f332ba7c1a595ba245796ef3e22eae190f5364bb62b/pillow-11.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:593c5fd6be85da83656b93ffcccc2312d2d149d251e98588b14fbc288fd8909c", size = 2626304 }, + { url = "https://files.pythonhosted.org/packages/e5/7b/ef35a71163bf36db06e9c8729608f78dedf032fc8313d19bd4be5c2588f3/pillow-11.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:11633d58b6ee5733bde153a8dafd25e505ea3d32e261accd388827ee987baf65", size = 2375117 }, + { url = "https://files.pythonhosted.org/packages/79/30/77f54228401e84d6791354888549b45824ab0ffde659bafa67956303a09f/pillow-11.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:70ca5ef3b3b1c4a0812b5c63c57c23b63e53bc38e758b37a951e5bc466449861", size = 3230060 }, + { url = "https://files.pythonhosted.org/packages/ce/b1/56723b74b07dd64c1010fee011951ea9c35a43d8020acd03111f14298225/pillow-11.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8000376f139d4d38d6851eb149b321a52bb8893a88dae8ee7d95840431977081", size = 3106192 }, + { url = "https://files.pythonhosted.org/packages/e1/cd/7bf7180e08f80a4dcc6b4c3a0aa9e0b0ae57168562726a05dc8aa8fa66b0/pillow-11.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ee85f0696a17dd28fbcfceb59f9510aa71934b483d1f5601d1030c3c8304f3c", size = 4446805 }, + { url = "https://files.pythonhosted.org/packages/97/42/87c856ea30c8ed97e8efbe672b58c8304dee0573f8c7cab62ae9e31db6ae/pillow-11.1.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:dd0e081319328928531df7a0e63621caf67652c8464303fd102141b785ef9547", size = 4530623 }, + { url = "https://files.pythonhosted.org/packages/ff/41/026879e90c84a88e33fb00cc6bd915ac2743c67e87a18f80270dfe3c2041/pillow-11.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e63e4e5081de46517099dc30abe418122f54531a6ae2ebc8680bcd7096860eab", size = 4465191 }, + { url = "https://files.pythonhosted.org/packages/e5/fb/a7960e838bc5df57a2ce23183bfd2290d97c33028b96bde332a9057834d3/pillow-11.1.0-cp313-cp313t-win32.whl", hash = "sha256:dda60aa465b861324e65a78c9f5cf0f4bc713e4309f83bc387be158b077963d9", size = 2295494 }, + { url = "https://files.pythonhosted.org/packages/d7/6c/6ec83ee2f6f0fda8d4cf89045c6be4b0373ebfc363ba8538f8c999f63fcd/pillow-11.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ad5db5781c774ab9a9b2c4302bbf0c1014960a0a7be63278d13ae6fdf88126fe", size = 2631595 }, + { url = "https://files.pythonhosted.org/packages/cf/6c/41c21c6c8af92b9fea313aa47c75de49e2f9a467964ee33eb0135d47eb64/pillow-11.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:67cd427c68926108778a9005f2a04adbd5e67c442ed21d95389fe1d595458756", size = 2377651 }, +] + +[[package]] +name = "platformdirs" +version = "4.3.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/2d/7d512a3913d60623e7eb945c6d1b4f0bddf1d0b7ada5225274c87e5b53d1/platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351", size = 21291 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/45/59578566b3275b8fd9157885918fcd0c4d74162928a5310926887b856a51/platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94", size = 18499 }, +] + +[[package]] +name = "pre-commit" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/08/39/679ca9b26c7bb2999ff122d50faa301e49af82ca9c066ec061cfbc0c6784/pre_commit-4.2.0.tar.gz", hash = "sha256:601283b9757afd87d40c4c4a9b2b5de9637a8ea02eaff7adc2d0fb4e04841146", size = 193424 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/74/a88bf1b1efeae488a0c0b7bdf71429c313722d1fc0f377537fbe554e6180/pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd", size = 220707 }, +] + +[[package]] +name = "prometheus-client" +version = "0.21.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/62/14/7d0f567991f3a9af8d1cd4f619040c93b68f09a02b6d0b6ab1b2d1ded5fe/prometheus_client-0.21.1.tar.gz", hash = "sha256:252505a722ac04b0456be05c05f75f45d760c2911ffc45f2a06bcaed9f3ae3fb", size = 78551 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/c2/ab7d37426c179ceb9aeb109a85cda8948bb269b7561a0be870cc656eefe4/prometheus_client-0.21.1-py3-none-any.whl", hash = "sha256:594b45c410d6f4f8888940fe80b5cc2521b305a1fafe1c58609ef715a001f301", size = 54682 }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.50" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/e1/bd15cb8ffdcfeeb2bdc215de3c3cffca11408d829e4b8416dcfe71ba8854/prompt_toolkit-3.0.50.tar.gz", hash = "sha256:544748f3860a2623ca5cd6d2795e7a14f3d0e1c3c9728359013f79877fc89bab", size = 429087 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/ea/d836f008d33151c7a1f62caf3d8dd782e4d15f6a43897f64480c2b8de2ad/prompt_toolkit-3.0.50-py3-none-any.whl", hash = "sha256:9b6427eb19e479d98acff65196a307c555eb567989e6d88ebbb1b509d9779198", size = 387816 }, +] + +[[package]] +name = "propcache" +version = "0.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/07/c8/fdc6686a986feae3541ea23dcaa661bd93972d3940460646c6bb96e21c40/propcache-0.3.1.tar.gz", hash = "sha256:40d980c33765359098837527e18eddefc9a24cea5b45e078a7f3bb5b032c6ecf", size = 43651 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/0f/5a5319ee83bd651f75311fcb0c492c21322a7fc8f788e4eef23f44243427/propcache-0.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7f30241577d2fef2602113b70ef7231bf4c69a97e04693bde08ddab913ba0ce5", size = 80243 }, + { url = "https://files.pythonhosted.org/packages/ce/84/3db5537e0879942783e2256616ff15d870a11d7ac26541336fe1b673c818/propcache-0.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:43593c6772aa12abc3af7784bff4a41ffa921608dd38b77cf1dfd7f5c4e71371", size = 46503 }, + { url = "https://files.pythonhosted.org/packages/e2/c8/b649ed972433c3f0d827d7f0cf9ea47162f4ef8f4fe98c5f3641a0bc63ff/propcache-0.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a75801768bbe65499495660b777e018cbe90c7980f07f8aa57d6be79ea6f71da", size = 45934 }, + { url = "https://files.pythonhosted.org/packages/59/f9/4c0a5cf6974c2c43b1a6810c40d889769cc8f84cea676cbe1e62766a45f8/propcache-0.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6f1324db48f001c2ca26a25fa25af60711e09b9aaf4b28488602776f4f9a744", size = 233633 }, + { url = "https://files.pythonhosted.org/packages/e7/64/66f2f4d1b4f0007c6e9078bd95b609b633d3957fe6dd23eac33ebde4b584/propcache-0.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cdb0f3e1eb6dfc9965d19734d8f9c481b294b5274337a8cb5cb01b462dcb7e0", size = 241124 }, + { url = "https://files.pythonhosted.org/packages/aa/bf/7b8c9fd097d511638fa9b6af3d986adbdf567598a567b46338c925144c1b/propcache-0.3.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1eb34d90aac9bfbced9a58b266f8946cb5935869ff01b164573a7634d39fbcb5", size = 240283 }, + { url = "https://files.pythonhosted.org/packages/fa/c9/e85aeeeaae83358e2a1ef32d6ff50a483a5d5248bc38510d030a6f4e2816/propcache-0.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f35c7070eeec2cdaac6fd3fe245226ed2a6292d3ee8c938e5bb645b434c5f256", size = 232498 }, + { url = "https://files.pythonhosted.org/packages/8e/66/acb88e1f30ef5536d785c283af2e62931cb934a56a3ecf39105887aa8905/propcache-0.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b23c11c2c9e6d4e7300c92e022046ad09b91fd00e36e83c44483df4afa990073", size = 221486 }, + { url = "https://files.pythonhosted.org/packages/f5/f9/233ddb05ffdcaee4448508ee1d70aa7deff21bb41469ccdfcc339f871427/propcache-0.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3e19ea4ea0bf46179f8a3652ac1426e6dcbaf577ce4b4f65be581e237340420d", size = 222675 }, + { url = "https://files.pythonhosted.org/packages/98/b8/eb977e28138f9e22a5a789daf608d36e05ed93093ef12a12441030da800a/propcache-0.3.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:bd39c92e4c8f6cbf5f08257d6360123af72af9f4da75a690bef50da77362d25f", size = 215727 }, + { url = "https://files.pythonhosted.org/packages/89/2d/5f52d9c579f67b8ee1edd9ec073c91b23cc5b7ff7951a1e449e04ed8fdf3/propcache-0.3.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b0313e8b923b3814d1c4a524c93dfecea5f39fa95601f6a9b1ac96cd66f89ea0", size = 217878 }, + { url = "https://files.pythonhosted.org/packages/7a/fd/5283e5ed8a82b00c7a989b99bb6ea173db1ad750bf0bf8dff08d3f4a4e28/propcache-0.3.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e861ad82892408487be144906a368ddbe2dc6297074ade2d892341b35c59844a", size = 230558 }, + { url = "https://files.pythonhosted.org/packages/90/38/ab17d75938ef7ac87332c588857422ae126b1c76253f0f5b1242032923ca/propcache-0.3.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:61014615c1274df8da5991a1e5da85a3ccb00c2d4701ac6f3383afd3ca47ab0a", size = 233754 }, + { url = "https://files.pythonhosted.org/packages/06/5d/3b921b9c60659ae464137508d3b4c2b3f52f592ceb1964aa2533b32fcf0b/propcache-0.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:71ebe3fe42656a2328ab08933d420df5f3ab121772eef78f2dc63624157f0ed9", size = 226088 }, + { url = "https://files.pythonhosted.org/packages/54/6e/30a11f4417d9266b5a464ac5a8c5164ddc9dd153dfa77bf57918165eb4ae/propcache-0.3.1-cp311-cp311-win32.whl", hash = "sha256:58aa11f4ca8b60113d4b8e32d37e7e78bd8af4d1a5b5cb4979ed856a45e62005", size = 40859 }, + { url = "https://files.pythonhosted.org/packages/1d/3a/8a68dd867da9ca2ee9dfd361093e9cb08cb0f37e5ddb2276f1b5177d7731/propcache-0.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:9532ea0b26a401264b1365146c440a6d78269ed41f83f23818d4b79497aeabe7", size = 45153 }, + { url = "https://files.pythonhosted.org/packages/41/aa/ca78d9be314d1e15ff517b992bebbed3bdfef5b8919e85bf4940e57b6137/propcache-0.3.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f78eb8422acc93d7b69964012ad7048764bb45a54ba7a39bb9e146c72ea29723", size = 80430 }, + { url = "https://files.pythonhosted.org/packages/1a/d8/f0c17c44d1cda0ad1979af2e593ea290defdde9eaeb89b08abbe02a5e8e1/propcache-0.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:89498dd49c2f9a026ee057965cdf8192e5ae070ce7d7a7bd4b66a8e257d0c976", size = 46637 }, + { url = "https://files.pythonhosted.org/packages/ae/bd/c1e37265910752e6e5e8a4c1605d0129e5b7933c3dc3cf1b9b48ed83b364/propcache-0.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:09400e98545c998d57d10035ff623266927cb784d13dd2b31fd33b8a5316b85b", size = 46123 }, + { url = "https://files.pythonhosted.org/packages/d4/b0/911eda0865f90c0c7e9f0415d40a5bf681204da5fd7ca089361a64c16b28/propcache-0.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa8efd8c5adc5a2c9d3b952815ff8f7710cefdcaf5f2c36d26aff51aeca2f12f", size = 243031 }, + { url = "https://files.pythonhosted.org/packages/0a/06/0da53397c76a74271621807265b6eb61fb011451b1ddebf43213df763669/propcache-0.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2fe5c910f6007e716a06d269608d307b4f36e7babee5f36533722660e8c4a70", size = 249100 }, + { url = "https://files.pythonhosted.org/packages/f1/eb/13090e05bf6b963fc1653cdc922133ced467cb4b8dab53158db5a37aa21e/propcache-0.3.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a0ab8cf8cdd2194f8ff979a43ab43049b1df0b37aa64ab7eca04ac14429baeb7", size = 250170 }, + { url = "https://files.pythonhosted.org/packages/3b/4c/f72c9e1022b3b043ec7dc475a0f405d4c3e10b9b1d378a7330fecf0652da/propcache-0.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:563f9d8c03ad645597b8d010ef4e9eab359faeb11a0a2ac9f7b4bc8c28ebef25", size = 245000 }, + { url = "https://files.pythonhosted.org/packages/e8/fd/970ca0e22acc829f1adf5de3724085e778c1ad8a75bec010049502cb3a86/propcache-0.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb6e0faf8cb6b4beea5d6ed7b5a578254c6d7df54c36ccd3d8b3eb00d6770277", size = 230262 }, + { url = "https://files.pythonhosted.org/packages/c4/42/817289120c6b9194a44f6c3e6b2c3277c5b70bbad39e7df648f177cc3634/propcache-0.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1c5c7ab7f2bb3f573d1cb921993006ba2d39e8621019dffb1c5bc94cdbae81e8", size = 236772 }, + { url = "https://files.pythonhosted.org/packages/7c/9c/3b3942b302badd589ad6b672da3ca7b660a6c2f505cafd058133ddc73918/propcache-0.3.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:050b571b2e96ec942898f8eb46ea4bfbb19bd5502424747e83badc2d4a99a44e", size = 231133 }, + { url = "https://files.pythonhosted.org/packages/98/a1/75f6355f9ad039108ff000dfc2e19962c8dea0430da9a1428e7975cf24b2/propcache-0.3.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e1c4d24b804b3a87e9350f79e2371a705a188d292fd310e663483af6ee6718ee", size = 230741 }, + { url = "https://files.pythonhosted.org/packages/67/0c/3e82563af77d1f8731132166da69fdfd95e71210e31f18edce08a1eb11ea/propcache-0.3.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e4fe2a6d5ce975c117a6bb1e8ccda772d1e7029c1cca1acd209f91d30fa72815", size = 244047 }, + { url = "https://files.pythonhosted.org/packages/f7/50/9fb7cca01532a08c4d5186d7bb2da6c4c587825c0ae134b89b47c7d62628/propcache-0.3.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:feccd282de1f6322f56f6845bf1207a537227812f0a9bf5571df52bb418d79d5", size = 246467 }, + { url = "https://files.pythonhosted.org/packages/a9/02/ccbcf3e1c604c16cc525309161d57412c23cf2351523aedbb280eb7c9094/propcache-0.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ec314cde7314d2dd0510c6787326bbffcbdc317ecee6b7401ce218b3099075a7", size = 241022 }, + { url = "https://files.pythonhosted.org/packages/db/19/e777227545e09ca1e77a6e21274ae9ec45de0f589f0ce3eca2a41f366220/propcache-0.3.1-cp312-cp312-win32.whl", hash = "sha256:7d2d5a0028d920738372630870e7d9644ce437142197f8c827194fca404bf03b", size = 40647 }, + { url = "https://files.pythonhosted.org/packages/24/bb/3b1b01da5dd04c77a204c84e538ff11f624e31431cfde7201d9110b092b1/propcache-0.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:88c423efef9d7a59dae0614eaed718449c09a5ac79a5f224a8b9664d603f04a3", size = 44784 }, + { url = "https://files.pythonhosted.org/packages/58/60/f645cc8b570f99be3cf46714170c2de4b4c9d6b827b912811eff1eb8a412/propcache-0.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f1528ec4374617a7a753f90f20e2f551121bb558fcb35926f99e3c42367164b8", size = 77865 }, + { url = "https://files.pythonhosted.org/packages/6f/d4/c1adbf3901537582e65cf90fd9c26fde1298fde5a2c593f987112c0d0798/propcache-0.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dc1915ec523b3b494933b5424980831b636fe483d7d543f7afb7b3bf00f0c10f", size = 45452 }, + { url = "https://files.pythonhosted.org/packages/d1/b5/fe752b2e63f49f727c6c1c224175d21b7d1727ce1d4873ef1c24c9216830/propcache-0.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a110205022d077da24e60b3df8bcee73971be9575dec5573dd17ae5d81751111", size = 44800 }, + { url = "https://files.pythonhosted.org/packages/62/37/fc357e345bc1971e21f76597028b059c3d795c5ca7690d7a8d9a03c9708a/propcache-0.3.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d249609e547c04d190e820d0d4c8ca03ed4582bcf8e4e160a6969ddfb57b62e5", size = 225804 }, + { url = "https://files.pythonhosted.org/packages/0d/f1/16e12c33e3dbe7f8b737809bad05719cff1dccb8df4dafbcff5575002c0e/propcache-0.3.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ced33d827625d0a589e831126ccb4f5c29dfdf6766cac441d23995a65825dcb", size = 230650 }, + { url = "https://files.pythonhosted.org/packages/3e/a2/018b9f2ed876bf5091e60153f727e8f9073d97573f790ff7cdf6bc1d1fb8/propcache-0.3.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4114c4ada8f3181af20808bedb250da6bae56660e4b8dfd9cd95d4549c0962f7", size = 234235 }, + { url = "https://files.pythonhosted.org/packages/45/5f/3faee66fc930dfb5da509e34c6ac7128870631c0e3582987fad161fcb4b1/propcache-0.3.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:975af16f406ce48f1333ec5e912fe11064605d5c5b3f6746969077cc3adeb120", size = 228249 }, + { url = "https://files.pythonhosted.org/packages/62/1e/a0d5ebda5da7ff34d2f5259a3e171a94be83c41eb1e7cd21a2105a84a02e/propcache-0.3.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a34aa3a1abc50740be6ac0ab9d594e274f59960d3ad253cd318af76b996dd654", size = 214964 }, + { url = "https://files.pythonhosted.org/packages/db/a0/d72da3f61ceab126e9be1f3bc7844b4e98c6e61c985097474668e7e52152/propcache-0.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9cec3239c85ed15bfaded997773fdad9fb5662b0a7cbc854a43f291eb183179e", size = 222501 }, + { url = "https://files.pythonhosted.org/packages/18/6d/a008e07ad7b905011253adbbd97e5b5375c33f0b961355ca0a30377504ac/propcache-0.3.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:05543250deac8e61084234d5fc54f8ebd254e8f2b39a16b1dce48904f45b744b", size = 217917 }, + { url = "https://files.pythonhosted.org/packages/98/37/02c9343ffe59e590e0e56dc5c97d0da2b8b19fa747ebacf158310f97a79a/propcache-0.3.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5cb5918253912e088edbf023788de539219718d3b10aef334476b62d2b53de53", size = 217089 }, + { url = "https://files.pythonhosted.org/packages/53/1b/d3406629a2c8a5666d4674c50f757a77be119b113eedd47b0375afdf1b42/propcache-0.3.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f3bbecd2f34d0e6d3c543fdb3b15d6b60dd69970c2b4c822379e5ec8f6f621d5", size = 228102 }, + { url = "https://files.pythonhosted.org/packages/cd/a7/3664756cf50ce739e5f3abd48febc0be1a713b1f389a502ca819791a6b69/propcache-0.3.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aca63103895c7d960a5b9b044a83f544b233c95e0dcff114389d64d762017af7", size = 230122 }, + { url = "https://files.pythonhosted.org/packages/35/36/0bbabaacdcc26dac4f8139625e930f4311864251276033a52fd52ff2a274/propcache-0.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a0a9898fdb99bf11786265468571e628ba60af80dc3f6eb89a3545540c6b0ef", size = 226818 }, + { url = "https://files.pythonhosted.org/packages/cc/27/4e0ef21084b53bd35d4dae1634b6d0bad35e9c58ed4f032511acca9d4d26/propcache-0.3.1-cp313-cp313-win32.whl", hash = "sha256:3a02a28095b5e63128bcae98eb59025924f121f048a62393db682f049bf4ac24", size = 40112 }, + { url = "https://files.pythonhosted.org/packages/a6/2c/a54614d61895ba6dd7ac8f107e2b2a0347259ab29cbf2ecc7b94fa38c4dc/propcache-0.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:813fbb8b6aea2fc9659815e585e548fe706d6f663fa73dff59a1677d4595a037", size = 44034 }, + { url = "https://files.pythonhosted.org/packages/5a/a8/0a4fd2f664fc6acc66438370905124ce62e84e2e860f2557015ee4a61c7e/propcache-0.3.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a444192f20f5ce8a5e52761a031b90f5ea6288b1eef42ad4c7e64fef33540b8f", size = 82613 }, + { url = "https://files.pythonhosted.org/packages/4d/e5/5ef30eb2cd81576256d7b6caaa0ce33cd1d2c2c92c8903cccb1af1a4ff2f/propcache-0.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0fbe94666e62ebe36cd652f5fc012abfbc2342de99b523f8267a678e4dfdee3c", size = 47763 }, + { url = "https://files.pythonhosted.org/packages/87/9a/87091ceb048efeba4d28e903c0b15bcc84b7c0bf27dc0261e62335d9b7b8/propcache-0.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f011f104db880f4e2166bcdcf7f58250f7a465bc6b068dc84c824a3d4a5c94dc", size = 47175 }, + { url = "https://files.pythonhosted.org/packages/3e/2f/854e653c96ad1161f96194c6678a41bbb38c7947d17768e8811a77635a08/propcache-0.3.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e584b6d388aeb0001d6d5c2bd86b26304adde6d9bb9bfa9c4889805021b96de", size = 292265 }, + { url = "https://files.pythonhosted.org/packages/40/8d/090955e13ed06bc3496ba4a9fb26c62e209ac41973cb0d6222de20c6868f/propcache-0.3.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a17583515a04358b034e241f952f1715243482fc2c2945fd99a1b03a0bd77d6", size = 294412 }, + { url = "https://files.pythonhosted.org/packages/39/e6/d51601342e53cc7582449e6a3c14a0479fab2f0750c1f4d22302e34219c6/propcache-0.3.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5aed8d8308215089c0734a2af4f2e95eeb360660184ad3912686c181e500b2e7", size = 294290 }, + { url = "https://files.pythonhosted.org/packages/3b/4d/be5f1a90abc1881884aa5878989a1acdafd379a91d9c7e5e12cef37ec0d7/propcache-0.3.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d8e309ff9a0503ef70dc9a0ebd3e69cf7b3894c9ae2ae81fc10943c37762458", size = 282926 }, + { url = "https://files.pythonhosted.org/packages/57/2b/8f61b998c7ea93a2b7eca79e53f3e903db1787fca9373af9e2cf8dc22f9d/propcache-0.3.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b655032b202028a582d27aeedc2e813299f82cb232f969f87a4fde491a233f11", size = 267808 }, + { url = "https://files.pythonhosted.org/packages/11/1c/311326c3dfce59c58a6098388ba984b0e5fb0381ef2279ec458ef99bd547/propcache-0.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f64d91b751df77931336b5ff7bafbe8845c5770b06630e27acd5dbb71e1931c", size = 290916 }, + { url = "https://files.pythonhosted.org/packages/4b/74/91939924b0385e54dc48eb2e4edd1e4903ffd053cf1916ebc5347ac227f7/propcache-0.3.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:19a06db789a4bd896ee91ebc50d059e23b3639c25d58eb35be3ca1cbe967c3bf", size = 262661 }, + { url = "https://files.pythonhosted.org/packages/c2/d7/e6079af45136ad325c5337f5dd9ef97ab5dc349e0ff362fe5c5db95e2454/propcache-0.3.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:bef100c88d8692864651b5f98e871fb090bd65c8a41a1cb0ff2322db39c96c27", size = 264384 }, + { url = "https://files.pythonhosted.org/packages/b7/d5/ba91702207ac61ae6f1c2da81c5d0d6bf6ce89e08a2b4d44e411c0bbe867/propcache-0.3.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:87380fb1f3089d2a0b8b00f006ed12bd41bd858fabfa7330c954c70f50ed8757", size = 291420 }, + { url = "https://files.pythonhosted.org/packages/58/70/2117780ed7edcd7ba6b8134cb7802aada90b894a9810ec56b7bb6018bee7/propcache-0.3.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e474fc718e73ba5ec5180358aa07f6aded0ff5f2abe700e3115c37d75c947e18", size = 290880 }, + { url = "https://files.pythonhosted.org/packages/4a/1f/ecd9ce27710021ae623631c0146719280a929d895a095f6d85efb6a0be2e/propcache-0.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:17d1c688a443355234f3c031349da69444be052613483f3e4158eef751abcd8a", size = 287407 }, + { url = "https://files.pythonhosted.org/packages/3e/66/2e90547d6b60180fb29e23dc87bd8c116517d4255240ec6d3f7dc23d1926/propcache-0.3.1-cp313-cp313t-win32.whl", hash = "sha256:359e81a949a7619802eb601d66d37072b79b79c2505e6d3fd8b945538411400d", size = 42573 }, + { url = "https://files.pythonhosted.org/packages/cb/8f/50ad8599399d1861b4d2b6b45271f0ef6af1b09b0a2386a46dbaf19c9535/propcache-0.3.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e7fb9a84c9abbf2b2683fa3e7b0d7da4d8ecf139a1c635732a8bda29c5214b0e", size = 46757 }, + { url = "https://files.pythonhosted.org/packages/b8/d3/c3cb8f1d6ae3b37f83e1de806713a9b3642c5895f0215a62e1a4bd6e5e34/propcache-0.3.1-py3-none-any.whl", hash = "sha256:9a8ecf38de50a7f518c21568c80f985e776397b902f1ce0b01f799aba1608b40", size = 12376 }, +] + +[[package]] +name = "protobuf" +version = "6.31.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/f3/b9655a711b32c19720253f6f06326faf90580834e2e83f840472d752bc8b/protobuf-6.31.1.tar.gz", hash = "sha256:d8cac4c982f0b957a4dc73a80e2ea24fab08e679c0de9deb835f4a12d69aca9a", size = 441797 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/6f/6ab8e4bf962fd5570d3deaa2d5c38f0a363f57b4501047b5ebeb83ab1125/protobuf-6.31.1-cp310-abi3-win32.whl", hash = "sha256:7fa17d5a29c2e04b7d90e5e32388b8bfd0e7107cd8e616feef7ed3fa6bdab5c9", size = 423603 }, + { url = "https://files.pythonhosted.org/packages/44/3a/b15c4347dd4bf3a1b0ee882f384623e2063bb5cf9fa9d57990a4f7df2fb6/protobuf-6.31.1-cp310-abi3-win_amd64.whl", hash = "sha256:426f59d2964864a1a366254fa703b8632dcec0790d8862d30034d8245e1cd447", size = 435283 }, + { url = "https://files.pythonhosted.org/packages/6a/c9/b9689a2a250264a84e66c46d8862ba788ee7a641cdca39bccf64f59284b7/protobuf-6.31.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:6f1227473dc43d44ed644425268eb7c2e488ae245d51c6866d19fe158e207402", size = 425604 }, + { url = "https://files.pythonhosted.org/packages/76/a1/7a5a94032c83375e4fe7e7f56e3976ea6ac90c5e85fac8576409e25c39c3/protobuf-6.31.1-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:a40fc12b84c154884d7d4c4ebd675d5b3b5283e155f324049ae396b95ddebc39", size = 322115 }, + { url = "https://files.pythonhosted.org/packages/fa/b1/b59d405d64d31999244643d88c45c8241c58f17cc887e73bcb90602327f8/protobuf-6.31.1-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:4ee898bf66f7a8b0bd21bce523814e6fbd8c6add948045ce958b73af7e8878c6", size = 321070 }, + { url = "https://files.pythonhosted.org/packages/f7/af/ab3c51ab7507a7325e98ffe691d9495ee3d3aa5f589afad65ec920d39821/protobuf-6.31.1-py3-none-any.whl", hash = "sha256:720a6c7e6b77288b85063569baae8536671b39f15cc22037ec7045658d80489e", size = 168724 }, +] + +[[package]] +name = "psutil" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", size = 238051 }, + { url = "https://files.pythonhosted.org/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", size = 239535 }, + { url = "https://files.pythonhosted.org/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", size = 275004 }, + { url = "https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", size = 277986 }, + { url = "https://files.pythonhosted.org/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", size = 279544 }, + { url = "https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053 }, + { url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885 }, +] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993 }, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842 }, +] + +[[package]] +name = "pyarrow" +version = "20.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/ee/a7810cb9f3d6e9238e61d312076a9859bf3668fd21c69744de9532383912/pyarrow-20.0.0.tar.gz", hash = "sha256:febc4a913592573c8d5805091a6c2b5064c8bd6e002131f01061797d91c783c1", size = 1125187 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/a2/b7930824181ceadd0c63c1042d01fa4ef63eee233934826a7a2a9af6e463/pyarrow-20.0.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:24ca380585444cb2a31324c546a9a56abbe87e26069189e14bdba19c86c049f0", size = 30856035 }, + { url = "https://files.pythonhosted.org/packages/9b/18/c765770227d7f5bdfa8a69f64b49194352325c66a5c3bb5e332dfd5867d9/pyarrow-20.0.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:95b330059ddfdc591a3225f2d272123be26c8fa76e8c9ee1a77aad507361cfdb", size = 32309552 }, + { url = "https://files.pythonhosted.org/packages/44/fb/dfb2dfdd3e488bb14f822d7335653092dde150cffc2da97de6e7500681f9/pyarrow-20.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f0fb1041267e9968c6d0d2ce3ff92e3928b243e2b6d11eeb84d9ac547308232", size = 41334704 }, + { url = "https://files.pythonhosted.org/packages/58/0d/08a95878d38808051a953e887332d4a76bc06c6ee04351918ee1155407eb/pyarrow-20.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8ff87cc837601532cc8242d2f7e09b4e02404de1b797aee747dd4ba4bd6313f", size = 42399836 }, + { url = "https://files.pythonhosted.org/packages/f3/cd/efa271234dfe38f0271561086eedcad7bc0f2ddd1efba423916ff0883684/pyarrow-20.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:7a3a5dcf54286e6141d5114522cf31dd67a9e7c9133d150799f30ee302a7a1ab", size = 40711789 }, + { url = "https://files.pythonhosted.org/packages/46/1f/7f02009bc7fc8955c391defee5348f510e589a020e4b40ca05edcb847854/pyarrow-20.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a6ad3e7758ecf559900261a4df985662df54fb7fdb55e8e3b3aa99b23d526b62", size = 42301124 }, + { url = "https://files.pythonhosted.org/packages/4f/92/692c562be4504c262089e86757a9048739fe1acb4024f92d39615e7bab3f/pyarrow-20.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6bb830757103a6cb300a04610e08d9636f0cd223d32f388418ea893a3e655f1c", size = 42916060 }, + { url = "https://files.pythonhosted.org/packages/a4/ec/9f5c7e7c828d8e0a3c7ef50ee62eca38a7de2fa6eb1b8fa43685c9414fef/pyarrow-20.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:96e37f0766ecb4514a899d9a3554fadda770fb57ddf42b63d80f14bc20aa7db3", size = 44547640 }, + { url = "https://files.pythonhosted.org/packages/54/96/46613131b4727f10fd2ffa6d0d6f02efcc09a0e7374eff3b5771548aa95b/pyarrow-20.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:3346babb516f4b6fd790da99b98bed9708e3f02e734c84971faccb20736848dc", size = 25781491 }, + { url = "https://files.pythonhosted.org/packages/a1/d6/0c10e0d54f6c13eb464ee9b67a68b8c71bcf2f67760ef5b6fbcddd2ab05f/pyarrow-20.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:75a51a5b0eef32727a247707d4755322cb970be7e935172b6a3a9f9ae98404ba", size = 30815067 }, + { url = "https://files.pythonhosted.org/packages/7e/e2/04e9874abe4094a06fd8b0cbb0f1312d8dd7d707f144c2ec1e5e8f452ffa/pyarrow-20.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:211d5e84cecc640c7a3ab900f930aaff5cd2702177e0d562d426fb7c4f737781", size = 32297128 }, + { url = "https://files.pythonhosted.org/packages/31/fd/c565e5dcc906a3b471a83273039cb75cb79aad4a2d4a12f76cc5ae90a4b8/pyarrow-20.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ba3cf4182828be7a896cbd232aa8dd6a31bd1f9e32776cc3796c012855e1199", size = 41334890 }, + { url = "https://files.pythonhosted.org/packages/af/a9/3bdd799e2c9b20c1ea6dc6fa8e83f29480a97711cf806e823f808c2316ac/pyarrow-20.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c3a01f313ffe27ac4126f4c2e5ea0f36a5fc6ab51f8726cf41fee4b256680bd", size = 42421775 }, + { url = "https://files.pythonhosted.org/packages/10/f7/da98ccd86354c332f593218101ae56568d5dcedb460e342000bd89c49cc1/pyarrow-20.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:a2791f69ad72addd33510fec7bb14ee06c2a448e06b649e264c094c5b5f7ce28", size = 40687231 }, + { url = "https://files.pythonhosted.org/packages/bb/1b/2168d6050e52ff1e6cefc61d600723870bf569cbf41d13db939c8cf97a16/pyarrow-20.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:4250e28a22302ce8692d3a0e8ec9d9dde54ec00d237cff4dfa9c1fbf79e472a8", size = 42295639 }, + { url = "https://files.pythonhosted.org/packages/b2/66/2d976c0c7158fd25591c8ca55aee026e6d5745a021915a1835578707feb3/pyarrow-20.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:89e030dc58fc760e4010148e6ff164d2f44441490280ef1e97a542375e41058e", size = 42908549 }, + { url = "https://files.pythonhosted.org/packages/31/a9/dfb999c2fc6911201dcbf348247f9cc382a8990f9ab45c12eabfd7243a38/pyarrow-20.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6102b4864d77102dbbb72965618e204e550135a940c2534711d5ffa787df2a5a", size = 44557216 }, + { url = "https://files.pythonhosted.org/packages/a0/8e/9adee63dfa3911be2382fb4d92e4b2e7d82610f9d9f668493bebaa2af50f/pyarrow-20.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:96d6a0a37d9c98be08f5ed6a10831d88d52cac7b13f5287f1e0f625a0de8062b", size = 25660496 }, + { url = "https://files.pythonhosted.org/packages/9b/aa/daa413b81446d20d4dad2944110dcf4cf4f4179ef7f685dd5a6d7570dc8e/pyarrow-20.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:a15532e77b94c61efadde86d10957950392999503b3616b2ffcef7621a002893", size = 30798501 }, + { url = "https://files.pythonhosted.org/packages/ff/75/2303d1caa410925de902d32ac215dc80a7ce7dd8dfe95358c165f2adf107/pyarrow-20.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:dd43f58037443af715f34f1322c782ec463a3c8a94a85fdb2d987ceb5658e061", size = 32277895 }, + { url = "https://files.pythonhosted.org/packages/92/41/fe18c7c0b38b20811b73d1bdd54b1fccba0dab0e51d2048878042d84afa8/pyarrow-20.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa0d288143a8585806e3cc7c39566407aab646fb9ece164609dac1cfff45f6ae", size = 41327322 }, + { url = "https://files.pythonhosted.org/packages/da/ab/7dbf3d11db67c72dbf36ae63dcbc9f30b866c153b3a22ef728523943eee6/pyarrow-20.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6953f0114f8d6f3d905d98e987d0924dabce59c3cda380bdfaa25a6201563b4", size = 42411441 }, + { url = "https://files.pythonhosted.org/packages/90/c3/0c7da7b6dac863af75b64e2f827e4742161128c350bfe7955b426484e226/pyarrow-20.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:991f85b48a8a5e839b2128590ce07611fae48a904cae6cab1f089c5955b57eb5", size = 40677027 }, + { url = "https://files.pythonhosted.org/packages/be/27/43a47fa0ff9053ab5203bb3faeec435d43c0d8bfa40179bfd076cdbd4e1c/pyarrow-20.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:97c8dc984ed09cb07d618d57d8d4b67a5100a30c3818c2fb0b04599f0da2de7b", size = 42281473 }, + { url = "https://files.pythonhosted.org/packages/bc/0b/d56c63b078876da81bbb9ba695a596eabee9b085555ed12bf6eb3b7cab0e/pyarrow-20.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9b71daf534f4745818f96c214dbc1e6124d7daf059167330b610fc69b6f3d3e3", size = 42893897 }, + { url = "https://files.pythonhosted.org/packages/92/ac/7d4bd020ba9145f354012838692d48300c1b8fe5634bfda886abcada67ed/pyarrow-20.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e8b88758f9303fa5a83d6c90e176714b2fd3852e776fc2d7e42a22dd6c2fb368", size = 44543847 }, + { url = "https://files.pythonhosted.org/packages/9d/07/290f4abf9ca702c5df7b47739c1b2c83588641ddfa2cc75e34a301d42e55/pyarrow-20.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:30b3051b7975801c1e1d387e17c588d8ab05ced9b1e14eec57915f79869b5031", size = 25653219 }, + { url = "https://files.pythonhosted.org/packages/95/df/720bb17704b10bd69dde086e1400b8eefb8f58df3f8ac9cff6c425bf57f1/pyarrow-20.0.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:ca151afa4f9b7bc45bcc791eb9a89e90a9eb2772767d0b1e5389609c7d03db63", size = 30853957 }, + { url = "https://files.pythonhosted.org/packages/d9/72/0d5f875efc31baef742ba55a00a25213a19ea64d7176e0fe001c5d8b6e9a/pyarrow-20.0.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:4680f01ecd86e0dd63e39eb5cd59ef9ff24a9d166db328679e36c108dc993d4c", size = 32247972 }, + { url = "https://files.pythonhosted.org/packages/d5/bc/e48b4fa544d2eea72f7844180eb77f83f2030b84c8dad860f199f94307ed/pyarrow-20.0.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f4c8534e2ff059765647aa69b75d6543f9fef59e2cd4c6d18015192565d2b70", size = 41256434 }, + { url = "https://files.pythonhosted.org/packages/c3/01/974043a29874aa2cf4f87fb07fd108828fc7362300265a2a64a94965e35b/pyarrow-20.0.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e1f8a47f4b4ae4c69c4d702cfbdfe4d41e18e5c7ef6f1bb1c50918c1e81c57b", size = 42353648 }, + { url = "https://files.pythonhosted.org/packages/68/95/cc0d3634cde9ca69b0e51cbe830d8915ea32dda2157560dda27ff3b3337b/pyarrow-20.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:a1f60dc14658efaa927f8214734f6a01a806d7690be4b3232ba526836d216122", size = 40619853 }, + { url = "https://files.pythonhosted.org/packages/29/c2/3ad40e07e96a3e74e7ed7cc8285aadfa84eb848a798c98ec0ad009eb6bcc/pyarrow-20.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:204a846dca751428991346976b914d6d2a82ae5b8316a6ed99789ebf976551e6", size = 42241743 }, + { url = "https://files.pythonhosted.org/packages/eb/cb/65fa110b483339add6a9bc7b6373614166b14e20375d4daa73483755f830/pyarrow-20.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f3b117b922af5e4c6b9a9115825726cac7d8b1421c37c2b5e24fbacc8930612c", size = 42839441 }, + { url = "https://files.pythonhosted.org/packages/98/7b/f30b1954589243207d7a0fbc9997401044bf9a033eec78f6cb50da3f304a/pyarrow-20.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e724a3fd23ae5b9c010e7be857f4405ed5e679db5c93e66204db1a69f733936a", size = 44503279 }, + { url = "https://files.pythonhosted.org/packages/37/40/ad395740cd641869a13bcf60851296c89624662575621968dcfafabaa7f6/pyarrow-20.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:82f1ee5133bd8f49d31be1299dc07f585136679666b502540db854968576faf9", size = 25944982 }, +] + +[[package]] +name = "pycparser" +version = "2.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 }, +] + +[[package]] +name = "pydantic" +version = "2.11.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f0/86/8ce9040065e8f924d642c58e4a344e33163a07f6b57f836d0d734e0ad3fb/pydantic-2.11.5.tar.gz", hash = "sha256:7f853db3d0ce78ce8bbb148c401c2cdd6431b3473c0cdff2755c7690952a7b7a", size = 787102 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/69/831ed22b38ff9b4b64b66569f0e5b7b97cf3638346eb95a2147fdb49ad5f/pydantic-2.11.5-py3-none-any.whl", hash = "sha256:f9c26ba06f9747749ca1e5c94d6a85cb84254577553c8785576fd38fa64dc0f7", size = 444229 }, +] + +[[package]] +name = "pydantic-core" +version = "2.33.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/8d/71db63483d518cbbf290261a1fc2839d17ff89fce7089e08cad07ccfce67/pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7", size = 2028584 }, + { url = "https://files.pythonhosted.org/packages/24/2f/3cfa7244ae292dd850989f328722d2aef313f74ffc471184dc509e1e4e5a/pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246", size = 1855071 }, + { url = "https://files.pythonhosted.org/packages/b3/d3/4ae42d33f5e3f50dd467761304be2fa0a9417fbf09735bc2cce003480f2a/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f", size = 1897823 }, + { url = "https://files.pythonhosted.org/packages/f4/f3/aa5976e8352b7695ff808599794b1fba2a9ae2ee954a3426855935799488/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc", size = 1983792 }, + { url = "https://files.pythonhosted.org/packages/d5/7a/cda9b5a23c552037717f2b2a5257e9b2bfe45e687386df9591eff7b46d28/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de", size = 2136338 }, + { url = "https://files.pythonhosted.org/packages/2b/9f/b8f9ec8dd1417eb9da784e91e1667d58a2a4a7b7b34cf4af765ef663a7e5/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a", size = 2730998 }, + { url = "https://files.pythonhosted.org/packages/47/bc/cd720e078576bdb8255d5032c5d63ee5c0bf4b7173dd955185a1d658c456/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef", size = 2003200 }, + { url = "https://files.pythonhosted.org/packages/ca/22/3602b895ee2cd29d11a2b349372446ae9727c32e78a94b3d588a40fdf187/pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e", size = 2113890 }, + { url = "https://files.pythonhosted.org/packages/ff/e6/e3c5908c03cf00d629eb38393a98fccc38ee0ce8ecce32f69fc7d7b558a7/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d", size = 2073359 }, + { url = "https://files.pythonhosted.org/packages/12/e7/6a36a07c59ebefc8777d1ffdaf5ae71b06b21952582e4b07eba88a421c79/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30", size = 2245883 }, + { url = "https://files.pythonhosted.org/packages/16/3f/59b3187aaa6cc0c1e6616e8045b284de2b6a87b027cce2ffcea073adf1d2/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf", size = 2241074 }, + { url = "https://files.pythonhosted.org/packages/e0/ed/55532bb88f674d5d8f67ab121a2a13c385df382de2a1677f30ad385f7438/pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51", size = 1910538 }, + { url = "https://files.pythonhosted.org/packages/fe/1b/25b7cccd4519c0b23c2dd636ad39d381abf113085ce4f7bec2b0dc755eb1/pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab", size = 1952909 }, + { url = "https://files.pythonhosted.org/packages/49/a9/d809358e49126438055884c4366a1f6227f0f84f635a9014e2deb9b9de54/pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65", size = 1897786 }, + { url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000 }, + { url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996 }, + { url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957 }, + { url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199 }, + { url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296 }, + { url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109 }, + { url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028 }, + { url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044 }, + { url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881 }, + { url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034 }, + { url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187 }, + { url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628 }, + { url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866 }, + { url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894 }, + { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688 }, + { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808 }, + { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580 }, + { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859 }, + { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810 }, + { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498 }, + { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611 }, + { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924 }, + { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196 }, + { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389 }, + { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223 }, + { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473 }, + { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269 }, + { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921 }, + { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162 }, + { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560 }, + { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777 }, + { url = "https://files.pythonhosted.org/packages/7b/27/d4ae6487d73948d6f20dddcd94be4ea43e74349b56eba82e9bdee2d7494c/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8", size = 2025200 }, + { url = "https://files.pythonhosted.org/packages/f1/b8/b3cb95375f05d33801024079b9392a5ab45267a63400bf1866e7ce0f0de4/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593", size = 1859123 }, + { url = "https://files.pythonhosted.org/packages/05/bc/0d0b5adeda59a261cd30a1235a445bf55c7e46ae44aea28f7bd6ed46e091/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612", size = 1892852 }, + { url = "https://files.pythonhosted.org/packages/3e/11/d37bdebbda2e449cb3f519f6ce950927b56d62f0b84fd9cb9e372a26a3d5/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7", size = 2067484 }, + { url = "https://files.pythonhosted.org/packages/8c/55/1f95f0a05ce72ecb02a8a8a1c3be0579bbc29b1d5ab68f1378b7bebc5057/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e", size = 2108896 }, + { url = "https://files.pythonhosted.org/packages/53/89/2b2de6c81fa131f423246a9109d7b2a375e83968ad0800d6e57d0574629b/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8", size = 2069475 }, + { url = "https://files.pythonhosted.org/packages/b8/e9/1f7efbe20d0b2b10f6718944b5d8ece9152390904f29a78e68d4e7961159/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf", size = 2239013 }, + { url = "https://files.pythonhosted.org/packages/3c/b2/5309c905a93811524a49b4e031e9851a6b00ff0fb668794472ea7746b448/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb", size = 2238715 }, + { url = "https://files.pythonhosted.org/packages/32/56/8a7ca5d2cd2cda1d245d34b1c9a942920a718082ae8e54e5f3e5a58b7add/pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1", size = 2066757 }, +] + +[[package]] +name = "pygments" +version = "2.19.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 }, +] + +[[package]] +name = "pyparsing" +version = "3.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/22/f1129e69d94ffff626bdb5c835506b3a5b4f3d070f17ea295e12c2c6f60f/pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be", size = 1088608 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/e7/df2285f3d08fee213f2d041540fa4fc9ca6c2d44cf36d3a035bf2a8d2bcc/pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf", size = 111120 }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, +] + +[[package]] +name = "python-json-logger" +version = "3.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/de/d3144a0bceede957f961e975f3752760fbe390d57fbe194baf709d8f1f7b/python_json_logger-3.3.0.tar.gz", hash = "sha256:12b7e74b17775e7d565129296105bbe3910842d9d0eb083fc83a6a617aa8df84", size = 16642 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/20/0f2523b9e50a8052bc6a8b732dfc8568abbdc42010aef03a2d750bdab3b2/python_json_logger-3.3.0-py3-none-any.whl", hash = "sha256:dd980fae8cffb24c13caf6e158d3d61c0d6d22342f932cb6e9deedab3d35eec7", size = 15163 }, +] + +[[package]] +name = "pytorch-lightning" +version = "2.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fsspec", extra = ["http"] }, + { name = "lightning-utilities" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "torch" }, + { name = "torchmetrics" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d8/75/b8ac01fd3974328ae9239c39550d48899b2fa1b0e064c01615b72a574082/pytorch_lightning-2.5.1.tar.gz", hash = "sha256:27a8adb799c13b8202afad518352248d61303fb230ec1f9fa60e0f81d431d6b1", size = 634309 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/ff/5701f79317a1a03e5ee8a1bf48e7273a8445162a2774e51fc06411a67c89/pytorch_lightning-2.5.1-py3-none-any.whl", hash = "sha256:0bfbbd3ad80281d3062f5d8029a759093bd969ff8162e7c1fe2918552b269f9e", size = 822982 }, +] + +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225 }, +] + +[[package]] +name = "pywin32" +version = "310" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/b1/68aa2986129fb1011dabbe95f0136f44509afaf072b12b8f815905a39f33/pywin32-310-cp311-cp311-win32.whl", hash = "sha256:1e765f9564e83011a63321bb9d27ec456a0ed90d3732c4b2e312b855365ed8bd", size = 8784284 }, + { url = "https://files.pythonhosted.org/packages/b3/bd/d1592635992dd8db5bb8ace0551bc3a769de1ac8850200cfa517e72739fb/pywin32-310-cp311-cp311-win_amd64.whl", hash = "sha256:126298077a9d7c95c53823934f000599f66ec9296b09167810eb24875f32689c", size = 9520748 }, + { url = "https://files.pythonhosted.org/packages/90/b1/ac8b1ffce6603849eb45a91cf126c0fa5431f186c2e768bf56889c46f51c/pywin32-310-cp311-cp311-win_arm64.whl", hash = "sha256:19ec5fc9b1d51c4350be7bb00760ffce46e6c95eaf2f0b2f1150657b1a43c582", size = 8455941 }, + { url = "https://files.pythonhosted.org/packages/6b/ec/4fdbe47932f671d6e348474ea35ed94227fb5df56a7c30cbbb42cd396ed0/pywin32-310-cp312-cp312-win32.whl", hash = "sha256:8a75a5cc3893e83a108c05d82198880704c44bbaee4d06e442e471d3c9ea4f3d", size = 8796239 }, + { url = "https://files.pythonhosted.org/packages/e3/e5/b0627f8bb84e06991bea89ad8153a9e50ace40b2e1195d68e9dff6b03d0f/pywin32-310-cp312-cp312-win_amd64.whl", hash = "sha256:bf5c397c9a9a19a6f62f3fb821fbf36cac08f03770056711f765ec1503972060", size = 9503839 }, + { url = "https://files.pythonhosted.org/packages/1f/32/9ccf53748df72301a89713936645a664ec001abd35ecc8578beda593d37d/pywin32-310-cp312-cp312-win_arm64.whl", hash = "sha256:2349cc906eae872d0663d4d6290d13b90621eaf78964bb1578632ff20e152966", size = 8459470 }, + { url = "https://files.pythonhosted.org/packages/1c/09/9c1b978ffc4ae53999e89c19c77ba882d9fce476729f23ef55211ea1c034/pywin32-310-cp313-cp313-win32.whl", hash = "sha256:5d241a659c496ada3253cd01cfaa779b048e90ce4b2b38cd44168ad555ce74ab", size = 8794384 }, + { url = "https://files.pythonhosted.org/packages/45/3c/b4640f740ffebadd5d34df35fecba0e1cfef8fde9f3e594df91c28ad9b50/pywin32-310-cp313-cp313-win_amd64.whl", hash = "sha256:667827eb3a90208ddbdcc9e860c81bde63a135710e21e4cb3348968e4bd5249e", size = 9503039 }, + { url = "https://files.pythonhosted.org/packages/b4/f4/f785020090fb050e7fb6d34b780f2231f302609dc964672f72bfaeb59a28/pywin32-310-cp313-cp313-win_arm64.whl", hash = "sha256:e308f831de771482b7cf692a1f308f8fca701b2d8f9dde6cc440c7da17e47b33", size = 8458152 }, +] + +[[package]] +name = "pywinpty" +version = "2.0.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/7c/917f9c4681bb8d34bfbe0b79d36bbcd902651aeab48790df3d30ba0202fb/pywinpty-2.0.15.tar.gz", hash = "sha256:312cf39153a8736c617d45ce8b6ad6cd2107de121df91c455b10ce6bba7a39b2", size = 29017 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/ac/6884dcb7108af66ad53f73ef4dad096e768c9203a6e6ce5e6b0c4a46e238/pywinpty-2.0.15-cp311-cp311-win_amd64.whl", hash = "sha256:9a6bcec2df2707aaa9d08b86071970ee32c5026e10bcc3cc5f6f391d85baf7ca", size = 1405249 }, + { url = "https://files.pythonhosted.org/packages/88/e5/9714def18c3a411809771a3fbcec70bffa764b9675afb00048a620fca604/pywinpty-2.0.15-cp312-cp312-win_amd64.whl", hash = "sha256:83a8f20b430bbc5d8957249f875341a60219a4e971580f2ba694fbfb54a45ebc", size = 1405243 }, + { url = "https://files.pythonhosted.org/packages/fb/16/2ab7b3b7f55f3c6929e5f629e1a68362981e4e5fed592a2ed1cb4b4914a5/pywinpty-2.0.15-cp313-cp313-win_amd64.whl", hash = "sha256:ab5920877dd632c124b4ed17bc6dd6ef3b9f86cd492b963ffdb1a67b85b0f408", size = 1405020 }, + { url = "https://files.pythonhosted.org/packages/7c/16/edef3515dd2030db2795dbfbe392232c7a0f3dc41b98e92b38b42ba497c7/pywinpty-2.0.15-cp313-cp313t-win_amd64.whl", hash = "sha256:a4560ad8c01e537708d2790dbe7da7d986791de805d89dd0d3697ca59e9e4901", size = 1404151 }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 }, + { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 }, + { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 }, + { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167 }, + { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952 }, + { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301 }, + { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638 }, + { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850 }, + { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980 }, + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, +] + +[[package]] +name = "pyzmq" +version = "26.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "implementation_name == 'pypy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/11/b9213d25230ac18a71b39b3723494e57adebe36e066397b961657b3b41c1/pyzmq-26.4.0.tar.gz", hash = "sha256:4bd13f85f80962f91a651a7356fe0472791a5f7a92f227822b5acf44795c626d", size = 278293 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/6d/234e3b0aa82fd0290b1896e9992f56bdddf1f97266110be54d0177a9d2d9/pyzmq-26.4.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:bfcf82644c9b45ddd7cd2a041f3ff8dce4a0904429b74d73a439e8cab1bd9e54", size = 1339723 }, + { url = "https://files.pythonhosted.org/packages/4f/11/6d561efe29ad83f7149a7cd48e498e539ed09019c6cd7ecc73f4cc725028/pyzmq-26.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9bcae3979b2654d5289d3490742378b2f3ce804b0b5fd42036074e2bf35b030", size = 672645 }, + { url = "https://files.pythonhosted.org/packages/19/fd/81bfe3e23f418644660bad1a90f0d22f0b3eebe33dd65a79385530bceb3d/pyzmq-26.4.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ccdff8ac4246b6fb60dcf3982dfaeeff5dd04f36051fe0632748fc0aa0679c01", size = 910133 }, + { url = "https://files.pythonhosted.org/packages/97/68/321b9c775595ea3df832a9516252b653fe32818db66fdc8fa31c9b9fce37/pyzmq-26.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4550af385b442dc2d55ab7717837812799d3674cb12f9a3aa897611839c18e9e", size = 867428 }, + { url = "https://files.pythonhosted.org/packages/4e/6e/159cbf2055ef36aa2aa297e01b24523176e5b48ead283c23a94179fb2ba2/pyzmq-26.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:2f9f7ffe9db1187a253fca95191854b3fda24696f086e8789d1d449308a34b88", size = 862409 }, + { url = "https://files.pythonhosted.org/packages/05/1c/45fb8db7be5a7d0cadea1070a9cbded5199a2d578de2208197e592f219bd/pyzmq-26.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3709c9ff7ba61589b7372923fd82b99a81932b592a5c7f1a24147c91da9a68d6", size = 1205007 }, + { url = "https://files.pythonhosted.org/packages/f8/fa/658c7f583af6498b463f2fa600f34e298e1b330886f82f1feba0dc2dd6c3/pyzmq-26.4.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f8f3c30fb2d26ae5ce36b59768ba60fb72507ea9efc72f8f69fa088450cff1df", size = 1514599 }, + { url = "https://files.pythonhosted.org/packages/4d/d7/44d641522353ce0a2bbd150379cb5ec32f7120944e6bfba4846586945658/pyzmq-26.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:382a4a48c8080e273427fc692037e3f7d2851959ffe40864f2db32646eeb3cef", size = 1414546 }, + { url = "https://files.pythonhosted.org/packages/72/76/c8ed7263218b3d1e9bce07b9058502024188bd52cc0b0a267a9513b431fc/pyzmq-26.4.0-cp311-cp311-win32.whl", hash = "sha256:d56aad0517d4c09e3b4f15adebba8f6372c5102c27742a5bdbfc74a7dceb8fca", size = 579247 }, + { url = "https://files.pythonhosted.org/packages/c3/d0/2d9abfa2571a0b1a67c0ada79a8aa1ba1cce57992d80f771abcdf99bb32c/pyzmq-26.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:963977ac8baed7058c1e126014f3fe58b3773f45c78cce7af5c26c09b6823896", size = 644727 }, + { url = "https://files.pythonhosted.org/packages/0d/d1/c8ad82393be6ccedfc3c9f3adb07f8f3976e3c4802640fe3f71441941e70/pyzmq-26.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:c0c8e8cadc81e44cc5088fcd53b9b3b4ce9344815f6c4a03aec653509296fae3", size = 559942 }, + { url = "https://files.pythonhosted.org/packages/10/44/a778555ebfdf6c7fc00816aad12d185d10a74d975800341b1bc36bad1187/pyzmq-26.4.0-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:5227cb8da4b6f68acfd48d20c588197fd67745c278827d5238c707daf579227b", size = 1341586 }, + { url = "https://files.pythonhosted.org/packages/9c/4f/f3a58dc69ac757e5103be3bd41fb78721a5e17da7cc617ddb56d973a365c/pyzmq-26.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1c07a7fa7f7ba86554a2b1bef198c9fed570c08ee062fd2fd6a4dcacd45f905", size = 665880 }, + { url = "https://files.pythonhosted.org/packages/fe/45/50230bcfb3ae5cb98bee683b6edeba1919f2565d7cc1851d3c38e2260795/pyzmq-26.4.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae775fa83f52f52de73183f7ef5395186f7105d5ed65b1ae65ba27cb1260de2b", size = 902216 }, + { url = "https://files.pythonhosted.org/packages/41/59/56bbdc5689be5e13727491ad2ba5efd7cd564365750514f9bc8f212eef82/pyzmq-26.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66c760d0226ebd52f1e6b644a9e839b5db1e107a23f2fcd46ec0569a4fdd4e63", size = 859814 }, + { url = "https://files.pythonhosted.org/packages/81/b1/57db58cfc8af592ce94f40649bd1804369c05b2190e4cbc0a2dad572baeb/pyzmq-26.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:ef8c6ecc1d520debc147173eaa3765d53f06cd8dbe7bd377064cdbc53ab456f5", size = 855889 }, + { url = "https://files.pythonhosted.org/packages/e8/92/47542e629cbac8f221c230a6d0f38dd3d9cff9f6f589ed45fdf572ffd726/pyzmq-26.4.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3150ef4084e163dec29ae667b10d96aad309b668fac6810c9e8c27cf543d6e0b", size = 1197153 }, + { url = "https://files.pythonhosted.org/packages/07/e5/b10a979d1d565d54410afc87499b16c96b4a181af46e7645ab4831b1088c/pyzmq-26.4.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4448c9e55bf8329fa1dcedd32f661bf611214fa70c8e02fee4347bc589d39a84", size = 1507352 }, + { url = "https://files.pythonhosted.org/packages/ab/58/5a23db84507ab9c01c04b1232a7a763be66e992aa2e66498521bbbc72a71/pyzmq-26.4.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e07dde3647afb084d985310d067a3efa6efad0621ee10826f2cb2f9a31b89d2f", size = 1406834 }, + { url = "https://files.pythonhosted.org/packages/22/74/aaa837b331580c13b79ac39396601fb361454ee184ca85e8861914769b99/pyzmq-26.4.0-cp312-cp312-win32.whl", hash = "sha256:ba034a32ecf9af72adfa5ee383ad0fd4f4e38cdb62b13624278ef768fe5b5b44", size = 577992 }, + { url = "https://files.pythonhosted.org/packages/30/0f/55f8c02c182856743b82dde46b2dc3e314edda7f1098c12a8227eeda0833/pyzmq-26.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:056a97aab4064f526ecb32f4343917a4022a5d9efb6b9df990ff72e1879e40be", size = 640466 }, + { url = "https://files.pythonhosted.org/packages/e4/29/073779afc3ef6f830b8de95026ef20b2d1ec22d0324d767748d806e57379/pyzmq-26.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:2f23c750e485ce1eb639dbd576d27d168595908aa2d60b149e2d9e34c9df40e0", size = 556342 }, + { url = "https://files.pythonhosted.org/packages/d7/20/fb2c92542488db70f833b92893769a569458311a76474bda89dc4264bd18/pyzmq-26.4.0-cp313-cp313-macosx_10_15_universal2.whl", hash = "sha256:c43fac689880f5174d6fc864857d1247fe5cfa22b09ed058a344ca92bf5301e3", size = 1339484 }, + { url = "https://files.pythonhosted.org/packages/58/29/2f06b9cabda3a6ea2c10f43e67ded3e47fc25c54822e2506dfb8325155d4/pyzmq-26.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:902aca7eba477657c5fb81c808318460328758e8367ecdd1964b6330c73cae43", size = 666106 }, + { url = "https://files.pythonhosted.org/packages/77/e4/dcf62bd29e5e190bd21bfccaa4f3386e01bf40d948c239239c2f1e726729/pyzmq-26.4.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5e48a830bfd152fe17fbdeaf99ac5271aa4122521bf0d275b6b24e52ef35eb6", size = 902056 }, + { url = "https://files.pythonhosted.org/packages/1a/cf/b36b3d7aea236087d20189bec1a87eeb2b66009731d7055e5c65f845cdba/pyzmq-26.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31be2b6de98c824c06f5574331f805707c667dc8f60cb18580b7de078479891e", size = 860148 }, + { url = "https://files.pythonhosted.org/packages/18/a6/f048826bc87528c208e90604c3bf573801e54bd91e390cbd2dfa860e82dc/pyzmq-26.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:6332452034be001bbf3206ac59c0d2a7713de5f25bb38b06519fc6967b7cf771", size = 855983 }, + { url = "https://files.pythonhosted.org/packages/0a/27/454d34ab6a1d9772a36add22f17f6b85baf7c16e14325fa29e7202ca8ee8/pyzmq-26.4.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:da8c0f5dd352136853e6a09b1b986ee5278dfddfebd30515e16eae425c872b30", size = 1197274 }, + { url = "https://files.pythonhosted.org/packages/f4/3d/7abfeab6b83ad38aa34cbd57c6fc29752c391e3954fd12848bd8d2ec0df6/pyzmq-26.4.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:f4ccc1a0a2c9806dda2a2dd118a3b7b681e448f3bb354056cad44a65169f6d86", size = 1507120 }, + { url = "https://files.pythonhosted.org/packages/13/ff/bc8d21dbb9bc8705126e875438a1969c4f77e03fc8565d6901c7933a3d01/pyzmq-26.4.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:1c0b5fceadbab461578daf8d1dcc918ebe7ddd2952f748cf30c7cf2de5d51101", size = 1406738 }, + { url = "https://files.pythonhosted.org/packages/f5/5d/d4cd85b24de71d84d81229e3bbb13392b2698432cf8fdcea5afda253d587/pyzmq-26.4.0-cp313-cp313-win32.whl", hash = "sha256:28e2b0ff5ba4b3dd11062d905682bad33385cfa3cc03e81abd7f0822263e6637", size = 577826 }, + { url = "https://files.pythonhosted.org/packages/c6/6c/f289c1789d7bb6e5a3b3bef7b2a55089b8561d17132be7d960d3ff33b14e/pyzmq-26.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:23ecc9d241004c10e8b4f49d12ac064cd7000e1643343944a10df98e57bc544b", size = 640406 }, + { url = "https://files.pythonhosted.org/packages/b3/99/676b8851cb955eb5236a0c1e9ec679ea5ede092bf8bf2c8a68d7e965cac3/pyzmq-26.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:1edb0385c7f025045d6e0f759d4d3afe43c17a3d898914ec6582e6f464203c08", size = 556216 }, + { url = "https://files.pythonhosted.org/packages/65/c2/1fac340de9d7df71efc59d9c50fc7a635a77b103392d1842898dd023afcb/pyzmq-26.4.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:93a29e882b2ba1db86ba5dd5e88e18e0ac6b627026c5cfbec9983422011b82d4", size = 1333769 }, + { url = "https://files.pythonhosted.org/packages/5c/c7/6c03637e8d742c3b00bec4f5e4cd9d1c01b2f3694c6f140742e93ca637ed/pyzmq-26.4.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb45684f276f57110bb89e4300c00f1233ca631f08f5f42528a5c408a79efc4a", size = 658826 }, + { url = "https://files.pythonhosted.org/packages/a5/97/a8dca65913c0f78e0545af2bb5078aebfc142ca7d91cdaffa1fbc73e5dbd/pyzmq-26.4.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f72073e75260cb301aad4258ad6150fa7f57c719b3f498cb91e31df16784d89b", size = 891650 }, + { url = "https://files.pythonhosted.org/packages/7d/7e/f63af1031eb060bf02d033732b910fe48548dcfdbe9c785e9f74a6cc6ae4/pyzmq-26.4.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be37e24b13026cfedd233bcbbccd8c0bcd2fdd186216094d095f60076201538d", size = 849776 }, + { url = "https://files.pythonhosted.org/packages/f6/fa/1a009ce582802a895c0d5fe9413f029c940a0a8ee828657a3bb0acffd88b/pyzmq-26.4.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:237b283044934d26f1eeff4075f751b05d2f3ed42a257fc44386d00df6a270cf", size = 842516 }, + { url = "https://files.pythonhosted.org/packages/6e/bc/f88b0bad0f7a7f500547d71e99f10336f2314e525d4ebf576a1ea4a1d903/pyzmq-26.4.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:b30f862f6768b17040929a68432c8a8be77780317f45a353cb17e423127d250c", size = 1189183 }, + { url = "https://files.pythonhosted.org/packages/d9/8c/db446a3dd9cf894406dec2e61eeffaa3c07c3abb783deaebb9812c4af6a5/pyzmq-26.4.0-cp313-cp313t-musllinux_1_1_i686.whl", hash = "sha256:c80fcd3504232f13617c6ab501124d373e4895424e65de8b72042333316f64a8", size = 1495501 }, + { url = "https://files.pythonhosted.org/packages/05/4c/bf3cad0d64c3214ac881299c4562b815f05d503bccc513e3fd4fdc6f67e4/pyzmq-26.4.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:26a2a7451606b87f67cdeca2c2789d86f605da08b4bd616b1a9981605ca3a364", size = 1395540 }, + { url = "https://files.pythonhosted.org/packages/04/52/a70fcd5592715702248306d8e1729c10742c2eac44529984413b05c68658/pyzmq-26.4.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4478b14cb54a805088299c25a79f27eaf530564a7a4f72bf432a040042b554eb", size = 834405 }, + { url = "https://files.pythonhosted.org/packages/25/f9/1a03f1accff16b3af1a6fa22cbf7ced074776abbf688b2e9cb4629700c62/pyzmq-26.4.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a28ac29c60e4ba84b5f58605ace8ad495414a724fe7aceb7cf06cd0598d04e1", size = 569578 }, + { url = "https://files.pythonhosted.org/packages/76/0c/3a633acd762aa6655fcb71fa841907eae0ab1e8582ff494b137266de341d/pyzmq-26.4.0-pp311-pypy311_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43b03c1ceea27c6520124f4fb2ba9c647409b9abdf9a62388117148a90419494", size = 798248 }, + { url = "https://files.pythonhosted.org/packages/cd/cc/6c99c84aa60ac1cc56747bed6be8ce6305b9b861d7475772e7a25ce019d3/pyzmq-26.4.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7731abd23a782851426d4e37deb2057bf9410848a4459b5ede4fe89342e687a9", size = 756757 }, + { url = "https://files.pythonhosted.org/packages/13/9c/d8073bd898eb896e94c679abe82e47506e2b750eb261cf6010ced869797c/pyzmq-26.4.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:a222ad02fbe80166b0526c038776e8042cd4e5f0dec1489a006a1df47e9040e0", size = 555371 }, +] + +[[package]] +name = "referencing" +version = "0.36.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775 }, +] + +[[package]] +name = "regex" +version = "2024.11.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/5f/bd69653fbfb76cf8604468d3b4ec4c403197144c7bfe0e6a5fc9e02a07cb/regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519", size = 399494 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/58/7e4d9493a66c88a7da6d205768119f51af0f684fe7be7bac8328e217a52c/regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638", size = 482669 }, + { url = "https://files.pythonhosted.org/packages/34/4c/8f8e631fcdc2ff978609eaeef1d6994bf2f028b59d9ac67640ed051f1218/regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7", size = 287684 }, + { url = "https://files.pythonhosted.org/packages/c5/1b/f0e4d13e6adf866ce9b069e191f303a30ab1277e037037a365c3aad5cc9c/regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20", size = 284589 }, + { url = "https://files.pythonhosted.org/packages/25/4d/ab21047f446693887f25510887e6820b93f791992994f6498b0318904d4a/regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114", size = 792121 }, + { url = "https://files.pythonhosted.org/packages/45/ee/c867e15cd894985cb32b731d89576c41a4642a57850c162490ea34b78c3b/regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3", size = 831275 }, + { url = "https://files.pythonhosted.org/packages/b3/12/b0f480726cf1c60f6536fa5e1c95275a77624f3ac8fdccf79e6727499e28/regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f", size = 818257 }, + { url = "https://files.pythonhosted.org/packages/bf/ce/0d0e61429f603bac433910d99ef1a02ce45a8967ffbe3cbee48599e62d88/regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0", size = 792727 }, + { url = "https://files.pythonhosted.org/packages/e4/c1/243c83c53d4a419c1556f43777ccb552bccdf79d08fda3980e4e77dd9137/regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55", size = 780667 }, + { url = "https://files.pythonhosted.org/packages/c5/f4/75eb0dd4ce4b37f04928987f1d22547ddaf6c4bae697623c1b05da67a8aa/regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89", size = 776963 }, + { url = "https://files.pythonhosted.org/packages/16/5d/95c568574e630e141a69ff8a254c2f188b4398e813c40d49228c9bbd9875/regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d", size = 784700 }, + { url = "https://files.pythonhosted.org/packages/8e/b5/f8495c7917f15cc6fee1e7f395e324ec3e00ab3c665a7dc9d27562fd5290/regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34", size = 848592 }, + { url = "https://files.pythonhosted.org/packages/1c/80/6dd7118e8cb212c3c60b191b932dc57db93fb2e36fb9e0e92f72a5909af9/regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d", size = 852929 }, + { url = "https://files.pythonhosted.org/packages/11/9b/5a05d2040297d2d254baf95eeeb6df83554e5e1df03bc1a6687fc4ba1f66/regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45", size = 781213 }, + { url = "https://files.pythonhosted.org/packages/26/b7/b14e2440156ab39e0177506c08c18accaf2b8932e39fb092074de733d868/regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9", size = 261734 }, + { url = "https://files.pythonhosted.org/packages/80/32/763a6cc01d21fb3819227a1cc3f60fd251c13c37c27a73b8ff4315433a8e/regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60", size = 274052 }, + { url = "https://files.pythonhosted.org/packages/ba/30/9a87ce8336b172cc232a0db89a3af97929d06c11ceaa19d97d84fa90a8f8/regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a", size = 483781 }, + { url = "https://files.pythonhosted.org/packages/01/e8/00008ad4ff4be8b1844786ba6636035f7ef926db5686e4c0f98093612add/regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9", size = 288455 }, + { url = "https://files.pythonhosted.org/packages/60/85/cebcc0aff603ea0a201667b203f13ba75d9fc8668fab917ac5b2de3967bc/regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2", size = 284759 }, + { url = "https://files.pythonhosted.org/packages/94/2b/701a4b0585cb05472a4da28ee28fdfe155f3638f5e1ec92306d924e5faf0/regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4", size = 794976 }, + { url = "https://files.pythonhosted.org/packages/4b/bf/fa87e563bf5fee75db8915f7352e1887b1249126a1be4813837f5dbec965/regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577", size = 833077 }, + { url = "https://files.pythonhosted.org/packages/a1/56/7295e6bad94b047f4d0834e4779491b81216583c00c288252ef625c01d23/regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3", size = 823160 }, + { url = "https://files.pythonhosted.org/packages/fb/13/e3b075031a738c9598c51cfbc4c7879e26729c53aa9cca59211c44235314/regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e", size = 796896 }, + { url = "https://files.pythonhosted.org/packages/24/56/0b3f1b66d592be6efec23a795b37732682520b47c53da5a32c33ed7d84e3/regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe", size = 783997 }, + { url = "https://files.pythonhosted.org/packages/f9/a1/eb378dada8b91c0e4c5f08ffb56f25fcae47bf52ad18f9b2f33b83e6d498/regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e", size = 781725 }, + { url = "https://files.pythonhosted.org/packages/83/f2/033e7dec0cfd6dda93390089864732a3409246ffe8b042e9554afa9bff4e/regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29", size = 789481 }, + { url = "https://files.pythonhosted.org/packages/83/23/15d4552ea28990a74e7696780c438aadd73a20318c47e527b47a4a5a596d/regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39", size = 852896 }, + { url = "https://files.pythonhosted.org/packages/e3/39/ed4416bc90deedbfdada2568b2cb0bc1fdb98efe11f5378d9892b2a88f8f/regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51", size = 860138 }, + { url = "https://files.pythonhosted.org/packages/93/2d/dd56bb76bd8e95bbce684326302f287455b56242a4f9c61f1bc76e28360e/regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad", size = 787692 }, + { url = "https://files.pythonhosted.org/packages/0b/55/31877a249ab7a5156758246b9c59539abbeba22461b7d8adc9e8475ff73e/regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54", size = 262135 }, + { url = "https://files.pythonhosted.org/packages/38/ec/ad2d7de49a600cdb8dd78434a1aeffe28b9d6fc42eb36afab4a27ad23384/regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b", size = 273567 }, + { url = "https://files.pythonhosted.org/packages/90/73/bcb0e36614601016552fa9344544a3a2ae1809dc1401b100eab02e772e1f/regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84", size = 483525 }, + { url = "https://files.pythonhosted.org/packages/0f/3f/f1a082a46b31e25291d830b369b6b0c5576a6f7fb89d3053a354c24b8a83/regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4", size = 288324 }, + { url = "https://files.pythonhosted.org/packages/09/c9/4e68181a4a652fb3ef5099e077faf4fd2a694ea6e0f806a7737aff9e758a/regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0", size = 284617 }, + { url = "https://files.pythonhosted.org/packages/fc/fd/37868b75eaf63843165f1d2122ca6cb94bfc0271e4428cf58c0616786dce/regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0", size = 795023 }, + { url = "https://files.pythonhosted.org/packages/c4/7c/d4cd9c528502a3dedb5c13c146e7a7a539a3853dc20209c8e75d9ba9d1b2/regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7", size = 833072 }, + { url = "https://files.pythonhosted.org/packages/4f/db/46f563a08f969159c5a0f0e722260568425363bea43bb7ae370becb66a67/regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7", size = 823130 }, + { url = "https://files.pythonhosted.org/packages/db/60/1eeca2074f5b87df394fccaa432ae3fc06c9c9bfa97c5051aed70e6e00c2/regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c", size = 796857 }, + { url = "https://files.pythonhosted.org/packages/10/db/ac718a08fcee981554d2f7bb8402f1faa7e868c1345c16ab1ebec54b0d7b/regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3", size = 784006 }, + { url = "https://files.pythonhosted.org/packages/c2/41/7da3fe70216cea93144bf12da2b87367590bcf07db97604edeea55dac9ad/regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07", size = 781650 }, + { url = "https://files.pythonhosted.org/packages/a7/d5/880921ee4eec393a4752e6ab9f0fe28009435417c3102fc413f3fe81c4e5/regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e", size = 789545 }, + { url = "https://files.pythonhosted.org/packages/dc/96/53770115e507081122beca8899ab7f5ae28ae790bfcc82b5e38976df6a77/regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6", size = 853045 }, + { url = "https://files.pythonhosted.org/packages/31/d3/1372add5251cc2d44b451bd94f43b2ec78e15a6e82bff6a290ef9fd8f00a/regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4", size = 860182 }, + { url = "https://files.pythonhosted.org/packages/ed/e3/c446a64984ea9f69982ba1a69d4658d5014bc7a0ea468a07e1a1265db6e2/regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d", size = 787733 }, + { url = "https://files.pythonhosted.org/packages/2b/f1/e40c8373e3480e4f29f2692bd21b3e05f296d3afebc7e5dcf21b9756ca1c/regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff", size = 262122 }, + { url = "https://files.pythonhosted.org/packages/45/94/bc295babb3062a731f52621cdc992d123111282e291abaf23faa413443ea/regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a", size = 273545 }, +] + +[[package]] +name = "requests" +version = "2.32.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, +] + +[[package]] +name = "rfc3339-validator" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/ea/a9387748e2d111c3c2b275ba970b735e04e15cdb1eb30693b6b5708c4dbd/rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", size = 5513 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa", size = 3490 }, +] + +[[package]] +name = "rfc3986-validator" +version = "0.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/88/f270de456dd7d11dcc808abfa291ecdd3f45ff44e3b549ffa01b126464d0/rfc3986_validator-0.1.1.tar.gz", hash = "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055", size = 6760 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl", hash = "sha256:2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9", size = 4242 }, +] + +[[package]] +name = "rpds-py" +version = "0.24.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/b3/52b213298a0ba7097c7ea96bee95e1947aa84cc816d48cebb539770cdf41/rpds_py-0.24.0.tar.gz", hash = "sha256:772cc1b2cd963e7e17e6cc55fe0371fb9c704d63e44cacec7b9b7f523b78919e", size = 26863 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/80/e6/c1458bbfb257448fdb2528071f1f4e19e26798ed5ef6d47d7aab0cb69661/rpds_py-0.24.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:2d3ee4615df36ab8eb16c2507b11e764dcc11fd350bbf4da16d09cda11fcedef", size = 377679 }, + { url = "https://files.pythonhosted.org/packages/dd/26/ea4181ef78f58b2c167548c6a833d7dc22408e5b3b181bda9dda440bb92d/rpds_py-0.24.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e13ae74a8a3a0c2f22f450f773e35f893484fcfacb00bb4344a7e0f4f48e1f97", size = 362571 }, + { url = "https://files.pythonhosted.org/packages/56/fa/1ec54dd492c64c280a2249a047fc3369e2789dc474eac20445ebfc72934b/rpds_py-0.24.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf86f72d705fc2ef776bb7dd9e5fbba79d7e1f3e258bf9377f8204ad0fc1c51e", size = 388012 }, + { url = "https://files.pythonhosted.org/packages/3a/be/bad8b0e0f7e58ef4973bb75e91c472a7d51da1977ed43b09989264bf065c/rpds_py-0.24.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c43583ea8517ed2e780a345dd9960896afc1327e8cf3ac8239c167530397440d", size = 394730 }, + { url = "https://files.pythonhosted.org/packages/35/56/ab417fc90c21826df048fc16e55316ac40876e4b790104ececcbce813d8f/rpds_py-0.24.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4cd031e63bc5f05bdcda120646a0d32f6d729486d0067f09d79c8db5368f4586", size = 448264 }, + { url = "https://files.pythonhosted.org/packages/b6/75/4c63862d5c05408589196c8440a35a14ea4ae337fa70ded1f03638373f06/rpds_py-0.24.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:34d90ad8c045df9a4259c47d2e16a3f21fdb396665c94520dbfe8766e62187a4", size = 446813 }, + { url = "https://files.pythonhosted.org/packages/e7/0c/91cf17dffa9a38835869797a9f041056091ebba6a53963d3641207e3d467/rpds_py-0.24.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e838bf2bb0b91ee67bf2b889a1a841e5ecac06dd7a2b1ef4e6151e2ce155c7ae", size = 389438 }, + { url = "https://files.pythonhosted.org/packages/1b/b0/60e6c72727c978276e02851819f3986bc40668f115be72c1bc4d922c950f/rpds_py-0.24.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04ecf5c1ff4d589987b4d9882872f80ba13da7d42427234fce8f22efb43133bc", size = 420416 }, + { url = "https://files.pythonhosted.org/packages/a1/d7/f46f85b9f863fb59fd3c534b5c874c48bee86b19e93423b9da8784605415/rpds_py-0.24.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:630d3d8ea77eabd6cbcd2ea712e1c5cecb5b558d39547ac988351195db433f6c", size = 565236 }, + { url = "https://files.pythonhosted.org/packages/2a/d1/1467620ded6dd70afc45ec822cdf8dfe7139537780d1f3905de143deb6fd/rpds_py-0.24.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ebcb786b9ff30b994d5969213a8430cbb984cdd7ea9fd6df06663194bd3c450c", size = 592016 }, + { url = "https://files.pythonhosted.org/packages/5d/13/fb1ded2e6adfaa0c0833106c42feb290973f665300f4facd5bf5d7891d9c/rpds_py-0.24.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:174e46569968ddbbeb8a806d9922f17cd2b524aa753b468f35b97ff9c19cb718", size = 560123 }, + { url = "https://files.pythonhosted.org/packages/1e/df/09fc1857ac7cc2eb16465a7199c314cbce7edde53c8ef21d615410d7335b/rpds_py-0.24.0-cp311-cp311-win32.whl", hash = "sha256:5ef877fa3bbfb40b388a5ae1cb00636a624690dcb9a29a65267054c9ea86d88a", size = 222256 }, + { url = "https://files.pythonhosted.org/packages/ff/25/939b40bc4d54bf910e5ee60fb5af99262c92458f4948239e8c06b0b750e7/rpds_py-0.24.0-cp311-cp311-win_amd64.whl", hash = "sha256:e274f62cbd274359eff63e5c7e7274c913e8e09620f6a57aae66744b3df046d6", size = 234718 }, + { url = "https://files.pythonhosted.org/packages/1a/e0/1c55f4a3be5f1ca1a4fd1f3ff1504a1478c1ed48d84de24574c4fa87e921/rpds_py-0.24.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:d8551e733626afec514b5d15befabea0dd70a343a9f23322860c4f16a9430205", size = 366945 }, + { url = "https://files.pythonhosted.org/packages/39/1b/a3501574fbf29118164314dbc800d568b8c1c7b3258b505360e8abb3902c/rpds_py-0.24.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e374c0ce0ca82e5b67cd61fb964077d40ec177dd2c4eda67dba130de09085c7", size = 351935 }, + { url = "https://files.pythonhosted.org/packages/dc/47/77d3d71c55f6a374edde29f1aca0b2e547325ed00a9da820cabbc9497d2b/rpds_py-0.24.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d69d003296df4840bd445a5d15fa5b6ff6ac40496f956a221c4d1f6f7b4bc4d9", size = 390817 }, + { url = "https://files.pythonhosted.org/packages/4e/ec/1e336ee27484379e19c7f9cc170f4217c608aee406d3ae3a2e45336bff36/rpds_py-0.24.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8212ff58ac6dfde49946bea57474a386cca3f7706fc72c25b772b9ca4af6b79e", size = 401983 }, + { url = "https://files.pythonhosted.org/packages/07/f8/39b65cbc272c635eaea6d393c2ad1ccc81c39eca2db6723a0ca4b2108fce/rpds_py-0.24.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:528927e63a70b4d5f3f5ccc1fa988a35456eb5d15f804d276709c33fc2f19bda", size = 451719 }, + { url = "https://files.pythonhosted.org/packages/32/05/05c2b27dd9c30432f31738afed0300659cb9415db0ff7429b05dfb09bbde/rpds_py-0.24.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a824d2c7a703ba6daaca848f9c3d5cb93af0505be505de70e7e66829affd676e", size = 442546 }, + { url = "https://files.pythonhosted.org/packages/7d/e0/19383c8b5d509bd741532a47821c3e96acf4543d0832beba41b4434bcc49/rpds_py-0.24.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44d51febb7a114293ffd56c6cf4736cb31cd68c0fddd6aa303ed09ea5a48e029", size = 393695 }, + { url = "https://files.pythonhosted.org/packages/9d/15/39f14e96d94981d0275715ae8ea564772237f3fa89bc3c21e24de934f2c7/rpds_py-0.24.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3fab5f4a2c64a8fb64fc13b3d139848817a64d467dd6ed60dcdd6b479e7febc9", size = 427218 }, + { url = "https://files.pythonhosted.org/packages/22/b9/12da7124905a680f690da7a9de6f11de770b5e359f5649972f7181c8bf51/rpds_py-0.24.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9be4f99bee42ac107870c61dfdb294d912bf81c3c6d45538aad7aecab468b6b7", size = 568062 }, + { url = "https://files.pythonhosted.org/packages/88/17/75229017a2143d915f6f803721a6d721eca24f2659c5718a538afa276b4f/rpds_py-0.24.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:564c96b6076a98215af52f55efa90d8419cc2ef45d99e314fddefe816bc24f91", size = 596262 }, + { url = "https://files.pythonhosted.org/packages/aa/64/8e8a1d8bd1b6b638d6acb6d41ab2cec7f2067a5b8b4c9175703875159a7c/rpds_py-0.24.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:75a810b7664c17f24bf2ffd7f92416c00ec84b49bb68e6a0d93e542406336b56", size = 564306 }, + { url = "https://files.pythonhosted.org/packages/68/1c/a7eac8d8ed8cb234a9b1064647824c387753343c3fab6ed7c83481ed0be7/rpds_py-0.24.0-cp312-cp312-win32.whl", hash = "sha256:f6016bd950be4dcd047b7475fdf55fb1e1f59fc7403f387be0e8123e4a576d30", size = 224281 }, + { url = "https://files.pythonhosted.org/packages/bb/46/b8b5424d1d21f2f2f3f2d468660085318d4f74a8df8289e3dd6ad224d488/rpds_py-0.24.0-cp312-cp312-win_amd64.whl", hash = "sha256:998c01b8e71cf051c28f5d6f1187abbdf5cf45fc0efce5da6c06447cba997034", size = 239719 }, + { url = "https://files.pythonhosted.org/packages/9d/c3/3607abc770395bc6d5a00cb66385a5479fb8cd7416ddef90393b17ef4340/rpds_py-0.24.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:3d2d8e4508e15fc05b31285c4b00ddf2e0eb94259c2dc896771966a163122a0c", size = 367072 }, + { url = "https://files.pythonhosted.org/packages/d8/35/8c7ee0fe465793e3af3298dc5a9f3013bd63e7a69df04ccfded8293a4982/rpds_py-0.24.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0f00c16e089282ad68a3820fd0c831c35d3194b7cdc31d6e469511d9bffc535c", size = 351919 }, + { url = "https://files.pythonhosted.org/packages/91/d3/7e1b972501eb5466b9aca46a9c31bcbbdc3ea5a076e9ab33f4438c1d069d/rpds_py-0.24.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:951cc481c0c395c4a08639a469d53b7d4afa252529a085418b82a6b43c45c240", size = 390360 }, + { url = "https://files.pythonhosted.org/packages/a2/a8/ccabb50d3c91c26ad01f9b09a6a3b03e4502ce51a33867c38446df9f896b/rpds_py-0.24.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c9ca89938dff18828a328af41ffdf3902405a19f4131c88e22e776a8e228c5a8", size = 400704 }, + { url = "https://files.pythonhosted.org/packages/53/ae/5fa5bf0f3bc6ce21b5ea88fc0ecd3a439e7cb09dd5f9ffb3dbe1b6894fc5/rpds_py-0.24.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed0ef550042a8dbcd657dfb284a8ee00f0ba269d3f2286b0493b15a5694f9fe8", size = 450839 }, + { url = "https://files.pythonhosted.org/packages/e3/ac/c4e18b36d9938247e2b54f6a03746f3183ca20e1edd7d3654796867f5100/rpds_py-0.24.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b2356688e5d958c4d5cb964af865bea84db29971d3e563fb78e46e20fe1848b", size = 441494 }, + { url = "https://files.pythonhosted.org/packages/bf/08/b543969c12a8f44db6c0f08ced009abf8f519191ca6985509e7c44102e3c/rpds_py-0.24.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78884d155fd15d9f64f5d6124b486f3d3f7fd7cd71a78e9670a0f6f6ca06fb2d", size = 393185 }, + { url = "https://files.pythonhosted.org/packages/da/7e/f6eb6a7042ce708f9dfc781832a86063cea8a125bbe451d663697b51944f/rpds_py-0.24.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6a4a535013aeeef13c5532f802708cecae8d66c282babb5cd916379b72110cf7", size = 426168 }, + { url = "https://files.pythonhosted.org/packages/38/b0/6cd2bb0509ac0b51af4bb138e145b7c4c902bb4b724d6fd143689d6e0383/rpds_py-0.24.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:84e0566f15cf4d769dade9b366b7b87c959be472c92dffb70462dd0844d7cbad", size = 567622 }, + { url = "https://files.pythonhosted.org/packages/64/b0/c401f4f077547d98e8b4c2ec6526a80e7cb04f519d416430ec1421ee9e0b/rpds_py-0.24.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:823e74ab6fbaa028ec89615ff6acb409e90ff45580c45920d4dfdddb069f2120", size = 595435 }, + { url = "https://files.pythonhosted.org/packages/9f/ec/7993b6e803294c87b61c85bd63e11142ccfb2373cf88a61ec602abcbf9d6/rpds_py-0.24.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c61a2cb0085c8783906b2f8b1f16a7e65777823c7f4d0a6aaffe26dc0d358dd9", size = 563762 }, + { url = "https://files.pythonhosted.org/packages/1f/29/4508003204cb2f461dc2b83dd85f8aa2b915bc98fe6046b9d50d4aa05401/rpds_py-0.24.0-cp313-cp313-win32.whl", hash = "sha256:60d9b630c8025b9458a9d114e3af579a2c54bd32df601c4581bd054e85258143", size = 223510 }, + { url = "https://files.pythonhosted.org/packages/f9/12/09e048d1814195e01f354155fb772fb0854bd3450b5f5a82224b3a319f0e/rpds_py-0.24.0-cp313-cp313-win_amd64.whl", hash = "sha256:6eea559077d29486c68218178ea946263b87f1c41ae7f996b1f30a983c476a5a", size = 239075 }, + { url = "https://files.pythonhosted.org/packages/d2/03/5027cde39bb2408d61e4dd0cf81f815949bb629932a6c8df1701d0257fc4/rpds_py-0.24.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:d09dc82af2d3c17e7dd17120b202a79b578d79f2b5424bda209d9966efeed114", size = 362974 }, + { url = "https://files.pythonhosted.org/packages/bf/10/24d374a2131b1ffafb783e436e770e42dfdb74b69a2cd25eba8c8b29d861/rpds_py-0.24.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5fc13b44de6419d1e7a7e592a4885b323fbc2f46e1f22151e3a8ed3b8b920405", size = 348730 }, + { url = "https://files.pythonhosted.org/packages/7a/d1/1ef88d0516d46cd8df12e5916966dbf716d5ec79b265eda56ba1b173398c/rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c347a20d79cedc0a7bd51c4d4b7dbc613ca4e65a756b5c3e57ec84bd43505b47", size = 387627 }, + { url = "https://files.pythonhosted.org/packages/4e/35/07339051b8b901ecefd449ebf8e5522e92bcb95e1078818cbfd9db8e573c/rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:20f2712bd1cc26a3cc16c5a1bfee9ed1abc33d4cdf1aabd297fe0eb724df4272", size = 394094 }, + { url = "https://files.pythonhosted.org/packages/dc/62/ee89ece19e0ba322b08734e95441952062391065c157bbd4f8802316b4f1/rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aad911555286884be1e427ef0dc0ba3929e6821cbeca2194b13dc415a462c7fd", size = 449639 }, + { url = "https://files.pythonhosted.org/packages/15/24/b30e9f9e71baa0b9dada3a4ab43d567c6b04a36d1cb531045f7a8a0a7439/rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0aeb3329c1721c43c58cae274d7d2ca85c1690d89485d9c63a006cb79a85771a", size = 438584 }, + { url = "https://files.pythonhosted.org/packages/28/d9/49f7b8f3b4147db13961e19d5e30077cd0854ccc08487026d2cb2142aa4a/rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a0f156e9509cee987283abd2296ec816225145a13ed0391df8f71bf1d789e2d", size = 391047 }, + { url = "https://files.pythonhosted.org/packages/49/b0/e66918d0972c33a259ba3cd7b7ff10ed8bd91dbcfcbec6367b21f026db75/rpds_py-0.24.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:aa6800adc8204ce898c8a424303969b7aa6a5e4ad2789c13f8648739830323b7", size = 418085 }, + { url = "https://files.pythonhosted.org/packages/e1/6b/99ed7ea0a94c7ae5520a21be77a82306aac9e4e715d4435076ead07d05c6/rpds_py-0.24.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a18fc371e900a21d7392517c6f60fe859e802547309e94313cd8181ad9db004d", size = 564498 }, + { url = "https://files.pythonhosted.org/packages/28/26/1cacfee6b800e6fb5f91acecc2e52f17dbf8b0796a7c984b4568b6d70e38/rpds_py-0.24.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9168764133fd919f8dcca2ead66de0105f4ef5659cbb4fa044f7014bed9a1797", size = 590202 }, + { url = "https://files.pythonhosted.org/packages/a9/9e/57bd2f9fba04a37cef673f9a66b11ca8c43ccdd50d386c455cd4380fe461/rpds_py-0.24.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5f6e3cec44ba05ee5cbdebe92d052f69b63ae792e7d05f1020ac5e964394080c", size = 561771 }, + { url = "https://files.pythonhosted.org/packages/9f/cf/b719120f375ab970d1c297dbf8de1e3c9edd26fe92c0ed7178dd94b45992/rpds_py-0.24.0-cp313-cp313t-win32.whl", hash = "sha256:8ebc7e65ca4b111d928b669713865f021b7773350eeac4a31d3e70144297baba", size = 221195 }, + { url = "https://files.pythonhosted.org/packages/2d/e5/22865285789f3412ad0c3d7ec4dc0a3e86483b794be8a5d9ed5a19390900/rpds_py-0.24.0-cp313-cp313t-win_amd64.whl", hash = "sha256:675269d407a257b8c00a6b58205b72eec8231656506c56fd429d924ca00bb350", size = 237354 }, + { url = "https://files.pythonhosted.org/packages/65/53/40bcc246a8354530d51a26d2b5b9afd1deacfb0d79e67295cc74df362f52/rpds_py-0.24.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f9e0057a509e096e47c87f753136c9b10d7a91842d8042c2ee6866899a717c0d", size = 378386 }, + { url = "https://files.pythonhosted.org/packages/80/b0/5ea97dd2f53e3618560aa1f9674e896e63dff95a9b796879a201bc4c1f00/rpds_py-0.24.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d6e109a454412ab82979c5b1b3aee0604eca4bbf9a02693bb9df027af2bfa91a", size = 363440 }, + { url = "https://files.pythonhosted.org/packages/57/9d/259b6eada6f747cdd60c9a5eb3efab15f6704c182547149926c38e5bd0d5/rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc1c892b1ec1f8cbd5da8de287577b455e388d9c328ad592eabbdcb6fc93bee5", size = 388816 }, + { url = "https://files.pythonhosted.org/packages/94/c1/faafc7183712f89f4b7620c3c15979ada13df137d35ef3011ae83e93b005/rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c39438c55983d48f4bb3487734d040e22dad200dab22c41e331cee145e7a50d", size = 395058 }, + { url = "https://files.pythonhosted.org/packages/6c/96/d7fa9d2a7b7604a61da201cc0306a355006254942093779d7121c64700ce/rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d7e8ce990ae17dda686f7e82fd41a055c668e13ddcf058e7fb5e9da20b57793", size = 448692 }, + { url = "https://files.pythonhosted.org/packages/96/37/a3146c6eebc65d6d8c96cc5ffdcdb6af2987412c789004213227fbe52467/rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9ea7f4174d2e4194289cb0c4e172d83e79a6404297ff95f2875cf9ac9bced8ba", size = 446462 }, + { url = "https://files.pythonhosted.org/packages/1f/13/6481dfd9ac7de43acdaaa416e3a7da40bc4bb8f5c6ca85e794100aa54596/rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb2954155bb8f63bb19d56d80e5e5320b61d71084617ed89efedb861a684baea", size = 390460 }, + { url = "https://files.pythonhosted.org/packages/61/e1/37e36bce65e109543cc4ff8d23206908649023549604fa2e7fbeba5342f7/rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04f2b712a2206e13800a8136b07aaedc23af3facab84918e7aa89e4be0260032", size = 421609 }, + { url = "https://files.pythonhosted.org/packages/20/dd/1f1a923d6cd798b8582176aca8a0784676f1a0449fb6f07fce6ac1cdbfb6/rpds_py-0.24.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:eda5c1e2a715a4cbbca2d6d304988460942551e4e5e3b7457b50943cd741626d", size = 565818 }, + { url = "https://files.pythonhosted.org/packages/56/ec/d8da6df6a1eb3a418944a17b1cb38dd430b9e5a2e972eafd2b06f10c7c46/rpds_py-0.24.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:9abc80fe8c1f87218db116016de575a7998ab1629078c90840e8d11ab423ee25", size = 592627 }, + { url = "https://files.pythonhosted.org/packages/b3/14/c492b9c7d5dd133e13f211ddea6bb9870f99e4f73932f11aa00bc09a9be9/rpds_py-0.24.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:6a727fd083009bc83eb83d6950f0c32b3c94c8b80a9b667c87f4bd1274ca30ba", size = 560885 }, +] + +[[package]] +name = "ruff" +version = "0.11.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/4c/4a3c5a97faaae6b428b336dcca81d03ad04779f8072c267ad2bd860126bf/ruff-0.11.10.tar.gz", hash = "sha256:d522fb204b4959909ecac47da02830daec102eeb100fb50ea9554818d47a5fa6", size = 4165632 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/9f/596c628f8824a2ce4cd12b0f0b4c0629a62dfffc5d0f742c19a1d71be108/ruff-0.11.10-py3-none-linux_armv6l.whl", hash = "sha256:859a7bfa7bc8888abbea31ef8a2b411714e6a80f0d173c2a82f9041ed6b50f58", size = 10316243 }, + { url = "https://files.pythonhosted.org/packages/3c/38/c1e0b77ab58b426f8c332c1d1d3432d9fc9a9ea622806e208220cb133c9e/ruff-0.11.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:968220a57e09ea5e4fd48ed1c646419961a0570727c7e069842edd018ee8afed", size = 11083636 }, + { url = "https://files.pythonhosted.org/packages/23/41/b75e15961d6047d7fe1b13886e56e8413be8467a4e1be0a07f3b303cd65a/ruff-0.11.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1067245bad978e7aa7b22f67113ecc6eb241dca0d9b696144256c3a879663bca", size = 10441624 }, + { url = "https://files.pythonhosted.org/packages/b6/2c/e396b6703f131406db1811ea3d746f29d91b41bbd43ad572fea30da1435d/ruff-0.11.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4854fd09c7aed5b1590e996a81aeff0c9ff51378b084eb5a0b9cd9518e6cff2", size = 10624358 }, + { url = "https://files.pythonhosted.org/packages/bd/8c/ee6cca8bdaf0f9a3704796022851a33cd37d1340bceaf4f6e991eb164e2e/ruff-0.11.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b4564e9f99168c0f9195a0fd5fa5928004b33b377137f978055e40008a082c5", size = 10176850 }, + { url = "https://files.pythonhosted.org/packages/e9/ce/4e27e131a434321b3b7c66512c3ee7505b446eb1c8a80777c023f7e876e6/ruff-0.11.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b6a9cc5b62c03cc1fea0044ed8576379dbaf751d5503d718c973d5418483641", size = 11759787 }, + { url = "https://files.pythonhosted.org/packages/58/de/1e2e77fc72adc7cf5b5123fd04a59ed329651d3eab9825674a9e640b100b/ruff-0.11.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:607ecbb6f03e44c9e0a93aedacb17b4eb4f3563d00e8b474298a201622677947", size = 12430479 }, + { url = "https://files.pythonhosted.org/packages/07/ed/af0f2340f33b70d50121628ef175523cc4c37619e98d98748c85764c8d88/ruff-0.11.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7b3a522fa389402cd2137df9ddefe848f727250535c70dafa840badffb56b7a4", size = 11919760 }, + { url = "https://files.pythonhosted.org/packages/24/09/d7b3d3226d535cb89234390f418d10e00a157b6c4a06dfbe723e9322cb7d/ruff-0.11.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f071b0deed7e9245d5820dac235cbdd4ef99d7b12ff04c330a241ad3534319f", size = 14041747 }, + { url = "https://files.pythonhosted.org/packages/62/b3/a63b4e91850e3f47f78795e6630ee9266cb6963de8f0191600289c2bb8f4/ruff-0.11.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a60e3a0a617eafba1f2e4186d827759d65348fa53708ca547e384db28406a0b", size = 11550657 }, + { url = "https://files.pythonhosted.org/packages/46/63/a4f95c241d79402ccdbdb1d823d156c89fbb36ebfc4289dce092e6c0aa8f/ruff-0.11.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:da8ec977eaa4b7bf75470fb575bea2cb41a0e07c7ea9d5a0a97d13dbca697bf2", size = 10489671 }, + { url = "https://files.pythonhosted.org/packages/6a/9b/c2238bfebf1e473495659c523d50b1685258b6345d5ab0b418ca3f010cd7/ruff-0.11.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ddf8967e08227d1bd95cc0851ef80d2ad9c7c0c5aab1eba31db49cf0a7b99523", size = 10160135 }, + { url = "https://files.pythonhosted.org/packages/ba/ef/ba7251dd15206688dbfba7d413c0312e94df3b31b08f5d695580b755a899/ruff-0.11.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5a94acf798a82db188f6f36575d80609072b032105d114b0f98661e1679c9125", size = 11170179 }, + { url = "https://files.pythonhosted.org/packages/73/9f/5c336717293203ba275dbfa2ea16e49b29a9fd9a0ea8b6febfc17e133577/ruff-0.11.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3afead355f1d16d95630df28d4ba17fb2cb9c8dfac8d21ced14984121f639bad", size = 11626021 }, + { url = "https://files.pythonhosted.org/packages/d9/2b/162fa86d2639076667c9aa59196c020dc6d7023ac8f342416c2f5ec4bda0/ruff-0.11.10-py3-none-win32.whl", hash = "sha256:dc061a98d32a97211af7e7f3fa1d4ca2fcf919fb96c28f39551f35fc55bdbc19", size = 10494958 }, + { url = "https://files.pythonhosted.org/packages/24/f3/66643d8f32f50a4b0d09a4832b7d919145ee2b944d43e604fbd7c144d175/ruff-0.11.10-py3-none-win_amd64.whl", hash = "sha256:5cc725fbb4d25b0f185cb42df07ab6b76c4489b4bfb740a175f3a59c70e8a224", size = 11650285 }, + { url = "https://files.pythonhosted.org/packages/95/3a/2e8704d19f376c799748ff9cb041225c1d59f3e7711bc5596c8cfdc24925/ruff-0.11.10-py3-none-win_arm64.whl", hash = "sha256:ef69637b35fb8b210743926778d0e45e1bffa850a7c61e428c6b971549b5f5d1", size = 10765278 }, +] + +[[package]] +name = "safetensors" +version = "0.5.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/71/7e/2d5d6ee7b40c0682315367ec7475693d110f512922d582fef1bd4a63adc3/safetensors-0.5.3.tar.gz", hash = "sha256:b6b0d6ecacec39a4fdd99cc19f4576f5219ce858e6fd8dbe7609df0b8dc56965", size = 67210 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/ae/88f6c49dbd0cc4da0e08610019a3c78a7d390879a919411a410a1876d03a/safetensors-0.5.3-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:bd20eb133db8ed15b40110b7c00c6df51655a2998132193de2f75f72d99c7073", size = 436917 }, + { url = "https://files.pythonhosted.org/packages/b8/3b/11f1b4a2f5d2ab7da34ecc062b0bc301f2be024d110a6466726bec8c055c/safetensors-0.5.3-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:21d01c14ff6c415c485616b8b0bf961c46b3b343ca59110d38d744e577f9cce7", size = 418419 }, + { url = "https://files.pythonhosted.org/packages/5d/9a/add3e6fef267658075c5a41573c26d42d80c935cdc992384dfae435feaef/safetensors-0.5.3-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11bce6164887cd491ca75c2326a113ba934be596e22b28b1742ce27b1d076467", size = 459493 }, + { url = "https://files.pythonhosted.org/packages/df/5c/bf2cae92222513cc23b3ff85c4a1bb2811a2c3583ac0f8e8d502751de934/safetensors-0.5.3-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4a243be3590bc3301c821da7a18d87224ef35cbd3e5f5727e4e0728b8172411e", size = 472400 }, + { url = "https://files.pythonhosted.org/packages/58/11/7456afb740bd45782d0f4c8e8e1bb9e572f1bf82899fb6ace58af47b4282/safetensors-0.5.3-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8bd84b12b1670a6f8e50f01e28156422a2bc07fb16fc4e98bded13039d688a0d", size = 522891 }, + { url = "https://files.pythonhosted.org/packages/57/3d/fe73a9d2ace487e7285f6e157afee2383bd1ddb911b7cb44a55cf812eae3/safetensors-0.5.3-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:391ac8cab7c829452175f871fcaf414aa1e292b5448bd02620f675a7f3e7abb9", size = 537694 }, + { url = "https://files.pythonhosted.org/packages/a6/f8/dae3421624fcc87a89d42e1898a798bc7ff72c61f38973a65d60df8f124c/safetensors-0.5.3-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cead1fa41fc54b1e61089fa57452e8834f798cb1dc7a09ba3524f1eb08e0317a", size = 471642 }, + { url = "https://files.pythonhosted.org/packages/ce/20/1fbe16f9b815f6c5a672f5b760951e20e17e43f67f231428f871909a37f6/safetensors-0.5.3-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1077f3e94182d72618357b04b5ced540ceb71c8a813d3319f1aba448e68a770d", size = 502241 }, + { url = "https://files.pythonhosted.org/packages/5f/18/8e108846b506487aa4629fe4116b27db65c3dde922de2c8e0cc1133f3f29/safetensors-0.5.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:799021e78287bac619c7b3f3606730a22da4cda27759ddf55d37c8db7511c74b", size = 638001 }, + { url = "https://files.pythonhosted.org/packages/82/5a/c116111d8291af6c8c8a8b40628fe833b9db97d8141c2a82359d14d9e078/safetensors-0.5.3-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:df26da01aaac504334644e1b7642fa000bfec820e7cef83aeac4e355e03195ff", size = 734013 }, + { url = "https://files.pythonhosted.org/packages/7d/ff/41fcc4d3b7de837963622e8610d998710705bbde9a8a17221d85e5d0baad/safetensors-0.5.3-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:32c3ef2d7af8b9f52ff685ed0bc43913cdcde135089ae322ee576de93eae5135", size = 670687 }, + { url = "https://files.pythonhosted.org/packages/40/ad/2b113098e69c985a3d8fbda4b902778eae4a35b7d5188859b4a63d30c161/safetensors-0.5.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:37f1521be045e56fc2b54c606d4455573e717b2d887c579ee1dbba5f868ece04", size = 643147 }, + { url = "https://files.pythonhosted.org/packages/0a/0c/95aeb51d4246bd9a3242d3d8349c1112b4ee7611a4b40f0c5c93b05f001d/safetensors-0.5.3-cp38-abi3-win32.whl", hash = "sha256:cfc0ec0846dcf6763b0ed3d1846ff36008c6e7290683b61616c4b040f6a54ace", size = 296677 }, + { url = "https://files.pythonhosted.org/packages/69/e2/b011c38e5394c4c18fb5500778a55ec43ad6106126e74723ffaee246f56e/safetensors-0.5.3-cp38-abi3-win_amd64.whl", hash = "sha256:836cbbc320b47e80acd40e44c8682db0e8ad7123209f69b093def21ec7cafd11", size = 308878 }, +] + +[[package]] +name = "scikit-learn" +version = "1.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "joblib" }, + { name = "numpy" }, + { name = "scipy" }, + { name = "threadpoolctl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/41/84/5f4af978fff619706b8961accac84780a6d298d82a8873446f72edb4ead0/scikit_learn-1.7.1.tar.gz", hash = "sha256:24b3f1e976a4665aa74ee0fcaac2b8fccc6ae77c8e07ab25da3ba6d3292b9802", size = 7190445 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/bd/a23177930abd81b96daffa30ef9c54ddbf544d3226b8788ce4c3ef1067b4/scikit_learn-1.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:90c8494ea23e24c0fb371afc474618c1019dc152ce4a10e4607e62196113851b", size = 9334838 }, + { url = "https://files.pythonhosted.org/packages/8d/a1/d3a7628630a711e2ac0d1a482910da174b629f44e7dd8cfcd6924a4ef81a/scikit_learn-1.7.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:bb870c0daf3bf3be145ec51df8ac84720d9972170786601039f024bf6d61a518", size = 8651241 }, + { url = "https://files.pythonhosted.org/packages/26/92/85ec172418f39474c1cd0221d611345d4f433fc4ee2fc68e01f524ccc4e4/scikit_learn-1.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:40daccd1b5623f39e8943ab39735cadf0bdce80e67cdca2adcb5426e987320a8", size = 9718677 }, + { url = "https://files.pythonhosted.org/packages/df/ce/abdb1dcbb1d2b66168ec43b23ee0cee356b4cc4100ddee3943934ebf1480/scikit_learn-1.7.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:30d1f413cfc0aa5a99132a554f1d80517563c34a9d3e7c118fde2d273c6fe0f7", size = 9511189 }, + { url = "https://files.pythonhosted.org/packages/b2/3b/47b5eaee01ef2b5a80ba3f7f6ecf79587cb458690857d4777bfd77371c6f/scikit_learn-1.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:c711d652829a1805a95d7fe96654604a8f16eab5a9e9ad87b3e60173415cb650", size = 8914794 }, + { url = "https://files.pythonhosted.org/packages/cb/16/57f176585b35ed865f51b04117947fe20f130f78940c6477b6d66279c9c2/scikit_learn-1.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3cee419b49b5bbae8796ecd690f97aa412ef1674410c23fc3257c6b8b85b8087", size = 9260431 }, + { url = "https://files.pythonhosted.org/packages/67/4e/899317092f5efcab0e9bc929e3391341cec8fb0e816c4789686770024580/scikit_learn-1.7.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2fd8b8d35817b0d9ebf0b576f7d5ffbbabdb55536b0655a8aaae629d7ffd2e1f", size = 8637191 }, + { url = "https://files.pythonhosted.org/packages/f3/1b/998312db6d361ded1dd56b457ada371a8d8d77ca2195a7d18fd8a1736f21/scikit_learn-1.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:588410fa19a96a69763202f1d6b7b91d5d7a5d73be36e189bc6396bfb355bd87", size = 9486346 }, + { url = "https://files.pythonhosted.org/packages/ad/09/a2aa0b4e644e5c4ede7006748f24e72863ba2ae71897fecfd832afea01b4/scikit_learn-1.7.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e3142f0abe1ad1d1c31a2ae987621e41f6b578144a911ff4ac94781a583adad7", size = 9290988 }, + { url = "https://files.pythonhosted.org/packages/15/fa/c61a787e35f05f17fc10523f567677ec4eeee5f95aa4798dbbbcd9625617/scikit_learn-1.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:3ddd9092c1bd469acab337d87930067c87eac6bd544f8d5027430983f1e1ae88", size = 8735568 }, + { url = "https://files.pythonhosted.org/packages/52/f8/e0533303f318a0f37b88300d21f79b6ac067188d4824f1047a37214ab718/scikit_learn-1.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b7839687fa46d02e01035ad775982f2470be2668e13ddd151f0f55a5bf123bae", size = 9213143 }, + { url = "https://files.pythonhosted.org/packages/71/f3/f1df377d1bdfc3e3e2adc9c119c238b182293e6740df4cbeac6de2cc3e23/scikit_learn-1.7.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:a10f276639195a96c86aa572ee0698ad64ee939a7b042060b98bd1930c261d10", size = 8591977 }, + { url = "https://files.pythonhosted.org/packages/99/72/c86a4cd867816350fe8dee13f30222340b9cd6b96173955819a5561810c5/scikit_learn-1.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:13679981fdaebc10cc4c13c43344416a86fcbc61449cb3e6517e1df9d12c8309", size = 9436142 }, + { url = "https://files.pythonhosted.org/packages/e8/66/277967b29bd297538dc7a6ecfb1a7dce751beabd0d7f7a2233be7a4f7832/scikit_learn-1.7.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f1262883c6a63f067a980a8cdd2d2e7f2513dddcef6a9eaada6416a7a7cbe43", size = 9282996 }, + { url = "https://files.pythonhosted.org/packages/e2/47/9291cfa1db1dae9880420d1e07dbc7e8dd4a7cdbc42eaba22512e6bde958/scikit_learn-1.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:ca6d31fb10e04d50bfd2b50d66744729dbb512d4efd0223b864e2fdbfc4cee11", size = 8707418 }, + { url = "https://files.pythonhosted.org/packages/61/95/45726819beccdaa34d3362ea9b2ff9f2b5d3b8bf721bd632675870308ceb/scikit_learn-1.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:781674d096303cfe3d351ae6963ff7c958db61cde3421cd490e3a5a58f2a94ae", size = 9561466 }, + { url = "https://files.pythonhosted.org/packages/ee/1c/6f4b3344805de783d20a51eb24d4c9ad4b11a7f75c1801e6ec6d777361fd/scikit_learn-1.7.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:10679f7f125fe7ecd5fad37dd1aa2daae7e3ad8df7f3eefa08901b8254b3e12c", size = 9040467 }, + { url = "https://files.pythonhosted.org/packages/6f/80/abe18fe471af9f1d181904203d62697998b27d9b62124cd281d740ded2f9/scikit_learn-1.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1f812729e38c8cb37f760dce71a9b83ccfb04f59b3dca7c6079dcdc60544fa9e", size = 9532052 }, + { url = "https://files.pythonhosted.org/packages/14/82/b21aa1e0c4cee7e74864d3a5a721ab8fcae5ca55033cb6263dca297ed35b/scikit_learn-1.7.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:88e1a20131cf741b84b89567e1717f27a2ced228e0f29103426102bc2e3b8ef7", size = 9361575 }, + { url = "https://files.pythonhosted.org/packages/f2/20/f4777fcd5627dc6695fa6b92179d0edb7a3ac1b91bcd9a1c7f64fa7ade23/scikit_learn-1.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b1bd1d919210b6a10b7554b717c9000b5485aa95a1d0f177ae0d7ee8ec750da5", size = 9277310 }, +] + +[[package]] +name = "scipy" +version = "1.16.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/81/18/b06a83f0c5ee8cddbde5e3f3d0bb9b702abfa5136ef6d4620ff67df7eee5/scipy-1.16.0.tar.gz", hash = "sha256:b5ef54021e832869c8cfb03bc3bf20366cbcd426e02a58e8a58d7584dfbb8f62", size = 30581216 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/f8/53fc4884df6b88afd5f5f00240bdc49fee2999c7eff3acf5953eb15bc6f8/scipy-1.16.0-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:deec06d831b8f6b5fb0b652433be6a09db29e996368ce5911faf673e78d20085", size = 36447362 }, + { url = "https://files.pythonhosted.org/packages/c9/25/fad8aa228fa828705142a275fc593d701b1817c98361a2d6b526167d07bc/scipy-1.16.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:d30c0fe579bb901c61ab4bb7f3eeb7281f0d4c4a7b52dbf563c89da4fd2949be", size = 28547120 }, + { url = "https://files.pythonhosted.org/packages/8d/be/d324ddf6b89fd1c32fecc307f04d095ce84abb52d2e88fab29d0cd8dc7a8/scipy-1.16.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:b2243561b45257f7391d0f49972fca90d46b79b8dbcb9b2cb0f9df928d370ad4", size = 20818922 }, + { url = "https://files.pythonhosted.org/packages/cd/e0/cf3f39e399ac83fd0f3ba81ccc5438baba7cfe02176be0da55ff3396f126/scipy-1.16.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:e6d7dfc148135e9712d87c5f7e4f2ddc1304d1582cb3a7d698bbadedb61c7afd", size = 23409695 }, + { url = "https://files.pythonhosted.org/packages/5b/61/d92714489c511d3ffd6830ac0eb7f74f243679119eed8b9048e56b9525a1/scipy-1.16.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:90452f6a9f3fe5a2cf3748e7be14f9cc7d9b124dce19667b54f5b429d680d539", size = 33444586 }, + { url = "https://files.pythonhosted.org/packages/af/2c/40108915fd340c830aee332bb85a9160f99e90893e58008b659b9f3dddc0/scipy-1.16.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a2f0bf2f58031c8701a8b601df41701d2a7be17c7ffac0a4816aeba89c4cdac8", size = 35284126 }, + { url = "https://files.pythonhosted.org/packages/d3/30/e9eb0ad3d0858df35d6c703cba0a7e16a18a56a9e6b211d861fc6f261c5f/scipy-1.16.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6c4abb4c11fc0b857474241b812ce69ffa6464b4bd8f4ecb786cf240367a36a7", size = 35608257 }, + { url = "https://files.pythonhosted.org/packages/c8/ff/950ee3e0d612b375110d8cda211c1f787764b4c75e418a4b71f4a5b1e07f/scipy-1.16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b370f8f6ac6ef99815b0d5c9f02e7ade77b33007d74802efc8316c8db98fd11e", size = 38040541 }, + { url = "https://files.pythonhosted.org/packages/8b/c9/750d34788288d64ffbc94fdb4562f40f609d3f5ef27ab4f3a4ad00c9033e/scipy-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:a16ba90847249bedce8aa404a83fb8334b825ec4a8e742ce6012a7a5e639f95c", size = 38570814 }, + { url = "https://files.pythonhosted.org/packages/01/c0/c943bc8d2bbd28123ad0f4f1eef62525fa1723e84d136b32965dcb6bad3a/scipy-1.16.0-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:7eb6bd33cef4afb9fa5f1fb25df8feeb1e52d94f21a44f1d17805b41b1da3180", size = 36459071 }, + { url = "https://files.pythonhosted.org/packages/99/0d/270e2e9f1a4db6ffbf84c9a0b648499842046e4e0d9b2275d150711b3aba/scipy-1.16.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:1dbc8fdba23e4d80394ddfab7a56808e3e6489176d559c6c71935b11a2d59db1", size = 28490500 }, + { url = "https://files.pythonhosted.org/packages/1c/22/01d7ddb07cff937d4326198ec8d10831367a708c3da72dfd9b7ceaf13028/scipy-1.16.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:7dcf42c380e1e3737b343dec21095c9a9ad3f9cbe06f9c05830b44b1786c9e90", size = 20762345 }, + { url = "https://files.pythonhosted.org/packages/34/7f/87fd69856569ccdd2a5873fe5d7b5bbf2ad9289d7311d6a3605ebde3a94b/scipy-1.16.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:26ec28675f4a9d41587266084c626b02899db373717d9312fa96ab17ca1ae94d", size = 23418563 }, + { url = "https://files.pythonhosted.org/packages/f6/f1/e4f4324fef7f54160ab749efbab6a4bf43678a9eb2e9817ed71a0a2fd8de/scipy-1.16.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:952358b7e58bd3197cfbd2f2f2ba829f258404bdf5db59514b515a8fe7a36c52", size = 33203951 }, + { url = "https://files.pythonhosted.org/packages/6d/f0/b6ac354a956384fd8abee2debbb624648125b298f2c4a7b4f0d6248048a5/scipy-1.16.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:03931b4e870c6fef5b5c0970d52c9f6ddd8c8d3e934a98f09308377eba6f3824", size = 35070225 }, + { url = "https://files.pythonhosted.org/packages/e5/73/5cbe4a3fd4bc3e2d67ffad02c88b83edc88f381b73ab982f48f3df1a7790/scipy-1.16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:512c4f4f85912767c351a0306824ccca6fd91307a9f4318efe8fdbd9d30562ef", size = 35389070 }, + { url = "https://files.pythonhosted.org/packages/86/e8/a60da80ab9ed68b31ea5a9c6dfd3c2f199347429f229bf7f939a90d96383/scipy-1.16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e69f798847e9add03d512eaf5081a9a5c9a98757d12e52e6186ed9681247a1ac", size = 37825287 }, + { url = "https://files.pythonhosted.org/packages/ea/b5/29fece1a74c6a94247f8a6fb93f5b28b533338e9c34fdcc9cfe7a939a767/scipy-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:adf9b1999323ba335adc5d1dc7add4781cb5a4b0ef1e98b79768c05c796c4e49", size = 38431929 }, + { url = "https://files.pythonhosted.org/packages/46/95/0746417bc24be0c2a7b7563946d61f670a3b491b76adede420e9d173841f/scipy-1.16.0-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:e9f414cbe9ca289a73e0cc92e33a6a791469b6619c240aa32ee18abdce8ab451", size = 36418162 }, + { url = "https://files.pythonhosted.org/packages/19/5a/914355a74481b8e4bbccf67259bbde171348a3f160b67b4945fbc5f5c1e5/scipy-1.16.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:bbba55fb97ba3cdef9b1ee973f06b09d518c0c7c66a009c729c7d1592be1935e", size = 28465985 }, + { url = "https://files.pythonhosted.org/packages/58/46/63477fc1246063855969cbefdcee8c648ba4b17f67370bd542ba56368d0b/scipy-1.16.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:58e0d4354eacb6004e7aa1cd350e5514bd0270acaa8d5b36c0627bb3bb486974", size = 20737961 }, + { url = "https://files.pythonhosted.org/packages/93/86/0fbb5588b73555e40f9d3d6dde24ee6fac7d8e301a27f6f0cab9d8f66ff2/scipy-1.16.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:75b2094ec975c80efc273567436e16bb794660509c12c6a31eb5c195cbf4b6dc", size = 23377941 }, + { url = "https://files.pythonhosted.org/packages/ca/80/a561f2bf4c2da89fa631b3cbf31d120e21ea95db71fd9ec00cb0247c7a93/scipy-1.16.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6b65d232157a380fdd11a560e7e21cde34fdb69d65c09cb87f6cc024ee376351", size = 33196703 }, + { url = "https://files.pythonhosted.org/packages/11/6b/3443abcd0707d52e48eb315e33cc669a95e29fc102229919646f5a501171/scipy-1.16.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1d8747f7736accd39289943f7fe53a8333be7f15a82eea08e4afe47d79568c32", size = 35083410 }, + { url = "https://files.pythonhosted.org/packages/20/ab/eb0fc00e1e48961f1bd69b7ad7e7266896fe5bad4ead91b5fc6b3561bba4/scipy-1.16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eb9f147a1b8529bb7fec2a85cf4cf42bdfadf9e83535c309a11fdae598c88e8b", size = 35387829 }, + { url = "https://files.pythonhosted.org/packages/57/9e/d6fc64e41fad5d481c029ee5a49eefc17f0b8071d636a02ceee44d4a0de2/scipy-1.16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d2b83c37edbfa837a8923d19c749c1935ad3d41cf196006a24ed44dba2ec4358", size = 37841356 }, + { url = "https://files.pythonhosted.org/packages/7c/a7/4c94bbe91f12126b8bf6709b2471900577b7373a4fd1f431f28ba6f81115/scipy-1.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:79a3c13d43c95aa80b87328a46031cf52508cf5f4df2767602c984ed1d3c6bbe", size = 38403710 }, + { url = "https://files.pythonhosted.org/packages/47/20/965da8497f6226e8fa90ad3447b82ed0e28d942532e92dd8b91b43f100d4/scipy-1.16.0-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:f91b87e1689f0370690e8470916fe1b2308e5b2061317ff76977c8f836452a47", size = 36813833 }, + { url = "https://files.pythonhosted.org/packages/28/f4/197580c3dac2d234e948806e164601c2df6f0078ed9f5ad4a62685b7c331/scipy-1.16.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:88a6ca658fb94640079e7a50b2ad3b67e33ef0f40e70bdb7dc22017dae73ac08", size = 28974431 }, + { url = "https://files.pythonhosted.org/packages/8a/fc/e18b8550048d9224426e76906694c60028dbdb65d28b1372b5503914b89d/scipy-1.16.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:ae902626972f1bd7e4e86f58fd72322d7f4ec7b0cfc17b15d4b7006efc385176", size = 21246454 }, + { url = "https://files.pythonhosted.org/packages/8c/48/07b97d167e0d6a324bfd7484cd0c209cc27338b67e5deadae578cf48e809/scipy-1.16.0-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:8cb824c1fc75ef29893bc32b3ddd7b11cf9ab13c1127fe26413a05953b8c32ed", size = 23772979 }, + { url = "https://files.pythonhosted.org/packages/4c/4f/9efbd3f70baf9582edf271db3002b7882c875ddd37dc97f0f675ad68679f/scipy-1.16.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:de2db7250ff6514366a9709c2cba35cb6d08498e961cba20d7cff98a7ee88938", size = 33341972 }, + { url = "https://files.pythonhosted.org/packages/3f/dc/9e496a3c5dbe24e76ee24525155ab7f659c20180bab058ef2c5fa7d9119c/scipy-1.16.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e85800274edf4db8dd2e4e93034f92d1b05c9421220e7ded9988b16976f849c1", size = 35185476 }, + { url = "https://files.pythonhosted.org/packages/ce/b3/21001cff985a122ba434c33f2c9d7d1dc3b669827e94f4fc4e1fe8b9dfd8/scipy-1.16.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4f720300a3024c237ace1cb11f9a84c38beb19616ba7c4cdcd771047a10a1706", size = 35570990 }, + { url = "https://files.pythonhosted.org/packages/e5/d3/7ba42647d6709251cdf97043d0c107e0317e152fa2f76873b656b509ff55/scipy-1.16.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:aad603e9339ddb676409b104c48a027e9916ce0d2838830691f39552b38a352e", size = 37950262 }, + { url = "https://files.pythonhosted.org/packages/eb/c4/231cac7a8385394ebbbb4f1ca662203e9d8c332825ab4f36ffc3ead09a42/scipy-1.16.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f56296fefca67ba605fd74d12f7bd23636267731a72cb3947963e76b8c0a25db", size = 38515076 }, +] + +[[package]] +name = "send2trash" +version = "1.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/3a/aec9b02217bb79b87bbc1a21bc6abc51e3d5dcf65c30487ac96c0908c722/Send2Trash-1.8.3.tar.gz", hash = "sha256:b18e7a3966d99871aefeb00cfbcfdced55ce4871194810fc71f4aa484b953abf", size = 17394 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl", hash = "sha256:0c31227e0bd08961c7665474a3d1ef7193929fedda4233843689baa056be46c9", size = 18072 }, +] + +[[package]] +name = "sentencepiece" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/d2/b9c7ca067c26d8ff085d252c89b5f69609ca93fb85a00ede95f4857865d4/sentencepiece-0.2.0.tar.gz", hash = "sha256:a52c19171daaf2e697dc6cbe67684e0fa341b1248966f6aebb541de654d15843", size = 2632106 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/43/8f8885168a47a02eba1455bd3f4f169f50ad5b8cebd2402d0f5e20854d04/sentencepiece-0.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:17982700c4f6dbb55fa3594f3d7e5dd1c8659a274af3738e33c987d2a27c9d5c", size = 2409036 }, + { url = "https://files.pythonhosted.org/packages/0f/35/e63ba28062af0a3d688a9f128e407a1a2608544b2f480cb49bf7f4b1cbb9/sentencepiece-0.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7c867012c0e8bcd5bdad0f791609101cb5c66acb303ab3270218d6debc68a65e", size = 1238921 }, + { url = "https://files.pythonhosted.org/packages/de/42/ae30952c4a0bd773e90c9bf2579f5533037c886dfc8ec68133d5694f4dd2/sentencepiece-0.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7fd6071249c74f779c5b27183295b9202f8dedb68034e716784364443879eaa6", size = 1181477 }, + { url = "https://files.pythonhosted.org/packages/e3/ac/2f2ab1d60bb2d795d054eebe5e3f24b164bc21b5a9b75fba7968b3b91b5a/sentencepiece-0.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27f90c55a65013cbb8f4d7aab0599bf925cde4adc67ae43a0d323677b5a1c6cb", size = 1259182 }, + { url = "https://files.pythonhosted.org/packages/45/fb/14633c6ecf262c468759ffcdb55c3a7ee38fe4eda6a70d75ee7c7d63c58b/sentencepiece-0.2.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b293734059ef656dcd65be62ff771507bea8fed0a711b6733976e1ed3add4553", size = 1355537 }, + { url = "https://files.pythonhosted.org/packages/fb/12/2f5c8d4764b00033cf1c935b702d3bb878d10be9f0b87f0253495832d85f/sentencepiece-0.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e58b47f933aca74c6a60a79dcb21d5b9e47416256c795c2d58d55cec27f9551d", size = 1301464 }, + { url = "https://files.pythonhosted.org/packages/4e/b1/67afc0bde24f6dcb3acdea0dd8dcdf4b8b0db240f6bacd39378bd32d09f8/sentencepiece-0.2.0-cp311-cp311-win32.whl", hash = "sha256:c581258cf346b327c62c4f1cebd32691826306f6a41d8c4bec43b010dee08e75", size = 936749 }, + { url = "https://files.pythonhosted.org/packages/a2/f6/587c62fd21fc988555b85351f50bbde43a51524caafd63bc69240ded14fd/sentencepiece-0.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:0993dbc665f4113017892f1b87c3904a44d0640eda510abcacdfb07f74286d36", size = 991520 }, + { url = "https://files.pythonhosted.org/packages/27/5a/141b227ed54293360a9ffbb7bf8252b4e5efc0400cdeac5809340e5d2b21/sentencepiece-0.2.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ea5f536e32ea8ec96086ee00d7a4a131ce583a1b18d130711707c10e69601cb2", size = 2409370 }, + { url = "https://files.pythonhosted.org/packages/2e/08/a4c135ad6fc2ce26798d14ab72790d66e813efc9589fd30a5316a88ca8d5/sentencepiece-0.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d0cb51f53b6aae3c36bafe41e86167c71af8370a039f542c43b0cce5ef24a68c", size = 1239288 }, + { url = "https://files.pythonhosted.org/packages/49/0a/2fe387f825ac5aad5a0bfe221904882106cac58e1b693ba7818785a882b6/sentencepiece-0.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3212121805afc58d8b00ab4e7dd1f8f76c203ddb9dc94aa4079618a31cf5da0f", size = 1181597 }, + { url = "https://files.pythonhosted.org/packages/cc/38/e4698ee2293fe4835dc033c49796a39b3eebd8752098f6bd0aa53a14af1f/sentencepiece-0.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a3149e3066c2a75e0d68a43eb632d7ae728c7925b517f4c05c40f6f7280ce08", size = 1259220 }, + { url = "https://files.pythonhosted.org/packages/12/24/fd7ef967c9dad2f6e6e5386d0cadaf65cda8b7be6e3861a9ab3121035139/sentencepiece-0.2.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:632f3594d3e7ac8b367bca204cb3fd05a01d5b21455acd097ea4c0e30e2f63d7", size = 1355962 }, + { url = "https://files.pythonhosted.org/packages/4f/d2/18246f43ca730bb81918f87b7e886531eda32d835811ad9f4657c54eee35/sentencepiece-0.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f295105c6bdbb05bd5e1b0cafbd78ff95036f5d3641e7949455a3f4e5e7c3109", size = 1301706 }, + { url = "https://files.pythonhosted.org/packages/8a/47/ca237b562f420044ab56ddb4c278672f7e8c866e183730a20e413b38a989/sentencepiece-0.2.0-cp312-cp312-win32.whl", hash = "sha256:fb89f811e5efd18bab141afc3fea3de141c3f69f3fe9e898f710ae7fe3aab251", size = 936941 }, + { url = "https://files.pythonhosted.org/packages/c6/97/d159c32642306ee2b70732077632895438867b3b6df282354bd550cf2a67/sentencepiece-0.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:7a673a72aab81fef5ebe755c6e0cc60087d1f3a4700835d40537183c1703a45f", size = 991994 }, +] + +[[package]] +name = "sentry-sdk" +version = "2.29.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/67/d552a5f8e5a6a56b2feea6529e2d8ccd54349084c84176d5a1f7295044bc/sentry_sdk-2.29.1.tar.gz", hash = "sha256:8d4a0206b95fa5fe85e5e7517ed662e3888374bdc342c00e435e10e6d831aa6d", size = 325518 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/e5/da07b0bd832cefd52d16f2b9bbbe31624d57552602c06631686b93ccb1bd/sentry_sdk-2.29.1-py2.py3-none-any.whl", hash = "sha256:90862fe0616ded4572da6c9dadb363121a1ae49a49e21c418f0634e9d10b4c19", size = 341553 }, +] + +[[package]] +name = "setproctitle" +version = "1.3.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/af/56efe21c53ac81ac87e000b15e60b3d8104224b4313b6eacac3597bd183d/setproctitle-1.3.6.tar.gz", hash = "sha256:c9f32b96c700bb384f33f7cf07954bb609d35dd82752cef57fb2ee0968409169", size = 26889 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/3b/8288d0cd969a63500dd62fc2c99ce6980f9909ccef0770ab1f86c361e0bf/setproctitle-1.3.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a1d856b0f4e4a33e31cdab5f50d0a14998f3a2d726a3fd5cb7c4d45a57b28d1b", size = 17412 }, + { url = "https://files.pythonhosted.org/packages/39/37/43a5a3e25ca1048dbbf4db0d88d346226f5f1acd131bb8e660f4bfe2799f/setproctitle-1.3.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:50706b9c0eda55f7de18695bfeead5f28b58aa42fd5219b3b1692d554ecbc9ec", size = 11963 }, + { url = "https://files.pythonhosted.org/packages/5b/47/f103c40e133154783c91a10ab08ac9fc410ed835aa85bcf7107cb882f505/setproctitle-1.3.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:af188f3305f0a65c3217c30c6d4c06891e79144076a91e8b454f14256acc7279", size = 31718 }, + { url = "https://files.pythonhosted.org/packages/1f/13/7325dd1c008dd6c0ebd370ddb7505977054a87e406f142318e395031a792/setproctitle-1.3.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cce0ed8b3f64c71c140f0ec244e5fdf8ecf78ddf8d2e591d4a8b6aa1c1214235", size = 33027 }, + { url = "https://files.pythonhosted.org/packages/0c/0a/6075bfea05a71379d77af98a9ac61163e8b6e5ef1ae58cd2b05871b2079c/setproctitle-1.3.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70100e2087fe05359f249a0b5f393127b3a1819bf34dec3a3e0d4941138650c9", size = 30223 }, + { url = "https://files.pythonhosted.org/packages/cc/41/fbf57ec52f4f0776193bd94334a841f0bc9d17e745f89c7790f336420c65/setproctitle-1.3.6-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1065ed36bd03a3fd4186d6c6de5f19846650b015789f72e2dea2d77be99bdca1", size = 31204 }, + { url = "https://files.pythonhosted.org/packages/97/b5/f799fb7a00de29fb0ac1dfd015528dea425b9e31a8f1068a0b3df52d317f/setproctitle-1.3.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4adf6a0013fe4e0844e3ba7583ec203ca518b9394c6cc0d3354df2bf31d1c034", size = 31181 }, + { url = "https://files.pythonhosted.org/packages/b5/b7/81f101b612014ec61723436022c31146178813d6ca6b947f7b9c84e9daf4/setproctitle-1.3.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:eb7452849f6615871eabed6560ffedfe56bc8af31a823b6be4ce1e6ff0ab72c5", size = 30101 }, + { url = "https://files.pythonhosted.org/packages/67/23/681232eed7640eab96719daa8647cc99b639e3daff5c287bd270ef179a73/setproctitle-1.3.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a094b7ce455ca341b59a0f6ce6be2e11411ba6e2860b9aa3dbb37468f23338f4", size = 32438 }, + { url = "https://files.pythonhosted.org/packages/19/f8/4d075a7bdc3609ac71535b849775812455e4c40aedfbf0778a6f123b1774/setproctitle-1.3.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ad1c2c2baaba62823a7f348f469a967ece0062140ca39e7a48e4bbb1f20d54c4", size = 30625 }, + { url = "https://files.pythonhosted.org/packages/5f/73/a2a8259ebee166aee1ca53eead75de0e190b3ddca4f716e5c7470ebb7ef6/setproctitle-1.3.6-cp311-cp311-win32.whl", hash = "sha256:8050c01331135f77ec99d99307bfbc6519ea24d2f92964b06f3222a804a3ff1f", size = 11488 }, + { url = "https://files.pythonhosted.org/packages/c9/15/52cf5e1ff0727d53704cfdde2858eaf237ce523b0b04db65faa84ff83e13/setproctitle-1.3.6-cp311-cp311-win_amd64.whl", hash = "sha256:9b73cf0fe28009a04a35bb2522e4c5b5176cc148919431dcb73fdbdfaab15781", size = 12201 }, + { url = "https://files.pythonhosted.org/packages/8f/fb/99456fd94d4207c5f6c40746a048a33a52b4239cd7d9c8d4889e2210ec82/setproctitle-1.3.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:af44bb7a1af163806bbb679eb8432fa7b4fb6d83a5d403b541b675dcd3798638", size = 17399 }, + { url = "https://files.pythonhosted.org/packages/d5/48/9699191fe6062827683c43bfa9caac33a2c89f8781dd8c7253fa3dba85fd/setproctitle-1.3.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3cca16fd055316a48f0debfcbfb6af7cea715429fc31515ab3fcac05abd527d8", size = 11966 }, + { url = "https://files.pythonhosted.org/packages/33/03/b085d192b9ecb9c7ce6ad6ef30ecf4110b7f39430b58a56245569827fcf4/setproctitle-1.3.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea002088d5554fd75e619742cefc78b84a212ba21632e59931b3501f0cfc8f67", size = 32017 }, + { url = "https://files.pythonhosted.org/packages/ae/68/c53162e645816f97212002111420d1b2f75bf6d02632e37e961dc2cd6d8b/setproctitle-1.3.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb465dd5825356c1191a038a86ee1b8166e3562d6e8add95eec04ab484cfb8a2", size = 33419 }, + { url = "https://files.pythonhosted.org/packages/ac/0d/119a45d15a816a6cf5ccc61b19729f82620095b27a47e0a6838216a95fae/setproctitle-1.3.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d2c8e20487b3b73c1fa72c56f5c89430617296cd380373e7af3a538a82d4cd6d", size = 30711 }, + { url = "https://files.pythonhosted.org/packages/e3/fb/5e9b5068df9e9f31a722a775a5e8322a29a638eaaa3eac5ea7f0b35e6314/setproctitle-1.3.6-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0d6252098e98129a1decb59b46920d4eca17b0395f3d71b0d327d086fefe77d", size = 31742 }, + { url = "https://files.pythonhosted.org/packages/35/88/54de1e73e8fce87d587889c7eedb48fc4ee2bbe4e4ca6331690d03024f86/setproctitle-1.3.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cf355fbf0d4275d86f9f57be705d8e5eaa7f8ddb12b24ced2ea6cbd68fdb14dc", size = 31925 }, + { url = "https://files.pythonhosted.org/packages/f3/01/65948d7badd66e63e3db247b923143da142790fa293830fdecf832712c2d/setproctitle-1.3.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e288f8a162d663916060beb5e8165a8551312b08efee9cf68302687471a6545d", size = 30981 }, + { url = "https://files.pythonhosted.org/packages/22/20/c495e61786f1d38d5dc340b9d9077fee9be3dfc7e89f515afe12e1526dbc/setproctitle-1.3.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b2e54f4a2dc6edf0f5ea5b1d0a608d2af3dcb5aa8c8eeab9c8841b23e1b054fe", size = 33209 }, + { url = "https://files.pythonhosted.org/packages/98/3f/a457b8550fbd34d5b482fe20b8376b529e76bf1fbf9a474a6d9a641ab4ad/setproctitle-1.3.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b6f4abde9a2946f57e8daaf1160b2351bcf64274ef539e6675c1d945dbd75e2a", size = 31587 }, + { url = "https://files.pythonhosted.org/packages/44/fe/743517340e5a635e3f1c4310baea20c16c66202f96a6f4cead222ffd6d84/setproctitle-1.3.6-cp312-cp312-win32.whl", hash = "sha256:db608db98ccc21248370d30044a60843b3f0f3d34781ceeea67067c508cd5a28", size = 11487 }, + { url = "https://files.pythonhosted.org/packages/60/9a/d88f1c1f0f4efff1bd29d9233583ee341114dda7d9613941453984849674/setproctitle-1.3.6-cp312-cp312-win_amd64.whl", hash = "sha256:082413db8a96b1f021088e8ec23f0a61fec352e649aba20881895815388b66d3", size = 12208 }, + { url = "https://files.pythonhosted.org/packages/89/76/f1a2fdbf9b9602945a7489ba5c52e9863de37381ef1a85a2b9ed0ff8bc79/setproctitle-1.3.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e2a9e62647dc040a76d55563580bf3bb8fe1f5b6ead08447c2ed0d7786e5e794", size = 17392 }, + { url = "https://files.pythonhosted.org/packages/5c/5b/4e0db8b10b4543afcb3dbc0827793d46e43ec1de6b377e313af3703d08e0/setproctitle-1.3.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:751ba352ed922e0af60458e961167fa7b732ac31c0ddd1476a2dfd30ab5958c5", size = 11951 }, + { url = "https://files.pythonhosted.org/packages/dc/fe/d5d00aaa700fe1f6160b6e95c225b29c01f4d9292176d48fd968815163ea/setproctitle-1.3.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7890e291bf4708e3b61db9069ea39b3ab0651e42923a5e1f4d78a7b9e4b18301", size = 32087 }, + { url = "https://files.pythonhosted.org/packages/9f/b3/894b827b93ef813c082479bebf88185860f01ac243df737823dd705e7fff/setproctitle-1.3.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2b17855ed7f994f3f259cf2dfbfad78814538536fa1a91b50253d84d87fd88d", size = 33502 }, + { url = "https://files.pythonhosted.org/packages/b2/cd/5330734cca1a4cfcb721432c22cb7899ff15a4101ba868b2ef452ffafea1/setproctitle-1.3.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e51ec673513465663008ce402171192a053564865c2fc6dc840620871a9bd7c", size = 30713 }, + { url = "https://files.pythonhosted.org/packages/fa/d3/c2590c5daa2e9a008d3f2b16c0f4a351826193be55f147cb32af49c6d814/setproctitle-1.3.6-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63cc10352dc6cf35a33951656aa660d99f25f574eb78132ce41a85001a638aa7", size = 31792 }, + { url = "https://files.pythonhosted.org/packages/e6/b1/c553ed5af8cfcecd5ae7737e63af58a17a03d26f3d61868c7eb20bf7e3cf/setproctitle-1.3.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0dba8faee2e4a96e934797c9f0f2d093f8239bf210406a99060b3eabe549628e", size = 31927 }, + { url = "https://files.pythonhosted.org/packages/70/78/2d5385206540127a3dca0ff83225b1ac66873f5cc89d4a6d3806c92f5ae2/setproctitle-1.3.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e3e44d08b61de0dd6f205528498f834a51a5c06689f8fb182fe26f3a3ce7dca9", size = 30981 }, + { url = "https://files.pythonhosted.org/packages/31/62/e3e4a4e006d0e549748e53cded4ff3b667be0602860fc61b7de8b412b667/setproctitle-1.3.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:de004939fc3fd0c1200d26ea9264350bfe501ffbf46c8cf5dc7f345f2d87a7f1", size = 33244 }, + { url = "https://files.pythonhosted.org/packages/aa/05/4b223fd4ef94e105dc7aff27fa502fb7200cf52be2bb0c064bd2406b5611/setproctitle-1.3.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3f8194b4d631b003a1176a75d1acd545e04b1f54b821638e098a93e6e62830ef", size = 31630 }, + { url = "https://files.pythonhosted.org/packages/1b/ba/5f68eb969f7336f54b54a599fd3ffbd7662f9733b080bc8598705971b3dd/setproctitle-1.3.6-cp313-cp313-win32.whl", hash = "sha256:d714e002dd3638170fe7376dc1b686dbac9cb712cde3f7224440af722cc9866a", size = 11480 }, + { url = "https://files.pythonhosted.org/packages/ba/f5/7f47f0ca35c9c357f16187cee9229f3eda0237bc6fdd3061441336f361c0/setproctitle-1.3.6-cp313-cp313-win_amd64.whl", hash = "sha256:b70c07409d465f3a8b34d52f863871fb8a00755370791d2bd1d4f82b3cdaf3d5", size = 12198 }, + { url = "https://files.pythonhosted.org/packages/39/ad/c3941b8fc6b32a976c9e2d9615a90ae793b69cd010ca8c3575dbc822104f/setproctitle-1.3.6-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:23a57d3b8f1549515c2dbe4a2880ebc1f27780dc126c5e064167563e015817f5", size = 17401 }, + { url = "https://files.pythonhosted.org/packages/04/38/a184f857b988d3a9c401e470a4e38182a5c99ee77bf90432d7665e9d35a3/setproctitle-1.3.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:81c443310831e29fabbd07b75ebbfa29d0740b56f5907c6af218482d51260431", size = 11959 }, + { url = "https://files.pythonhosted.org/packages/b7/b9/4878ef9d8483adfd1edf6bf95151362aaec0d05aac306a97ff0383f491b5/setproctitle-1.3.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d88c63bd395c787b0aa81d8bbc22c1809f311032ce3e823a6517b711129818e4", size = 33463 }, + { url = "https://files.pythonhosted.org/packages/cc/60/3ef49d1931aff2a36a7324a49cca10d77ef03e0278452fd468c33a52d7e3/setproctitle-1.3.6-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73f14b86d0e2858ece6bf5807c9889670e392c001d414b4293d0d9b291942c3", size = 34959 }, + { url = "https://files.pythonhosted.org/packages/81/c6/dee0a973acecefb0db6c9c2e0ea7f18b7e4db773a72e534741ebdee8bbb8/setproctitle-1.3.6-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3393859eb8f19f5804049a685bf286cb08d447e28ba5c6d8543c7bf5500d5970", size = 32055 }, + { url = "https://files.pythonhosted.org/packages/ea/a5/5dd5c4192cf18d16349a32a07f728a9a48a2a05178e16966cabd6645903e/setproctitle-1.3.6-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:785cd210c0311d9be28a70e281a914486d62bfd44ac926fcd70cf0b4d65dff1c", size = 32986 }, + { url = "https://files.pythonhosted.org/packages/df/a6/1508d37eb8008670d33f13fcdb91cbd8ef54697276469abbfdd3d4428c59/setproctitle-1.3.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c051f46ed1e13ba8214b334cbf21902102807582fbfaf0fef341b9e52f0fafbf", size = 32736 }, + { url = "https://files.pythonhosted.org/packages/1a/73/c84ec8880d543766a12fcd6b65dbd013770974a40577889f357409b0441e/setproctitle-1.3.6-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:49498ebf68ca3e75321ffe634fcea5cc720502bfaa79bd6b03ded92ce0dc3c24", size = 31945 }, + { url = "https://files.pythonhosted.org/packages/95/0a/126b9ff7a406a69a62825fe5bd6d1ba8671919a7018c4f9e2c63f49bfcb6/setproctitle-1.3.6-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:4431629c178193f23c538cb1de3da285a99ccc86b20ee91d81eb5f1a80e0d2ba", size = 34333 }, + { url = "https://files.pythonhosted.org/packages/9a/fd/5474b04f1c013ff460129d2bc774557dd6e186da4667865efef9a83bf378/setproctitle-1.3.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d136fbf8ad4321716e44d6d6b3d8dffb4872626010884e07a1db54b7450836cf", size = 32508 }, + { url = "https://files.pythonhosted.org/packages/32/21/2503e38520cb076a7ecaef6a35d6a6fa89cf02af3541c84c811fd7500d20/setproctitle-1.3.6-cp313-cp313t-win32.whl", hash = "sha256:d483cc23cc56ab32911ea0baa0d2d9ea7aa065987f47de847a0a93a58bf57905", size = 11482 }, + { url = "https://files.pythonhosted.org/packages/65/23/7833d75a27fba25ddc5cd3b54cd03c4bf8e18b8e2dbec622eb6326278ce8/setproctitle-1.3.6-cp313-cp313t-win_amd64.whl", hash = "sha256:74973aebea3543ad033b9103db30579ec2b950a466e09f9c2180089e8346e0ec", size = 12209 }, +] + +[[package]] +name = "setuptools" +version = "78.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/5a/0db4da3bc908df06e5efae42b44e75c81dd52716e10192ff36d0c1c8e379/setuptools-78.1.0.tar.gz", hash = "sha256:18fd474d4a82a5f83dac888df697af65afa82dec7323d09c3e37d1f14288da54", size = 1367827 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/21/f43f0a1fa8b06b32812e0975981f4677d28e0f3271601dc88ac5a5b83220/setuptools-78.1.0-py3-none-any.whl", hash = "sha256:3e386e96793c8702ae83d17b853fb93d3e09ef82ec62722e61da5cd22376dcd8", size = 1256108 }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, +] + +[[package]] +name = "smmap" +version = "5.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/44/cd/a040c4b3119bbe532e5b0732286f805445375489fceaec1f48306068ee3b/smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5", size = 22329 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/be/d09147ad1ec7934636ad912901c5fd7667e1c858e19d355237db0d0cd5e4/smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e", size = 24303 }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, +] + +[[package]] +name = "soupsieve" +version = "2.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/ce/fbaeed4f9fb8b2daa961f90591662df6a86c1abf25c548329a86920aedfb/soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb", size = 101569 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/c2/fe97d779f3ef3b15f05c94a2f1e3d21732574ed441687474db9d342a7315/soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9", size = 36186 }, +] + +[[package]] +name = "stack-data" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asttokens" }, + { name = "executing" }, + { name = "pure-eval" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521 }, +] + +[[package]] +name = "sympy" +version = "1.13.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mpmath" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ca/99/5a5b6f19ff9f083671ddf7b9632028436167cd3d33e11015754e41b249a4/sympy-1.13.1.tar.gz", hash = "sha256:9cebf7e04ff162015ce31c9c6c9144daa34a93bd082f54fd8f12deca4f47515f", size = 7533040 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/fe/81695a1aa331a842b582453b605175f419fe8540355886031328089d840a/sympy-1.13.1-py3-none-any.whl", hash = "sha256:db36cdc64bf61b9b24578b6f7bab1ecdd2452cf008f34faa33776680c26d66f8", size = 6189177 }, +] + +[[package]] +name = "terminado" +version = "0.18.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess", marker = "os_name != 'nt'" }, + { name = "pywinpty", marker = "os_name == 'nt' and sys_platform != 'linux'" }, + { name = "tornado" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/11/965c6fd8e5cc254f1fe142d547387da17a8ebfd75a3455f637c663fb38a0/terminado-0.18.1.tar.gz", hash = "sha256:de09f2c4b85de4765f7714688fff57d3e75bad1f909b589fde880460c753fd2e", size = 32701 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl", hash = "sha256:a4468e1b37bb318f8a86514f65814e1afc977cf29b3992a4500d9dd305dcceb0", size = 14154 }, +] + +[[package]] +name = "threadpoolctl" +version = "3.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e", size = 21274 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638 }, +] + +[[package]] +name = "tinycss2" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "webencodings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/fd/7a5ee21fd08ff70d3d33a5781c255cbe779659bd03278feb98b19ee550f4/tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7", size = 87085 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289", size = 26610 }, +] + +[[package]] +name = "tokenizers" +version = "0.21.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/92/76/5ac0c97f1117b91b7eb7323dcd61af80d72f790b4df71249a7850c195f30/tokenizers-0.21.1.tar.gz", hash = "sha256:a1bb04dc5b448985f86ecd4b05407f5a8d97cb2c0532199b2a302a604a0165ab", size = 343256 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/1f/328aee25f9115bf04262e8b4e5a2050b7b7cf44b59c74e982db7270c7f30/tokenizers-0.21.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:e78e413e9e668ad790a29456e677d9d3aa50a9ad311a40905d6861ba7692cf41", size = 2780767 }, + { url = "https://files.pythonhosted.org/packages/ae/1a/4526797f3719b0287853f12c5ad563a9be09d446c44ac784cdd7c50f76ab/tokenizers-0.21.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:cd51cd0a91ecc801633829fcd1fda9cf8682ed3477c6243b9a095539de4aecf3", size = 2650555 }, + { url = "https://files.pythonhosted.org/packages/4d/7a/a209b29f971a9fdc1da86f917fe4524564924db50d13f0724feed37b2a4d/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28da6b72d4fb14ee200a1bd386ff74ade8992d7f725f2bde2c495a9a98cf4d9f", size = 2937541 }, + { url = "https://files.pythonhosted.org/packages/3c/1e/b788b50ffc6191e0b1fc2b0d49df8cff16fe415302e5ceb89f619d12c5bc/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:34d8cfde551c9916cb92014e040806122295a6800914bab5865deb85623931cf", size = 2819058 }, + { url = "https://files.pythonhosted.org/packages/36/aa/3626dfa09a0ecc5b57a8c58eeaeb7dd7ca9a37ad9dd681edab5acd55764c/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aaa852d23e125b73d283c98f007e06d4595732104b65402f46e8ef24b588d9f8", size = 3133278 }, + { url = "https://files.pythonhosted.org/packages/a4/4d/8fbc203838b3d26269f944a89459d94c858f5b3f9a9b6ee9728cdcf69161/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a21a15d5c8e603331b8a59548bbe113564136dc0f5ad8306dd5033459a226da0", size = 3144253 }, + { url = "https://files.pythonhosted.org/packages/d8/1b/2bd062adeb7c7511b847b32e356024980c0ffcf35f28947792c2d8ad2288/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2fdbd4c067c60a0ac7eca14b6bd18a5bebace54eb757c706b47ea93204f7a37c", size = 3398225 }, + { url = "https://files.pythonhosted.org/packages/8a/63/38be071b0c8e06840bc6046991636bcb30c27f6bb1e670f4f4bc87cf49cc/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dd9a0061e403546f7377df940e866c3e678d7d4e9643d0461ea442b4f89e61a", size = 3038874 }, + { url = "https://files.pythonhosted.org/packages/ec/83/afa94193c09246417c23a3c75a8a0a96bf44ab5630a3015538d0c316dd4b/tokenizers-0.21.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:db9484aeb2e200c43b915a1a0150ea885e35f357a5a8fabf7373af333dcc8dbf", size = 9014448 }, + { url = "https://files.pythonhosted.org/packages/ae/b3/0e1a37d4f84c0f014d43701c11eb8072704f6efe8d8fc2dcdb79c47d76de/tokenizers-0.21.1-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:ed248ab5279e601a30a4d67bdb897ecbe955a50f1e7bb62bd99f07dd11c2f5b6", size = 8937877 }, + { url = "https://files.pythonhosted.org/packages/ac/33/ff08f50e6d615eb180a4a328c65907feb6ded0b8f990ec923969759dc379/tokenizers-0.21.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:9ac78b12e541d4ce67b4dfd970e44c060a2147b9b2a21f509566d556a509c67d", size = 9186645 }, + { url = "https://files.pythonhosted.org/packages/5f/aa/8ae85f69a9f6012c6f8011c6f4aa1c96154c816e9eea2e1b758601157833/tokenizers-0.21.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e5a69c1a4496b81a5ee5d2c1f3f7fbdf95e90a0196101b0ee89ed9956b8a168f", size = 9384380 }, + { url = "https://files.pythonhosted.org/packages/e8/5b/a5d98c89f747455e8b7a9504910c865d5e51da55e825a7ae641fb5ff0a58/tokenizers-0.21.1-cp39-abi3-win32.whl", hash = "sha256:1039a3a5734944e09de1d48761ade94e00d0fa760c0e0551151d4dd851ba63e3", size = 2239506 }, + { url = "https://files.pythonhosted.org/packages/e6/b6/072a8e053ae600dcc2ac0da81a23548e3b523301a442a6ca900e92ac35be/tokenizers-0.21.1-cp39-abi3-win_amd64.whl", hash = "sha256:0f0dcbcc9f6e13e675a66d7a5f2f225a736745ce484c1a4e07476a89ccdad382", size = 2435481 }, +] + +[[package]] +name = "torch" +version = "2.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { 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-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-nvtx-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "setuptools", marker = "python_full_version >= '3.12'" }, + { name = "sympy" }, + { name = "triton", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "typing-extensions" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/a9/97cbbc97002fff0de394a2da2cdfa859481fdca36996d7bd845d50aa9d8d/torch-2.6.0-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:7979834102cd5b7a43cc64e87f2f3b14bd0e1458f06e9f88ffa386d07c7446e1", size = 766715424 }, + { url = "https://files.pythonhosted.org/packages/6d/fa/134ce8f8a7ea07f09588c9cc2cea0d69249efab977707cf67669431dcf5c/torch-2.6.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:ccbd0320411fe1a3b3fec7b4d3185aa7d0c52adac94480ab024b5c8f74a0bf1d", size = 95759416 }, + { url = "https://files.pythonhosted.org/packages/11/c5/2370d96b31eb1841c3a0883a492c15278a6718ccad61bb6a649c80d1d9eb/torch-2.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:46763dcb051180ce1ed23d1891d9b1598e07d051ce4c9d14307029809c4d64f7", size = 204164970 }, + { url = "https://files.pythonhosted.org/packages/0b/fa/f33a4148c6fb46ca2a3f8de39c24d473822d5774d652b66ed9b1214da5f7/torch-2.6.0-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:94fc63b3b4bedd327af588696559f68c264440e2503cc9e6954019473d74ae21", size = 66530713 }, + { url = "https://files.pythonhosted.org/packages/e5/35/0c52d708144c2deb595cd22819a609f78fdd699b95ff6f0ebcd456e3c7c1/torch-2.6.0-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:2bb8987f3bb1ef2675897034402373ddfc8f5ef0e156e2d8cfc47cacafdda4a9", size = 766624563 }, + { url = "https://files.pythonhosted.org/packages/01/d6/455ab3fbb2c61c71c8842753b566012e1ed111e7a4c82e0e1c20d0c76b62/torch-2.6.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:b789069020c5588c70d5c2158ac0aa23fd24a028f34a8b4fcb8fcb4d7efcf5fb", size = 95607867 }, + { url = "https://files.pythonhosted.org/packages/18/cf/ae99bd066571656185be0d88ee70abc58467b76f2f7c8bfeb48735a71fe6/torch-2.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:7e1448426d0ba3620408218b50aa6ada88aeae34f7a239ba5431f6c8774b1239", size = 204120469 }, + { url = "https://files.pythonhosted.org/packages/81/b4/605ae4173aa37fb5aa14605d100ff31f4f5d49f617928c9f486bb3aaec08/torch-2.6.0-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:9a610afe216a85a8b9bc9f8365ed561535c93e804c2a317ef7fabcc5deda0989", size = 66532538 }, + { url = "https://files.pythonhosted.org/packages/24/85/ead1349fc30fe5a32cadd947c91bda4a62fbfd7f8c34ee61f6398d38fb48/torch-2.6.0-cp313-cp313-manylinux1_x86_64.whl", hash = "sha256:4874a73507a300a5d089ceaff616a569e7bb7c613c56f37f63ec3ffac65259cf", size = 766626191 }, + { url = "https://files.pythonhosted.org/packages/dd/b0/26f06f9428b250d856f6d512413e9e800b78625f63801cbba13957432036/torch-2.6.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:a0d5e1b9874c1a6c25556840ab8920569a7a4137afa8a63a32cee0bc7d89bd4b", size = 95611439 }, + { url = "https://files.pythonhosted.org/packages/c2/9c/fc5224e9770c83faed3a087112d73147cd7c7bfb7557dcf9ad87e1dda163/torch-2.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:510c73251bee9ba02ae1cb6c9d4ee0907b3ce6020e62784e2d7598e0cfa4d6cc", size = 204126475 }, + { url = "https://files.pythonhosted.org/packages/88/8b/d60c0491ab63634763be1537ad488694d316ddc4a20eaadd639cedc53971/torch-2.6.0-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:ff96f4038f8af9f7ec4231710ed4549da1bdebad95923953a25045dcf6fd87e2", size = 66536783 }, +] + +[[package]] +name = "torchaudio" +version = "2.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "torch" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/30/bba293c8300245a09b7f82d3cfc04aee1950228da49c6cdd637d1145b6f5/torchaudio-2.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c12fc41241b8dfce3ccc1917f1c81a0f92f532d9917706600046f1eb21d2d765", size = 1815253 }, + { url = "https://files.pythonhosted.org/packages/3e/00/2c69d436c613043f3051210d2f84a4c9062a815fa609c5f54d25ea8bfd07/torchaudio-2.6.0-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:377b177a3d683a9163e4cab5a06f0346dac9ff96fa527477338fd90fc6a2a4b6", size = 3382518 }, + { url = "https://files.pythonhosted.org/packages/f5/b8/7d4dbbf6b505caddbfccd38e2882e47a791310b32b347f977a0a66efbf80/torchaudio-2.6.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:0f0db5c997d031c34066d8be1c0ce7d2a1f2b6c016a92885b20b00bfeb17b753", size = 1652980 }, + { url = "https://files.pythonhosted.org/packages/1f/31/417d6955585be76842e9b0159d3801c0b5f9a4ea0db39db1a72bc262c861/torchaudio-2.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:52182f6de4e7b342d139e54b703185d428de9cce3c4cf914a9b2ab2359d192a3", size = 2454430 }, + { url = "https://files.pythonhosted.org/packages/ac/4a/d71b932bda4171970bdf4997541b5c778daa0e2967ed5009d207fca86ded/torchaudio-2.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d0e4b08c42325bf4b887de9a25c44ed882997001740e1bd7d901f65581cf1ab", size = 1812899 }, + { url = "https://files.pythonhosted.org/packages/ed/aa/9082e715a673dd8e22b6a60cec7f301e897406023672b2090f8bcd8a5959/torchaudio-2.6.0-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:715aa21f6bdbd085454c313ae3a2c7cc07bf2e8cf05752f819afb5b4c57f4e6f", size = 3379510 }, + { url = "https://files.pythonhosted.org/packages/f2/e7/0bcb2e33f4bdec69477344eccfe25c515b90496888095e99f837ea422089/torchaudio-2.6.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:6291d9507dc1d6b4ffe8843fbfb201e6c8270dd8c42ad70bb76226c0ebdcad56", size = 1653523 }, + { url = "https://files.pythonhosted.org/packages/80/95/29e917905328337c7b104ce81f3bb5e2ad8dc70af2edf1d43f67eb621513/torchaudio-2.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:86d6239792bf94741a41acd6fe3d549faaf0d50e7275d17d076a190bd007e2f9", size = 2449191 }, + { url = "https://files.pythonhosted.org/packages/fb/73/861afa5864e95fbf42b693e0359b2bf0177b6b5f4274fa4472fd51e5298e/torchaudio-2.6.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:66f2e0bd5ab56fd81419d2f5afb74a9a70141688594646441756c8c24f424a73", size = 1813188 }, + { url = "https://files.pythonhosted.org/packages/d2/f0/daffd9afa60bd835a2d7980eddfe44524adcb3ee0837486ceae4cd1f68e2/torchaudio-2.6.0-cp313-cp313-manylinux1_x86_64.whl", hash = "sha256:52f15185349c370fc1faa84e8b8b2782c007472db9d586a16bba314130b322f2", size = 3380706 }, + { url = "https://files.pythonhosted.org/packages/94/7b/887b91372e34119aa140cf67614e5ba901bf6a0db86f2c39e30ff71eec54/torchaudio-2.6.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:b521ea9618fb4c29a6f8071628170c222291f46a48a3bf424cfeb488f54af714", size = 1653553 }, + { url = "https://files.pythonhosted.org/packages/55/c8/3010878a5e7f15d89450e22769697173c6dc244a0647ddc5386c28b6dacc/torchaudio-2.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:393fa74ec40d167f0170728ea21c9b5e0f830648fd02df7db2bf7e62f64245ec", size = 2449350 }, +] + +[[package]] +name = "torchmetrics" +version = "1.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "lightning-utilities" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "torch" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/c4/2d921ccf7433ee6d8d6edeaca238674cbbc74528120631c662014795c645/torchmetrics-1.7.1.tar.gz", hash = "sha256:0ac1a0e90d2375866ceb5d3868720c6df7d7d0c5729b7ad36e92c897c6af70c2", size = 565045 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/ee/4d0a7213a6f412afb3483031009a3b970dd7bed3be24de95ab04fba1c05a/torchmetrics-1.7.1-py3-none-any.whl", hash = "sha256:9a4c45edbd0a1844cc1540c9e71bfbe0ee783a2ed997344d947fefecac90dbb9", size = 961489 }, +] + +[[package]] +name = "torchvision" +version = "0.21.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "pillow" }, + { name = "torch" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/3d/b7241abfa3e6651c6e00796f5de2bd1ce4d500bf5159bcbfeea47e711b93/torchvision-0.21.0-1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:ff96666b94a55e802ea6796cabe788541719e6f4905fc59c380fed3517b6a64d", size = 2329320 }, + { url = "https://files.pythonhosted.org/packages/52/5b/76ca113a853b19c7b1da761f8a72cb6429b3bd0bf932537d8df4657f47c3/torchvision-0.21.0-1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:ffa2a16499508fe6798323e455f312c7c55f2a88901c9a7c0fb1efa86cf7e327", size = 2329878 }, + { url = "https://files.pythonhosted.org/packages/4e/fe/5e193353706dab96fe73ae100d5a633ff635ce310e0d92f3bc2958d075b1/torchvision-0.21.0-1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:7e9e9afa150e40cd2a8f0701c43cb82a8d724f512896455c0918b987f94b84a4", size = 2280711 }, + { url = "https://files.pythonhosted.org/packages/29/88/00c69db213ee2443ada8886ec60789b227e06bb869d85ee324578221a7f7/torchvision-0.21.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:110d115333524d60e9e474d53c7d20f096dbd8a080232f88dddb90566f90064c", size = 1784141 }, + { url = "https://files.pythonhosted.org/packages/be/a2/b0cedf0a411f1a5d75cfc0b87cde56dd1ddc1878be46a42c905cd8580220/torchvision-0.21.0-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:3891cd086c5071bda6b4ee9d266bb2ac39c998c045c2ebcd1e818b8316fb5d41", size = 7237719 }, + { url = "https://files.pythonhosted.org/packages/8c/a1/ee962ef9d0b2bf7a6f8b14cb95acb70e05cd2101af521032a09e43f8582f/torchvision-0.21.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:54454923a50104c66a9ab6bd8b73a11c2fc218c964b1006d5d1fe5b442c3dcb6", size = 14700617 }, + { url = "https://files.pythonhosted.org/packages/88/53/4ad334b9b1d8dd99836869fec139cb74a27781298360b91b9506c53f1d10/torchvision-0.21.0-cp311-cp311-win_amd64.whl", hash = "sha256:49bcfad8cfe2c27dee116c45d4f866d7974bcf14a5a9fbef893635deae322f2f", size = 1560523 }, + { url = "https://files.pythonhosted.org/packages/6e/1b/28f527b22d5e8800184d0bc847f801ae92c7573a8c15979d92b7091c0751/torchvision-0.21.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:97a5814a93c793aaf0179cfc7f916024f4b63218929aee977b645633d074a49f", size = 1784140 }, + { url = "https://files.pythonhosted.org/packages/36/63/0722e153fd27d64d5b0af45b5c8cb0e80b35a68cf0130303bc9a8bb095c7/torchvision-0.21.0-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:b578bcad8a4083b40d34f689b19ca9f7c63e511758d806510ea03c29ac568f7b", size = 7238673 }, + { url = "https://files.pythonhosted.org/packages/bb/ea/03541ed901cdc30b934f897060d09bbf7a98466a08ad1680320f9ce0cbe0/torchvision-0.21.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:5083a5b1fec2351bf5ea9900a741d54086db75baec4b1d21e39451e00977f1b1", size = 14701186 }, + { url = "https://files.pythonhosted.org/packages/4c/6a/c7752603060d076dfed95135b78b047dc71792630cbcb022e3693d6f32ef/torchvision-0.21.0-cp312-cp312-win_amd64.whl", hash = "sha256:6eb75d41e3bbfc2f7642d0abba9383cc9ae6c5a4ca8d6b00628c225e1eaa63b3", size = 1560520 }, + { url = "https://files.pythonhosted.org/packages/f9/56/47d456b61c3bbce7bed4af3925c83d405bb87468e659fd3cf3d9840c3b51/torchvision-0.21.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:659b76c86757cb2ee4ca2db245e0740cfc3081fef46f0f1064d11adb4a8cee31", size = 1784141 }, + { url = "https://files.pythonhosted.org/packages/cb/4c/99880813aa50e64447fb1c4c6c804a793d2d78f7f7c53e99ddee7fa175fa/torchvision-0.21.0-cp313-cp313-manylinux1_x86_64.whl", hash = "sha256:084ac3f5a1f50c70d630a488d19bf62f323018eae1b1c1232f2b7047d3a7b76d", size = 7238714 }, + { url = "https://files.pythonhosted.org/packages/0b/2d/3c3ee10608310a395594aac7da8640372ed79c6585910ccae6919658dcdc/torchvision-0.21.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:5045a3a5f21ec3eea6962fa5f2fa2d4283f854caec25ada493fcf4aab2925467", size = 2281252 }, + { url = "https://files.pythonhosted.org/packages/ed/b4/fc60e3bc003879d3de842baea258fffc3586f4b49cd435a5ba1e09c33315/torchvision-0.21.0-cp313-cp313-win_amd64.whl", hash = "sha256:9147f5e096a9270684e3befdee350f3cacafd48e0c54ab195f45790a9c146d67", size = 1560519 }, +] + +[[package]] +name = "tornado" +version = "6.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/59/45/a0daf161f7d6f36c3ea5fc0c2de619746cc3dd4c76402e9db545bd920f63/tornado-6.4.2.tar.gz", hash = "sha256:92bad5b4746e9879fd7bf1eb21dce4e3fc5128d71601f80005afa39237ad620b", size = 501135 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/7e/71f604d8cea1b58f82ba3590290b66da1e72d840aeb37e0d5f7291bd30db/tornado-6.4.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e828cce1123e9e44ae2a50a9de3055497ab1d0aeb440c5ac23064d9e44880da1", size = 436299 }, + { url = "https://files.pythonhosted.org/packages/96/44/87543a3b99016d0bf54fdaab30d24bf0af2e848f1d13d34a3a5380aabe16/tornado-6.4.2-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:072ce12ada169c5b00b7d92a99ba089447ccc993ea2143c9ede887e0937aa803", size = 434253 }, + { url = "https://files.pythonhosted.org/packages/cb/fb/fdf679b4ce51bcb7210801ef4f11fdac96e9885daa402861751353beea6e/tornado-6.4.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a017d239bd1bb0919f72af256a970624241f070496635784d9bf0db640d3fec", size = 437602 }, + { url = "https://files.pythonhosted.org/packages/4f/3b/e31aeffffc22b475a64dbeb273026a21b5b566f74dee48742817626c47dc/tornado-6.4.2-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c36e62ce8f63409301537222faffcef7dfc5284f27eec227389f2ad11b09d946", size = 436972 }, + { url = "https://files.pythonhosted.org/packages/22/55/b78a464de78051a30599ceb6983b01d8f732e6f69bf37b4ed07f642ac0fc/tornado-6.4.2-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca9eb02196e789c9cb5c3c7c0f04fb447dc2adffd95265b2c7223a8a615ccbf", size = 437173 }, + { url = "https://files.pythonhosted.org/packages/79/5e/be4fb0d1684eb822c9a62fb18a3e44a06188f78aa466b2ad991d2ee31104/tornado-6.4.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:304463bd0772442ff4d0f5149c6f1c2135a1fae045adf070821c6cdc76980634", size = 437892 }, + { url = "https://files.pythonhosted.org/packages/f5/33/4f91fdd94ea36e1d796147003b490fe60a0215ac5737b6f9c65e160d4fe0/tornado-6.4.2-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:c82c46813ba483a385ab2a99caeaedf92585a1f90defb5693351fa7e4ea0bf73", size = 437334 }, + { url = "https://files.pythonhosted.org/packages/2b/ae/c1b22d4524b0e10da2f29a176fb2890386f7bd1f63aacf186444873a88a0/tornado-6.4.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:932d195ca9015956fa502c6b56af9eb06106140d844a335590c1ec7f5277d10c", size = 437261 }, + { url = "https://files.pythonhosted.org/packages/b5/25/36dbd49ab6d179bcfc4c6c093a51795a4f3bed380543a8242ac3517a1751/tornado-6.4.2-cp38-abi3-win32.whl", hash = "sha256:2876cef82e6c5978fde1e0d5b1f919d756968d5b4282418f3146b79b58556482", size = 438463 }, + { url = "https://files.pythonhosted.org/packages/61/cc/58b1adeb1bb46228442081e746fcdbc4540905c87e8add7c277540934edb/tornado-6.4.2-cp38-abi3-win_amd64.whl", hash = "sha256:908b71bf3ff37d81073356a5fadcc660eb10c1476ee6e2725588626ce7e5ca38", size = 438907 }, +] + +[[package]] +name = "tqdm" +version = "4.67.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540 }, +] + +[[package]] +name = "traitlets" +version = "5.14.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359 }, +] + +[[package]] +name = "transformers" +version = "4.52.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "huggingface-hub" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "regex" }, + { name = "requests" }, + { name = "safetensors" }, + { name = "tokenizers" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/07/42/271bcf364788337ac24e7f200005ac7142aaf022206bd6119d2daca22c04/transformers-4.52.3.tar.gz", hash = "sha256:2e1de29374f27920aaf6d589d4e6339f33def2fb08809e1a1d792e040e9fbce7", size = 8951324 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/f8/1f086942bc6a044e4e68dacf6de761a45367795efd5f57ad356765691c79/transformers-4.52.3-py3-none-any.whl", hash = "sha256:cd04059da50e7cf2a617ce3143ba8beffbf119f8c25a0717c3454fd9d0f19609", size = 10460322 }, +] + +[[package]] +name = "triton" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/2e/757d2280d4fefe7d33af7615124e7e298ae7b8e3bc4446cdb8e88b0f9bab/triton-3.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8009a1fb093ee8546495e96731336a33fb8856a38e45bb4ab6affd6dbc3ba220", size = 253157636 }, + { url = "https://files.pythonhosted.org/packages/06/00/59500052cb1cf8cf5316be93598946bc451f14072c6ff256904428eaf03c/triton-3.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d9b215efc1c26fa7eefb9a157915c92d52e000d2bf83e5f69704047e63f125c", size = 253159365 }, + { url = "https://files.pythonhosted.org/packages/c7/30/37a3384d1e2e9320331baca41e835e90a3767303642c7a80d4510152cbcf/triton-3.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5dfa23ba84541d7c0a531dfce76d8bcd19159d50a4a8b14ad01e91734a5c1b0", size = 253154278 }, +] + +[[package]] +name = "types-python-dateutil" +version = "2.9.0.20241206" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/60/47d92293d9bc521cd2301e423a358abfac0ad409b3a1606d8fbae1321961/types_python_dateutil-2.9.0.20241206.tar.gz", hash = "sha256:18f493414c26ffba692a72369fea7a154c502646301ebfe3d56a04b3767284cb", size = 13802 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/b3/ca41df24db5eb99b00d97f89d7674a90cb6b3134c52fb8121b6d8d30f15c/types_python_dateutil-2.9.0.20241206-py3-none-any.whl", hash = "sha256:e248a4bc70a486d3e3ec84d0dc30eec3a5f979d6e7ee4123ae043eedbb987f53", size = 14384 }, +] + +[[package]] +name = "typing-extensions" +version = "4.13.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/76/ad/cd3e3465232ec2416ae9b983f27b9e94dc8171d56ac99b345319a9475967/typing_extensions-4.13.1.tar.gz", hash = "sha256:98795af00fb9640edec5b8e31fc647597b4691f099ad75f469a2616be1a76dff", size = 106633 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/c5/e7a0b0f5ed69f94c8ab7379c599e6036886bffcde609969a5325f47f1332/typing_extensions-4.13.1-py3-none-any.whl", hash = "sha256:4b6cf02909eb5495cfbc3f6e8fd49217e6cc7944e145cdda8caa3734777f9e69", size = 45739 }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552 }, +] + +[[package]] +name = "tzdata" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839 }, +] + +[[package]] +name = "uri-template" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/31/c7/0336f2bd0bcbada6ccef7aaa25e443c118a704f828a0620c6fa0207c1b64/uri-template-1.3.0.tar.gz", hash = "sha256:0e00f8eb65e18c7de20d595a14336e9f337ead580c70934141624b6d1ffdacc7", size = 21678 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl", hash = "sha256:a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363", size = 11140 }, +] + +[[package]] +name = "urllib3" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 }, +] + +[[package]] +name = "virtualenv" +version = "20.31.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/56/2c/444f465fb2c65f40c3a104fd0c495184c4f2336d65baf398e3c75d72ea94/virtualenv-20.31.2.tar.gz", hash = "sha256:e10c0a9d02835e592521be48b332b6caee6887f332c111aa79a09b9e79efc2af", size = 6076316 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/40/b1c265d4b2b62b58576588510fc4d1fe60a86319c8de99fd8e9fec617d2c/virtualenv-20.31.2-py3-none-any.whl", hash = "sha256:36efd0d9650ee985f0cad72065001e66d49a6f24eb44d98980f630686243cf11", size = 6057982 }, +] + +[[package]] +name = "wandb" +version = "0.20.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "gitpython" }, + { name = "packaging" }, + { name = "platformdirs" }, + { name = "protobuf" }, + { name = "psutil" }, + { name = "pydantic" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "sentry-sdk" }, + { name = "setproctitle" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/1f/92be0ca87fb49eb48c16dcf0845a3579a57c4734fec2b95862cf5a0494a0/wandb-0.20.1.tar.gz", hash = "sha256:dbd3fc60dfe7bf83c4de24b206b99b44949fef323f817a783883db72fc5f3bfe", size = 40320062 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/18/afcc37d0b93dd6f6d0f0c5683b9cfff9416ae1539931f58932a2938c0070/wandb-0.20.1-py3-none-any.whl", hash = "sha256:e6395cabf074247042be1cf0dc6ab0b06aa4c9538c2e1fdc5b507a690ce0cf17", size = 6458872 }, + { url = "https://files.pythonhosted.org/packages/e6/b5/70f9e2a3d1380b729ae5853763d938edc50072df357f79bbd19b9aae8e3f/wandb-0.20.1-py3-none-macosx_10_14_x86_64.whl", hash = "sha256:2475a48c693adf677d40da9e1c8ceeaf86d745ffc3b7e3535731279d02f9e845", size = 22517483 }, + { url = "https://files.pythonhosted.org/packages/cc/7e/4eb9aeb2fd974d410a8f2eb11b0219536503913a050d46a03206151705c8/wandb-0.20.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:99cce804c31ec1e0d1e691650a7d51773ed7329c41745d56384fa3655a0e9b2c", size = 22034511 }, + { url = "https://files.pythonhosted.org/packages/34/38/1df22c2273e6f7ab0aae4fd032085d6d92ab112f5b261646e7dc5e675cfe/wandb-0.20.1-py3-none-macosx_11_0_x86_64.whl", hash = "sha256:ce3ee412677a1679e04b21e03a91e1e02eb90faf658d682bee86c33cf5f32e09", size = 22720771 }, + { url = "https://files.pythonhosted.org/packages/38/96/78fc7a7ea7158d136c84f481423f8736c9346a2387287ec8a6d92019975c/wandb-0.20.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e58ca32c7147161158f09b0fb5f5896876f8569d0d10ae7b64d0510c868ce33", size = 21537453 }, + { url = "https://files.pythonhosted.org/packages/88/c9/41b8bdb493e5eda32b502bc1cc49d539335a92cacaf0ef304d7dae0240aa/wandb-0.20.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:591506ecbdd396648cc323ba270f3ab4aed3158e1dbfa7636c09f9f7f0253e1c", size = 23161349 }, + { url = "https://files.pythonhosted.org/packages/7d/f2/79e783cc50a47d373dfbda862eb5396de8139167e8c6443a16ef0166106f/wandb-0.20.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:382508532db09893f81cc926b1d333caa4c8a7db057878899fadf929bbdb3b56", size = 21550624 }, + { url = "https://files.pythonhosted.org/packages/26/32/23890a726302e7be28bda9fff47ce9b491af64e339aba4d32b3b8d1a7aaf/wandb-0.20.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:29ea495e49393db860f17437fe37e48018da90436ce10949b471780f09293bd7", size = 23237996 }, + { url = "https://files.pythonhosted.org/packages/af/94/296e520b086b2a4f10e99bcea3cd5856421b9c004824663501e3789a713b/wandb-0.20.1-py3-none-win32.whl", hash = "sha256:455ee0a652e59ab1e4b546fa1dc833dd3063aa7e64eb8abf95d22f0e9f08c574", size = 22518456 }, + { url = "https://files.pythonhosted.org/packages/52/5f/c44ad7b2a062ca5f4da99ae475cea274c38f6ec37bdaca1b1c653ee87274/wandb-0.20.1-py3-none-win_amd64.whl", hash = "sha256:6d2431652f096b7e394c29a99135a6441c02ed3198b963f0b351a5b5e56aeca0", size = 22518459 }, +] + +[[package]] +name = "wcwidth" +version = "0.2.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166 }, +] + +[[package]] +name = "webcolors" +version = "24.11.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/29/061ec845fb58521848f3739e466efd8250b4b7b98c1b6c5bf4d40b419b7e/webcolors-24.11.1.tar.gz", hash = "sha256:ecb3d768f32202af770477b8b65f318fa4f566c22948673a977b00d589dd80f6", size = 45064 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/e8/c0e05e4684d13459f93d312077a9a2efbe04d59c393bc2b8802248c908d4/webcolors-24.11.1-py3-none-any.whl", hash = "sha256:515291393b4cdf0eb19c155749a096f779f7d909f7cceea072791cb9095b92e9", size = 14934 }, +] + +[[package]] +name = "webencodings" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774 }, +] + +[[package]] +name = "websocket-client" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e6/30/fba0d96b4b5fbf5948ed3f4681f7da2f9f64512e1d303f94b4cc174c24a5/websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da", size = 54648 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/84/44687a29792a70e111c5c477230a72c4b957d88d16141199bf9acb7537a3/websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526", size = 58826 }, +] + +[[package]] +name = "widgetsnbextension" +version = "4.0.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/56/fc/238c424fd7f4ebb25f8b1da9a934a3ad7c848286732ae04263661eb0fc03/widgetsnbextension-4.0.13.tar.gz", hash = "sha256:ffcb67bc9febd10234a362795f643927f4e0c05d9342c727b65d2384f8feacb6", size = 1164730 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/21/02/88b65cc394961a60c43c70517066b6b679738caf78506a5da7b88ffcb643/widgetsnbextension-4.0.13-py3-none-any.whl", hash = "sha256:74b2692e8500525cc38c2b877236ba51d34541e6385eeed5aec15a70f88a6c71", size = 2335872 }, +] + +[[package]] +name = "xxhash" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/5e/d6e5258d69df8b4ed8c83b6664f2b47d30d2dec551a29ad72a6c69eafd31/xxhash-3.5.0.tar.gz", hash = "sha256:84f2caddf951c9cbf8dc2e22a89d4ccf5d86391ac6418fe81e3c67d0cf60b45f", size = 84241 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/c7/afed0f131fbda960ff15eee7f304fa0eeb2d58770fade99897984852ef23/xxhash-3.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02c2e816896dc6f85922ced60097bcf6f008dedfc5073dcba32f9c8dd786f3c1", size = 31969 }, + { url = "https://files.pythonhosted.org/packages/8c/0c/7c3bc6d87e5235672fcc2fb42fd5ad79fe1033925f71bf549ee068c7d1ca/xxhash-3.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6027dcd885e21581e46d3c7f682cfb2b870942feeed58a21c29583512c3f09f8", size = 30800 }, + { url = "https://files.pythonhosted.org/packages/04/9e/01067981d98069eec1c20201f8c145367698e9056f8bc295346e4ea32dd1/xxhash-3.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1308fa542bbdbf2fa85e9e66b1077eea3a88bef38ee8a06270b4298a7a62a166", size = 221566 }, + { url = "https://files.pythonhosted.org/packages/d4/09/d4996de4059c3ce5342b6e1e6a77c9d6c91acce31f6ed979891872dd162b/xxhash-3.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c28b2fdcee797e1c1961cd3bcd3d545cab22ad202c846235197935e1df2f8ef7", size = 201214 }, + { url = "https://files.pythonhosted.org/packages/62/f5/6d2dc9f8d55a7ce0f5e7bfef916e67536f01b85d32a9fbf137d4cadbee38/xxhash-3.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:924361811732ddad75ff23e90efd9ccfda4f664132feecb90895bade6a1b4623", size = 429433 }, + { url = "https://files.pythonhosted.org/packages/d9/72/9256303f10e41ab004799a4aa74b80b3c5977d6383ae4550548b24bd1971/xxhash-3.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89997aa1c4b6a5b1e5b588979d1da048a3c6f15e55c11d117a56b75c84531f5a", size = 194822 }, + { url = "https://files.pythonhosted.org/packages/34/92/1a3a29acd08248a34b0e6a94f4e0ed9b8379a4ff471f1668e4dce7bdbaa8/xxhash-3.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:685c4f4e8c59837de103344eb1c8a3851f670309eb5c361f746805c5471b8c88", size = 208538 }, + { url = "https://files.pythonhosted.org/packages/53/ad/7fa1a109663366de42f724a1cdb8e796a260dbac45047bce153bc1e18abf/xxhash-3.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dbd2ecfbfee70bc1a4acb7461fa6af7748ec2ab08ac0fa298f281c51518f982c", size = 216953 }, + { url = "https://files.pythonhosted.org/packages/35/02/137300e24203bf2b2a49b48ce898ecce6fd01789c0fcd9c686c0a002d129/xxhash-3.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:25b5a51dc3dfb20a10833c8eee25903fd2e14059e9afcd329c9da20609a307b2", size = 203594 }, + { url = "https://files.pythonhosted.org/packages/23/03/aeceb273933d7eee248c4322b98b8e971f06cc3880e5f7602c94e5578af5/xxhash-3.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a8fb786fb754ef6ff8c120cb96629fb518f8eb5a61a16aac3a979a9dbd40a084", size = 210971 }, + { url = "https://files.pythonhosted.org/packages/e3/64/ed82ec09489474cbb35c716b189ddc1521d8b3de12b1b5ab41ce7f70253c/xxhash-3.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a905ad00ad1e1c34fe4e9d7c1d949ab09c6fa90c919860c1534ff479f40fd12d", size = 415050 }, + { url = "https://files.pythonhosted.org/packages/71/43/6db4c02dcb488ad4e03bc86d70506c3d40a384ee73c9b5c93338eb1f3c23/xxhash-3.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:963be41bcd49f53af6d795f65c0da9b4cc518c0dd9c47145c98f61cb464f4839", size = 192216 }, + { url = "https://files.pythonhosted.org/packages/22/6d/db4abec29e7a567455344433d095fdb39c97db6955bb4a2c432e486b4d28/xxhash-3.5.0-cp311-cp311-win32.whl", hash = "sha256:109b436096d0a2dd039c355fa3414160ec4d843dfecc64a14077332a00aeb7da", size = 30120 }, + { url = "https://files.pythonhosted.org/packages/52/1c/fa3b61c0cf03e1da4767213672efe186b1dfa4fc901a4a694fb184a513d1/xxhash-3.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:b702f806693201ad6c0a05ddbbe4c8f359626d0b3305f766077d51388a6bac58", size = 30003 }, + { url = "https://files.pythonhosted.org/packages/6b/8e/9e6fc572acf6e1cc7ccb01973c213f895cb8668a9d4c2b58a99350da14b7/xxhash-3.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:c4dcb4120d0cc3cc448624147dba64e9021b278c63e34a38789b688fd0da9bf3", size = 26777 }, + { url = "https://files.pythonhosted.org/packages/07/0e/1bfce2502c57d7e2e787600b31c83535af83746885aa1a5f153d8c8059d6/xxhash-3.5.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:14470ace8bd3b5d51318782cd94e6f94431974f16cb3b8dc15d52f3b69df8e00", size = 31969 }, + { url = "https://files.pythonhosted.org/packages/3f/d6/8ca450d6fe5b71ce521b4e5db69622383d039e2b253e9b2f24f93265b52c/xxhash-3.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:59aa1203de1cb96dbeab595ded0ad0c0056bb2245ae11fac11c0ceea861382b9", size = 30787 }, + { url = "https://files.pythonhosted.org/packages/5b/84/de7c89bc6ef63d750159086a6ada6416cc4349eab23f76ab870407178b93/xxhash-3.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08424f6648526076e28fae6ea2806c0a7d504b9ef05ae61d196d571e5c879c84", size = 220959 }, + { url = "https://files.pythonhosted.org/packages/fe/86/51258d3e8a8545ff26468c977101964c14d56a8a37f5835bc0082426c672/xxhash-3.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:61a1ff00674879725b194695e17f23d3248998b843eb5e933007ca743310f793", size = 200006 }, + { url = "https://files.pythonhosted.org/packages/02/0a/96973bd325412feccf23cf3680fd2246aebf4b789122f938d5557c54a6b2/xxhash-3.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2f2c61bee5844d41c3eb015ac652a0229e901074951ae48581d58bfb2ba01be", size = 428326 }, + { url = "https://files.pythonhosted.org/packages/11/a7/81dba5010f7e733de88af9555725146fc133be97ce36533867f4c7e75066/xxhash-3.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d32a592cac88d18cc09a89172e1c32d7f2a6e516c3dfde1b9adb90ab5df54a6", size = 194380 }, + { url = "https://files.pythonhosted.org/packages/fb/7d/f29006ab398a173f4501c0e4977ba288f1c621d878ec217b4ff516810c04/xxhash-3.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70dabf941dede727cca579e8c205e61121afc9b28516752fd65724be1355cc90", size = 207934 }, + { url = "https://files.pythonhosted.org/packages/8a/6e/6e88b8f24612510e73d4d70d9b0c7dff62a2e78451b9f0d042a5462c8d03/xxhash-3.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e5d0ddaca65ecca9c10dcf01730165fd858533d0be84c75c327487c37a906a27", size = 216301 }, + { url = "https://files.pythonhosted.org/packages/af/51/7862f4fa4b75a25c3b4163c8a873f070532fe5f2d3f9b3fc869c8337a398/xxhash-3.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e5b5e16c5a480fe5f59f56c30abdeba09ffd75da8d13f6b9b6fd224d0b4d0a2", size = 203351 }, + { url = "https://files.pythonhosted.org/packages/22/61/8d6a40f288f791cf79ed5bb113159abf0c81d6efb86e734334f698eb4c59/xxhash-3.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149b7914451eb154b3dfaa721315117ea1dac2cc55a01bfbd4df7c68c5dd683d", size = 210294 }, + { url = "https://files.pythonhosted.org/packages/17/02/215c4698955762d45a8158117190261b2dbefe9ae7e5b906768c09d8bc74/xxhash-3.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:eade977f5c96c677035ff39c56ac74d851b1cca7d607ab3d8f23c6b859379cab", size = 414674 }, + { url = "https://files.pythonhosted.org/packages/31/5c/b7a8db8a3237cff3d535261325d95de509f6a8ae439a5a7a4ffcff478189/xxhash-3.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fa9f547bd98f5553d03160967866a71056a60960be00356a15ecc44efb40ba8e", size = 192022 }, + { url = "https://files.pythonhosted.org/packages/78/e3/dd76659b2811b3fd06892a8beb850e1996b63e9235af5a86ea348f053e9e/xxhash-3.5.0-cp312-cp312-win32.whl", hash = "sha256:f7b58d1fd3551b8c80a971199543379be1cee3d0d409e1f6d8b01c1a2eebf1f8", size = 30170 }, + { url = "https://files.pythonhosted.org/packages/d9/6b/1c443fe6cfeb4ad1dcf231cdec96eb94fb43d6498b4469ed8b51f8b59a37/xxhash-3.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:fa0cafd3a2af231b4e113fba24a65d7922af91aeb23774a8b78228e6cd785e3e", size = 30040 }, + { url = "https://files.pythonhosted.org/packages/0f/eb/04405305f290173acc0350eba6d2f1a794b57925df0398861a20fbafa415/xxhash-3.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:586886c7e89cb9828bcd8a5686b12e161368e0064d040e225e72607b43858ba2", size = 26796 }, + { url = "https://files.pythonhosted.org/packages/c9/b8/e4b3ad92d249be5c83fa72916c9091b0965cb0faeff05d9a0a3870ae6bff/xxhash-3.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:37889a0d13b0b7d739cfc128b1c902f04e32de17b33d74b637ad42f1c55101f6", size = 31795 }, + { url = "https://files.pythonhosted.org/packages/fc/d8/b3627a0aebfbfa4c12a41e22af3742cf08c8ea84f5cc3367b5de2d039cce/xxhash-3.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:97a662338797c660178e682f3bc180277b9569a59abfb5925e8620fba00b9fc5", size = 30792 }, + { url = "https://files.pythonhosted.org/packages/c3/cc/762312960691da989c7cd0545cb120ba2a4148741c6ba458aa723c00a3f8/xxhash-3.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f85e0108d51092bdda90672476c7d909c04ada6923c14ff9d913c4f7dc8a3bc", size = 220950 }, + { url = "https://files.pythonhosted.org/packages/fe/e9/cc266f1042c3c13750e86a535496b58beb12bf8c50a915c336136f6168dc/xxhash-3.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2fd827b0ba763ac919440042302315c564fdb797294d86e8cdd4578e3bc7f3", size = 199980 }, + { url = "https://files.pythonhosted.org/packages/bf/85/a836cd0dc5cc20376de26b346858d0ac9656f8f730998ca4324921a010b9/xxhash-3.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:82085c2abec437abebf457c1d12fccb30cc8b3774a0814872511f0f0562c768c", size = 428324 }, + { url = "https://files.pythonhosted.org/packages/b4/0e/15c243775342ce840b9ba34aceace06a1148fa1630cd8ca269e3223987f5/xxhash-3.5.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07fda5de378626e502b42b311b049848c2ef38784d0d67b6f30bb5008642f8eb", size = 194370 }, + { url = "https://files.pythonhosted.org/packages/87/a1/b028bb02636dfdc190da01951d0703b3d904301ed0ef6094d948983bef0e/xxhash-3.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c279f0d2b34ef15f922b77966640ade58b4ccdfef1c4d94b20f2a364617a493f", size = 207911 }, + { url = "https://files.pythonhosted.org/packages/80/d5/73c73b03fc0ac73dacf069fdf6036c9abad82de0a47549e9912c955ab449/xxhash-3.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:89e66ceed67b213dec5a773e2f7a9e8c58f64daeb38c7859d8815d2c89f39ad7", size = 216352 }, + { url = "https://files.pythonhosted.org/packages/b6/2a/5043dba5ddbe35b4fe6ea0a111280ad9c3d4ba477dd0f2d1fe1129bda9d0/xxhash-3.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bcd51708a633410737111e998ceb3b45d3dbc98c0931f743d9bb0a209033a326", size = 203410 }, + { url = "https://files.pythonhosted.org/packages/a2/b2/9a8ded888b7b190aed75b484eb5c853ddd48aa2896e7b59bbfbce442f0a1/xxhash-3.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3ff2c0a34eae7df88c868be53a8dd56fbdf592109e21d4bfa092a27b0bf4a7bf", size = 210322 }, + { url = "https://files.pythonhosted.org/packages/98/62/440083fafbc917bf3e4b67c2ade621920dd905517e85631c10aac955c1d2/xxhash-3.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:4e28503dccc7d32e0b9817aa0cbfc1f45f563b2c995b7a66c4c8a0d232e840c7", size = 414725 }, + { url = "https://files.pythonhosted.org/packages/75/db/009206f7076ad60a517e016bb0058381d96a007ce3f79fa91d3010f49cc2/xxhash-3.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a6c50017518329ed65a9e4829154626f008916d36295b6a3ba336e2458824c8c", size = 192070 }, + { url = "https://files.pythonhosted.org/packages/1f/6d/c61e0668943a034abc3a569cdc5aeae37d686d9da7e39cf2ed621d533e36/xxhash-3.5.0-cp313-cp313-win32.whl", hash = "sha256:53a068fe70301ec30d868ece566ac90d873e3bb059cf83c32e76012c889b8637", size = 30172 }, + { url = "https://files.pythonhosted.org/packages/96/14/8416dce965f35e3d24722cdf79361ae154fa23e2ab730e5323aa98d7919e/xxhash-3.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:80babcc30e7a1a484eab952d76a4f4673ff601f54d5142c26826502740e70b43", size = 30041 }, + { url = "https://files.pythonhosted.org/packages/27/ee/518b72faa2073f5aa8e3262408d284892cb79cf2754ba0c3a5870645ef73/xxhash-3.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:4811336f1ce11cac89dcbd18f3a25c527c16311709a89313c3acaf771def2d4b", size = 26801 }, +] + +[[package]] +name = "yarl" +version = "1.19.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/4d/8a8f57caccce49573e567744926f88c6ab3ca0b47a257806d1cf88584c5f/yarl-1.19.0.tar.gz", hash = "sha256:01e02bb80ae0dbed44273c304095295106e1d9470460e773268a27d11e594892", size = 184396 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/df/5fa7cd75e46306e0f9baf38a7c8969ff6730ea503b86232e85cb740304cf/yarl-1.19.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:163ff326680de5f6d4966954cf9e3fe1bf980f5fee2255e46e89b8cf0f3418b5", size = 145126 }, + { url = "https://files.pythonhosted.org/packages/2a/be/c1b52129cd2166ab7337f08e701a61baa7c260c7b03b534098cc8297aecc/yarl-1.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a626c4d9cca298d1be8625cff4b17004a9066330ac82d132bbda64a4c17c18d3", size = 96691 }, + { url = "https://files.pythonhosted.org/packages/8d/39/ad62139b45515f9bf129c805aeaaedf86fd93ae57ffe911f4caeabef3e74/yarl-1.19.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:961c3e401ea7f13d02b8bb7cb0c709152a632a6e14cdc8119e9c6ee5596cd45d", size = 94505 }, + { url = "https://files.pythonhosted.org/packages/be/be/04e3202cdc9bb5f81761e327af7095cffb0d81e32421a6b87f926052d2ae/yarl-1.19.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a39d7b807ab58e633ed760f80195cbd145b58ba265436af35f9080f1810dfe64", size = 355485 }, + { url = "https://files.pythonhosted.org/packages/00/7d/1463203663ca1ae62af8fb9ebc9601dd07f04dbced7edb1df3141a2cb2fe/yarl-1.19.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c4228978fb59c6b10f60124ba8e311c26151e176df364e996f3f8ff8b93971b5", size = 344569 }, + { url = "https://files.pythonhosted.org/packages/b0/1b/5263203017348669e637bb73856fb9632110538e92d5e9f8214fcc764da9/yarl-1.19.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9ba536b17ecf3c74a94239ec1137a3ad3caea8c0e4deb8c8d2ffe847d870a8c5", size = 371426 }, + { url = "https://files.pythonhosted.org/packages/78/59/90ca5f16d56b7741e5383951acc2e065fce41920eb5d8fda3065b5e288dc/yarl-1.19.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a251e00e445d2e9df7b827c9843c0b87f58a3254aaa3f162fb610747491fe00f", size = 368102 }, + { url = "https://files.pythonhosted.org/packages/84/f2/5e33aa0251ffd2c2a9041bf887e163eeefdc1dca238fdabac444d9463c3f/yarl-1.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9b92431d8b4d4ca5ccbfdbac95b05a3a6cd70cd73aa62f32f9627acfde7549c", size = 358740 }, + { url = "https://files.pythonhosted.org/packages/22/9e/ba92d234c81cf94495fc01eaa0b6000175733f76bd63e60ff748bce22c81/yarl-1.19.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec2f56edaf476f70b5831bbd59700b53d9dd011b1f77cd4846b5ab5c5eafdb3f", size = 346965 }, + { url = "https://files.pythonhosted.org/packages/8d/0b/d4f53136ef12ddad540855a886d7503a6cc17cfabb9a03ce0c179f3b9e51/yarl-1.19.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:acf9b92c4245ac8b59bc7ec66a38d3dcb8d1f97fac934672529562bb824ecadb", size = 368547 }, + { url = "https://files.pythonhosted.org/packages/31/4b/35ec8622908a728f378a8511f0ab2d47878b2c0b8cbe035f2d907914a5fc/yarl-1.19.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:57711f1465c06fee8825b95c0b83e82991e6d9425f9a042c3c19070a70ac92bf", size = 357610 }, + { url = "https://files.pythonhosted.org/packages/c1/71/1f39f7c55b0684834d945a2bcfdfe59e6e02ca2483a3d33c2f77a0c3b177/yarl-1.19.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:528e86f5b1de0ad8dd758ddef4e0ed24f5d946d4a1cef80ffb2d4fca4e10f122", size = 365331 }, + { url = "https://files.pythonhosted.org/packages/2e/13/57675964de5c8ccf6427df93ac97f9bb7328f3f8f7ebc31a5f5a286ab1c0/yarl-1.19.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:3b77173663e075d9e5a57e09d711e9da2f3266be729ecca0b8ae78190990d260", size = 378624 }, + { url = "https://files.pythonhosted.org/packages/d4/c6/5868e40f8da041ed0c3b5fd8c08cece849d9f609e970e6043308767fbb60/yarl-1.19.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d8717924cf0a825b62b1a96fc7d28aab7f55a81bf5338b8ef41d7a76ab9223e9", size = 383981 }, + { url = "https://files.pythonhosted.org/packages/f4/3f/e40124c986d96741d3d341ffac35be42b6df82ef8c18b5984ca2e7d838dd/yarl-1.19.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0df9f0221a78d858793f40cbea3915c29f969c11366646a92ca47e080a14f881", size = 378868 }, + { url = "https://files.pythonhosted.org/packages/01/eb/caf2774c770288bd87a818b11f3a56ada6a855f1987d93421aae01a175bf/yarl-1.19.0-cp311-cp311-win32.whl", hash = "sha256:8b3ade62678ee2c7c10dcd6be19045135e9badad53108f7d2ed14896ee396045", size = 86446 }, + { url = "https://files.pythonhosted.org/packages/4a/97/d4fe6168c1bb789507ffeb58c2e8c675a7e71de732dc02e12bda904c1362/yarl-1.19.0-cp311-cp311-win_amd64.whl", hash = "sha256:0626ee31edb23ac36bdffe607231de2cca055ad3a5e2dc5da587ef8bc6a321bc", size = 93121 }, + { url = "https://files.pythonhosted.org/packages/b8/70/44ef8f69d61cb5123167a4dda87f6c739a833fbdb2ed52960b4e8409d65c/yarl-1.19.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7b687c334da3ff8eab848c9620c47a253d005e78335e9ce0d6868ed7e8fd170b", size = 146855 }, + { url = "https://files.pythonhosted.org/packages/c3/94/38c14d6c8217cc818647689f2dd647b976ced8fea08d0ac84e3c8168252b/yarl-1.19.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b0fe766febcf523a2930b819c87bb92407ae1368662c1bc267234e79b20ff894", size = 97523 }, + { url = "https://files.pythonhosted.org/packages/35/a5/43a613586a6255105c4655a911c307ef3420e49e540d6ae2c5829863fb25/yarl-1.19.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:742ceffd3c7beeb2b20d47cdb92c513eef83c9ef88c46829f88d5b06be6734ee", size = 95540 }, + { url = "https://files.pythonhosted.org/packages/d4/60/ed26049f4a8b06ebfa6d5f3cb6a51b152fd57081aa818b6497474f65a631/yarl-1.19.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2af682a1e97437382ee0791eacbf540318bd487a942e068e7e0a6c571fadbbd3", size = 344386 }, + { url = "https://files.pythonhosted.org/packages/49/a6/b84899cab411f49af5986cfb44b514040788d81c8084f5811e6a7c0f1ce6/yarl-1.19.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:63702f1a098d0eaaea755e9c9d63172be1acb9e2d4aeb28b187092bcc9ca2d17", size = 338889 }, + { url = "https://files.pythonhosted.org/packages/cc/ce/0704f7166a781b1f81bdd45c4f49eadbae0230ebd35b9ec7cd7769d3a6ff/yarl-1.19.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3560dcba3c71ae7382975dc1e912ee76e50b4cd7c34b454ed620d55464f11876", size = 353107 }, + { url = "https://files.pythonhosted.org/packages/75/e5/0ecd6f2a9cc4264c16d8dfb0d3d71ba8d03cb58f3bcd42b1df4358331189/yarl-1.19.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:68972df6a0cc47c8abaf77525a76ee5c5f6ea9bbdb79b9565b3234ded3c5e675", size = 353128 }, + { url = "https://files.pythonhosted.org/packages/ad/c7/cd0fd1de581f1c2e8f996e704c9fd979e00106f18eebd91b0173cf1a13c6/yarl-1.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5684e7ff93ea74e47542232bd132f608df4d449f8968fde6b05aaf9e08a140f9", size = 349107 }, + { url = "https://files.pythonhosted.org/packages/e6/34/ba3e5a20bd1d6a09034fc7985aaf1309976f2a7a5aefd093c9e56f6e1e0c/yarl-1.19.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8182ad422bfacdebd4759ce3adc6055c0c79d4740aea1104e05652a81cd868c6", size = 335144 }, + { url = "https://files.pythonhosted.org/packages/1e/98/d9b7beb932fade015906efe0980aa7d522b8f93cf5ebf1082e74faa314b7/yarl-1.19.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aee5b90a5a9b71ac57400a7bdd0feaa27c51e8f961decc8d412e720a004a1791", size = 360795 }, + { url = "https://files.pythonhosted.org/packages/9a/11/70b8770039cc54af5948970591517a1e1d093df3f04f328c655c9a0fefb7/yarl-1.19.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:8c0b2371858d5a814b08542d5d548adb03ff2d7ab32f23160e54e92250961a72", size = 360140 }, + { url = "https://files.pythonhosted.org/packages/d4/67/708e3e36fafc4d9d96b4eecc6c8b9f37c8ad50df8a16c7a1d5ba9df53050/yarl-1.19.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cd430c2b7df4ae92498da09e9b12cad5bdbb140d22d138f9e507de1aa3edfea3", size = 364431 }, + { url = "https://files.pythonhosted.org/packages/c3/8b/937fbbcc895553a7e16fcd86ae4e0724c6ac9468237ad8e7c29cc3b1c9d9/yarl-1.19.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a93208282c0ccdf73065fd76c6c129bd428dba5ff65d338ae7d2ab27169861a0", size = 373832 }, + { url = "https://files.pythonhosted.org/packages/f8/ca/288ddc2230c9b6647fe907504f1119adb41252ac533eb564d3fc73511215/yarl-1.19.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:b8179280cdeb4c36eb18d6534a328f9d40da60d2b96ac4a295c5f93e2799e9d9", size = 378122 }, + { url = "https://files.pythonhosted.org/packages/4f/5a/79e1ef31d14968fbfc0ecec70a6683b574890d9c7550c376dd6d40de7754/yarl-1.19.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eda3c2b42dc0c389b7cfda2c4df81c12eeb552019e0de28bde8f913fc3d1fcf3", size = 375178 }, + { url = "https://files.pythonhosted.org/packages/95/38/9b0e56bf14026c3f550ad6425679f6d1a2f4821d70767f39d6f4c56a0820/yarl-1.19.0-cp312-cp312-win32.whl", hash = "sha256:57f3fed859af367b9ca316ecc05ce79ce327d6466342734305aa5cc380e4d8be", size = 86172 }, + { url = "https://files.pythonhosted.org/packages/b3/96/5c2f3987c4bb4e5cdebea3caf99a45946b13a9516f849c02222203d99860/yarl-1.19.0-cp312-cp312-win_amd64.whl", hash = "sha256:5507c1f7dd3d41251b67eecba331c8b2157cfd324849879bebf74676ce76aff7", size = 92617 }, + { url = "https://files.pythonhosted.org/packages/cd/a7/222144efa2f4a47363a5fee27d8a1d24851283b5a7f628890805fe7f7a66/yarl-1.19.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:59281b9ed27bc410e0793833bcbe7fc149739d56ffa071d1e0fe70536a4f7b61", size = 144789 }, + { url = "https://files.pythonhosted.org/packages/72/4f/3ee8de3f94baa33c0716260b0048b1fd5306f104b3efc6e1713693e7063e/yarl-1.19.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d27a6482ad5e05e8bafd47bf42866f8a1c0c3345abcb48d4511b3c29ecc197dc", size = 96685 }, + { url = "https://files.pythonhosted.org/packages/3e/7c/fbeebf875c1ededd872d6fefabd8a8526ef8aba6e9e8bcdf230d895d487b/yarl-1.19.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7a8e19fd5a6fdf19a91f2409665c7a089ffe7b9b5394ab33c0eec04cbecdd01f", size = 94307 }, + { url = "https://files.pythonhosted.org/packages/f3/ff/b7a9c1d7df37e594b43b7a8030e228ccd4ce361eeff24a92b17fe210e57d/yarl-1.19.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cda34ab19099c3a1685ad48fe45172536610c312b993310b5f1ca3eb83453b36", size = 342811 }, + { url = "https://files.pythonhosted.org/packages/79/e2/9e092876b2156c1d386e4864e85eba541ccabf2b9dcc47da64624bad0cc9/yarl-1.19.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7908a25d33f94852b479910f9cae6cdb9e2a509894e8d5f416c8342c0253c397", size = 336928 }, + { url = "https://files.pythonhosted.org/packages/71/24/648d99c134f2e14fc01ba790ad36ab56815e00069e60a12a4af893448b83/yarl-1.19.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e66c14d162bac94973e767b24de5d7e6c5153f7305a64ff4fcba701210bcd638", size = 351021 }, + { url = "https://files.pythonhosted.org/packages/0c/ee/7278d475784d407d1990a5939722e66a0fef057046fb5f1721f0a6eb156c/yarl-1.19.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c03607bf932aa4cfae371e2dc9ca8b76faf031f106dac6a6ff1458418140c165", size = 354454 }, + { url = "https://files.pythonhosted.org/packages/15/ae/242546114e052a7de21a75bd7d4860266439f90bbc21c5e4dd696866d91d/yarl-1.19.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9931343d1c1f4e77421687b6b94bbebd8a15a64ab8279adf6fbb047eff47e536", size = 347594 }, + { url = "https://files.pythonhosted.org/packages/46/2c/35f4347f76ea4c986e9c1f774b085f489b3a1bf1503c67a4dfc5d8e68e92/yarl-1.19.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:262087a8a0d73e1d169d45c2baf968126f93c97cf403e1af23a7d5455d52721f", size = 334113 }, + { url = "https://files.pythonhosted.org/packages/20/89/3086bc8ec8d7bd505531c51056452d7ae6af906d29c427374f1170ac1938/yarl-1.19.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:70f384921c24e703d249a6ccdabeb57dd6312b568b504c69e428a8dd3e8e68ca", size = 361037 }, + { url = "https://files.pythonhosted.org/packages/a1/5b/2c9765524a70d1c51922b41c91caa30c8094a416734349166e1a3d8de055/yarl-1.19.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:756b9ea5292a2c180d1fe782a377bc4159b3cfefaca7e41b5b0a00328ef62fa9", size = 361025 }, + { url = "https://files.pythonhosted.org/packages/ca/f8/c4a190bcc3cd98fb428d1dd31519e58004153dc7f2acd1236ecae54e3433/yarl-1.19.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cbeb9c145d534c240a63b6ecc8a8dd451faeb67b3dc61d729ec197bb93e29497", size = 364397 }, + { url = "https://files.pythonhosted.org/packages/6b/fb/f65b1347be8e12ac4e3e37a9bb880e6b9b604f252aaafd88e4879b1e9348/yarl-1.19.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:087ae8f8319848c18e0d114d0f56131a9c017f29200ab1413b0137ad7c83e2ae", size = 374065 }, + { url = "https://files.pythonhosted.org/packages/1c/c5/102cc3b9baad1a76f9127453ad08e0f5bc9c996c18128b1e28fe03817d6c/yarl-1.19.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362f5480ba527b6c26ff58cff1f229afe8b7fdd54ee5ffac2ab827c1a75fc71c", size = 381341 }, + { url = "https://files.pythonhosted.org/packages/f7/ce/f5dc0439320dfe59fadab8cdd24ac324be19cf6ae4736422c7e2a510ddf3/yarl-1.19.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f408d4b4315e814e5c3668094e33d885f13c7809cbe831cbdc5b1bb8c7a448f4", size = 376552 }, + { url = "https://files.pythonhosted.org/packages/a9/4a/4833a134c76af987eff3ce8cb71e42932234120e6be061eb2555061e8844/yarl-1.19.0-cp313-cp313-win32.whl", hash = "sha256:24e4c367ad69988a2283dd45ea88172561ca24b2326b9781e164eb46eea68345", size = 85878 }, + { url = "https://files.pythonhosted.org/packages/32/e9/59327daab3af8f79221638a8f0d11474d20f6a8fbc41e9da80c5ef69e688/yarl-1.19.0-cp313-cp313-win_amd64.whl", hash = "sha256:0110f91c57ab43d1538dfa92d61c45e33b84df9257bd08fcfcda90cce931cbc9", size = 92448 }, + { url = "https://files.pythonhosted.org/packages/a4/06/ae25a353e8f032322df6f30d6bb1fc329773ee48e1a80a2196ccb8d1206b/yarl-1.19.0-py3-none-any.whl", hash = "sha256:a727101eb27f66727576630d02985d8a065d09cd0b5fcbe38a5793f71b2a97ef", size = 45990 }, +] + +[[package]] +name = "zstandard" +version = "0.23.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation == 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ed/f6/2ac0287b442160a89d726b17a9184a4c615bb5237db763791a7fd16d9df1/zstandard-0.23.0.tar.gz", hash = "sha256:b2d8c62d08e7255f68f7a740bae85b3c9b8e5466baa9cbf7f57f1cde0ac6bc09", size = 681701 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/40/f67e7d2c25a0e2dc1744dd781110b0b60306657f8696cafb7ad7579469bd/zstandard-0.23.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:34895a41273ad33347b2fc70e1bff4240556de3c46c6ea430a7ed91f9042aa4e", size = 788699 }, + { url = "https://files.pythonhosted.org/packages/e8/46/66d5b55f4d737dd6ab75851b224abf0afe5774976fe511a54d2eb9063a41/zstandard-0.23.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:77ea385f7dd5b5676d7fd943292ffa18fbf5c72ba98f7d09fc1fb9e819b34c23", size = 633681 }, + { url = "https://files.pythonhosted.org/packages/63/b6/677e65c095d8e12b66b8f862b069bcf1f1d781b9c9c6f12eb55000d57583/zstandard-0.23.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:983b6efd649723474f29ed42e1467f90a35a74793437d0bc64a5bf482bedfa0a", size = 4944328 }, + { url = "https://files.pythonhosted.org/packages/59/cc/e76acb4c42afa05a9d20827116d1f9287e9c32b7ad58cc3af0721ce2b481/zstandard-0.23.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80a539906390591dd39ebb8d773771dc4db82ace6372c4d41e2d293f8e32b8db", size = 5311955 }, + { url = "https://files.pythonhosted.org/packages/78/e4/644b8075f18fc7f632130c32e8f36f6dc1b93065bf2dd87f03223b187f26/zstandard-0.23.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:445e4cb5048b04e90ce96a79b4b63140e3f4ab5f662321975679b5f6360b90e2", size = 5344944 }, + { url = "https://files.pythonhosted.org/packages/76/3f/dbafccf19cfeca25bbabf6f2dd81796b7218f768ec400f043edc767015a6/zstandard-0.23.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd30d9c67d13d891f2360b2a120186729c111238ac63b43dbd37a5a40670b8ca", size = 5442927 }, + { url = "https://files.pythonhosted.org/packages/0c/c3/d24a01a19b6733b9f218e94d1a87c477d523237e07f94899e1c10f6fd06c/zstandard-0.23.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d20fd853fbb5807c8e84c136c278827b6167ded66c72ec6f9a14b863d809211c", size = 4864910 }, + { url = "https://files.pythonhosted.org/packages/1c/a9/cf8f78ead4597264f7618d0875be01f9bc23c9d1d11afb6d225b867cb423/zstandard-0.23.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed1708dbf4d2e3a1c5c69110ba2b4eb6678262028afd6c6fbcc5a8dac9cda68e", size = 4935544 }, + { url = "https://files.pythonhosted.org/packages/2c/96/8af1e3731b67965fb995a940c04a2c20997a7b3b14826b9d1301cf160879/zstandard-0.23.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:be9b5b8659dff1f913039c2feee1aca499cfbc19e98fa12bc85e037c17ec6ca5", size = 5467094 }, + { url = "https://files.pythonhosted.org/packages/ff/57/43ea9df642c636cb79f88a13ab07d92d88d3bfe3e550b55a25a07a26d878/zstandard-0.23.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:65308f4b4890aa12d9b6ad9f2844b7ee42c7f7a4fd3390425b242ffc57498f48", size = 4860440 }, + { url = "https://files.pythonhosted.org/packages/46/37/edb78f33c7f44f806525f27baa300341918fd4c4af9472fbc2c3094be2e8/zstandard-0.23.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:98da17ce9cbf3bfe4617e836d561e433f871129e3a7ac16d6ef4c680f13a839c", size = 4700091 }, + { url = "https://files.pythonhosted.org/packages/c1/f1/454ac3962671a754f3cb49242472df5c2cced4eb959ae203a377b45b1a3c/zstandard-0.23.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:8ed7d27cb56b3e058d3cf684d7200703bcae623e1dcc06ed1e18ecda39fee003", size = 5208682 }, + { url = "https://files.pythonhosted.org/packages/85/b2/1734b0fff1634390b1b887202d557d2dd542de84a4c155c258cf75da4773/zstandard-0.23.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:b69bb4f51daf461b15e7b3db033160937d3ff88303a7bc808c67bbc1eaf98c78", size = 5669707 }, + { url = "https://files.pythonhosted.org/packages/52/5a/87d6971f0997c4b9b09c495bf92189fb63de86a83cadc4977dc19735f652/zstandard-0.23.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:034b88913ecc1b097f528e42b539453fa82c3557e414b3de9d5632c80439a473", size = 5201792 }, + { url = "https://files.pythonhosted.org/packages/79/02/6f6a42cc84459d399bd1a4e1adfc78d4dfe45e56d05b072008d10040e13b/zstandard-0.23.0-cp311-cp311-win32.whl", hash = "sha256:f2d4380bf5f62daabd7b751ea2339c1a21d1c9463f1feb7fc2bdcea2c29c3160", size = 430586 }, + { url = "https://files.pythonhosted.org/packages/be/a2/4272175d47c623ff78196f3c10e9dc7045c1b9caf3735bf041e65271eca4/zstandard-0.23.0-cp311-cp311-win_amd64.whl", hash = "sha256:62136da96a973bd2557f06ddd4e8e807f9e13cbb0bfb9cc06cfe6d98ea90dfe0", size = 495420 }, + { url = "https://files.pythonhosted.org/packages/7b/83/f23338c963bd9de687d47bf32efe9fd30164e722ba27fb59df33e6b1719b/zstandard-0.23.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b4567955a6bc1b20e9c31612e615af6b53733491aeaa19a6b3b37f3b65477094", size = 788713 }, + { url = "https://files.pythonhosted.org/packages/5b/b3/1a028f6750fd9227ee0b937a278a434ab7f7fdc3066c3173f64366fe2466/zstandard-0.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e172f57cd78c20f13a3415cc8dfe24bf388614324d25539146594c16d78fcc8", size = 633459 }, + { url = "https://files.pythonhosted.org/packages/26/af/36d89aae0c1f95a0a98e50711bc5d92c144939efc1f81a2fcd3e78d7f4c1/zstandard-0.23.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0e166f698c5a3e914947388c162be2583e0c638a4703fc6a543e23a88dea3c1", size = 4945707 }, + { url = "https://files.pythonhosted.org/packages/cd/2e/2051f5c772f4dfc0aae3741d5fc72c3dcfe3aaeb461cc231668a4db1ce14/zstandard-0.23.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12a289832e520c6bd4dcaad68e944b86da3bad0d339ef7989fb7e88f92e96072", size = 5306545 }, + { url = "https://files.pythonhosted.org/packages/0a/9e/a11c97b087f89cab030fa71206963090d2fecd8eb83e67bb8f3ffb84c024/zstandard-0.23.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d50d31bfedd53a928fed6707b15a8dbeef011bb6366297cc435accc888b27c20", size = 5337533 }, + { url = "https://files.pythonhosted.org/packages/fc/79/edeb217c57fe1bf16d890aa91a1c2c96b28c07b46afed54a5dcf310c3f6f/zstandard-0.23.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72c68dda124a1a138340fb62fa21b9bf4848437d9ca60bd35db36f2d3345f373", size = 5436510 }, + { url = "https://files.pythonhosted.org/packages/81/4f/c21383d97cb7a422ddf1ae824b53ce4b51063d0eeb2afa757eb40804a8ef/zstandard-0.23.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53dd9d5e3d29f95acd5de6802e909ada8d8d8cfa37a3ac64836f3bc4bc5512db", size = 4859973 }, + { url = "https://files.pythonhosted.org/packages/ab/15/08d22e87753304405ccac8be2493a495f529edd81d39a0870621462276ef/zstandard-0.23.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6a41c120c3dbc0d81a8e8adc73312d668cd34acd7725f036992b1b72d22c1772", size = 4936968 }, + { url = "https://files.pythonhosted.org/packages/eb/fa/f3670a597949fe7dcf38119a39f7da49a8a84a6f0b1a2e46b2f71a0ab83f/zstandard-0.23.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:40b33d93c6eddf02d2c19f5773196068d875c41ca25730e8288e9b672897c105", size = 5467179 }, + { url = "https://files.pythonhosted.org/packages/4e/a9/dad2ab22020211e380adc477a1dbf9f109b1f8d94c614944843e20dc2a99/zstandard-0.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9206649ec587e6b02bd124fb7799b86cddec350f6f6c14bc82a2b70183e708ba", size = 4848577 }, + { url = "https://files.pythonhosted.org/packages/08/03/dd28b4484b0770f1e23478413e01bee476ae8227bbc81561f9c329e12564/zstandard-0.23.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76e79bc28a65f467e0409098fa2c4376931fd3207fbeb6b956c7c476d53746dd", size = 4693899 }, + { url = "https://files.pythonhosted.org/packages/2b/64/3da7497eb635d025841e958bcd66a86117ae320c3b14b0ae86e9e8627518/zstandard-0.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:66b689c107857eceabf2cf3d3fc699c3c0fe8ccd18df2219d978c0283e4c508a", size = 5199964 }, + { url = "https://files.pythonhosted.org/packages/43/a4/d82decbab158a0e8a6ebb7fc98bc4d903266bce85b6e9aaedea1d288338c/zstandard-0.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9c236e635582742fee16603042553d276cca506e824fa2e6489db04039521e90", size = 5655398 }, + { url = "https://files.pythonhosted.org/packages/f2/61/ac78a1263bc83a5cf29e7458b77a568eda5a8f81980691bbc6eb6a0d45cc/zstandard-0.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a8fffdbd9d1408006baaf02f1068d7dd1f016c6bcb7538682622c556e7b68e35", size = 5191313 }, + { url = "https://files.pythonhosted.org/packages/e7/54/967c478314e16af5baf849b6ee9d6ea724ae5b100eb506011f045d3d4e16/zstandard-0.23.0-cp312-cp312-win32.whl", hash = "sha256:dc1d33abb8a0d754ea4763bad944fd965d3d95b5baef6b121c0c9013eaf1907d", size = 430877 }, + { url = "https://files.pythonhosted.org/packages/75/37/872d74bd7739639c4553bf94c84af7d54d8211b626b352bc57f0fd8d1e3f/zstandard-0.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:64585e1dba664dc67c7cdabd56c1e5685233fbb1fc1966cfba2a340ec0dfff7b", size = 495595 }, + { url = "https://files.pythonhosted.org/packages/80/f1/8386f3f7c10261fe85fbc2c012fdb3d4db793b921c9abcc995d8da1b7a80/zstandard-0.23.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:576856e8594e6649aee06ddbfc738fec6a834f7c85bf7cadd1c53d4a58186ef9", size = 788975 }, + { url = "https://files.pythonhosted.org/packages/16/e8/cbf01077550b3e5dc86089035ff8f6fbbb312bc0983757c2d1117ebba242/zstandard-0.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:38302b78a850ff82656beaddeb0bb989a0322a8bbb1bf1ab10c17506681d772a", size = 633448 }, + { url = "https://files.pythonhosted.org/packages/06/27/4a1b4c267c29a464a161aeb2589aff212b4db653a1d96bffe3598f3f0d22/zstandard-0.23.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2240ddc86b74966c34554c49d00eaafa8200a18d3a5b6ffbf7da63b11d74ee2", size = 4945269 }, + { url = "https://files.pythonhosted.org/packages/7c/64/d99261cc57afd9ae65b707e38045ed8269fbdae73544fd2e4a4d50d0ed83/zstandard-0.23.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ef230a8fd217a2015bc91b74f6b3b7d6522ba48be29ad4ea0ca3a3775bf7dd5", size = 5306228 }, + { url = "https://files.pythonhosted.org/packages/7a/cf/27b74c6f22541f0263016a0fd6369b1b7818941de639215c84e4e94b2a1c/zstandard-0.23.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:774d45b1fac1461f48698a9d4b5fa19a69d47ece02fa469825b442263f04021f", size = 5336891 }, + { url = "https://files.pythonhosted.org/packages/fa/18/89ac62eac46b69948bf35fcd90d37103f38722968e2981f752d69081ec4d/zstandard-0.23.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f77fa49079891a4aab203d0b1744acc85577ed16d767b52fc089d83faf8d8ed", size = 5436310 }, + { url = "https://files.pythonhosted.org/packages/a8/a8/5ca5328ee568a873f5118d5b5f70d1f36c6387716efe2e369010289a5738/zstandard-0.23.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac184f87ff521f4840e6ea0b10c0ec90c6b1dcd0bad2f1e4a9a1b4fa177982ea", size = 4859912 }, + { url = "https://files.pythonhosted.org/packages/ea/ca/3781059c95fd0868658b1cf0440edd832b942f84ae60685d0cfdb808bca1/zstandard-0.23.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c363b53e257246a954ebc7c488304b5592b9c53fbe74d03bc1c64dda153fb847", size = 4936946 }, + { url = "https://files.pythonhosted.org/packages/ce/11/41a58986f809532742c2b832c53b74ba0e0a5dae7e8ab4642bf5876f35de/zstandard-0.23.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e7792606d606c8df5277c32ccb58f29b9b8603bf83b48639b7aedf6df4fe8171", size = 5466994 }, + { url = "https://files.pythonhosted.org/packages/83/e3/97d84fe95edd38d7053af05159465d298c8b20cebe9ccb3d26783faa9094/zstandard-0.23.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a0817825b900fcd43ac5d05b8b3079937073d2b1ff9cf89427590718b70dd840", size = 4848681 }, + { url = "https://files.pythonhosted.org/packages/6e/99/cb1e63e931de15c88af26085e3f2d9af9ce53ccafac73b6e48418fd5a6e6/zstandard-0.23.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9da6bc32faac9a293ddfdcb9108d4b20416219461e4ec64dfea8383cac186690", size = 4694239 }, + { url = "https://files.pythonhosted.org/packages/ab/50/b1e703016eebbc6501fc92f34db7b1c68e54e567ef39e6e59cf5fb6f2ec0/zstandard-0.23.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fd7699e8fd9969f455ef2926221e0233f81a2542921471382e77a9e2f2b57f4b", size = 5200149 }, + { url = "https://files.pythonhosted.org/packages/aa/e0/932388630aaba70197c78bdb10cce2c91fae01a7e553b76ce85471aec690/zstandard-0.23.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d477ed829077cd945b01fc3115edd132c47e6540ddcd96ca169facff28173057", size = 5655392 }, + { url = "https://files.pythonhosted.org/packages/02/90/2633473864f67a15526324b007a9f96c96f56d5f32ef2a56cc12f9548723/zstandard-0.23.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ce8b52c5987b3e34d5674b0ab529a4602b632ebab0a93b07bfb4dfc8f8a33", size = 5191299 }, + { url = "https://files.pythonhosted.org/packages/b0/4c/315ca5c32da7e2dc3455f3b2caee5c8c2246074a61aac6ec3378a97b7136/zstandard-0.23.0-cp313-cp313-win32.whl", hash = "sha256:a9b07268d0c3ca5c170a385a0ab9fb7fdd9f5fd866be004c4ea39e44edce47dd", size = 430862 }, + { url = "https://files.pythonhosted.org/packages/a2/bf/c6aaba098e2d04781e8f4f7c0ba3c7aa73d00e4c436bcc0cf059a66691d1/zstandard-0.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:f3513916e8c645d0610815c257cbfd3242adfd5c4cfa78be514e5a3ebb42a41b", size = 495578 }, +] + +[[package]] +name = "zstd" +version = "1.5.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/63/36/4a5a54756881bde43151dfdb3bed2c4b7f5151b4274767d372194ce13a1e/zstd-1.5.7.1.tar.gz", hash = "sha256:1a8550e35a68e7530935715342dab3bb903705e9e91d87ef8845694b7bbb866f", size = 670457 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/e3/5be836cdf9654f9707fa588959acebfa136374de632139eaa328fe5cbc93/zstd-1.5.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0f91ed2f1874f31fa17df650199603ae11a510b96d6de06c9792408705538c7c", size = 269564 }, + { url = "https://files.pythonhosted.org/packages/90/12/bef69552a53413fed28cb6c668772baa7de0b896b9ac9c2b0fdfbe84ee34/zstd-1.5.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6bb066635f4b9850593ad9b31325b7d5c711c490fb49f5ce22fba7b541367f46", size = 228106 }, + { url = "https://files.pythonhosted.org/packages/c3/f7/dd3c9b6dbd6786df2ce59be0fb9f9dcf89281ffe617d950f0b3f8885b392/zstd-1.5.7.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:c2c8b620e2d6fdc409686667a7faa5a4d1f101ce00b0216caedf2ac9f105cf95", size = 1536405 }, + { url = "https://files.pythonhosted.org/packages/ff/91/a4125a0fadcc6af67373079586f03bb7a771164b1e3366c14bb812684c6a/zstd-1.5.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:dd9dfc594a18a95c90164dab85c32985cd3eb23d0ac84c9d30dd99d271073ac7", size = 1615972 }, + { url = "https://files.pythonhosted.org/packages/90/34/dd00f7963ca09c327b83ee77dd8f2247c5500735b340fda047d6a4f03b13/zstd-1.5.7.1-cp311-cp311-manylinux_2_4_i686.whl", hash = "sha256:99bbc9eabd5c0626a619343f8d788b682c9c1f648a8eef7738bd375ce9676408", size = 322170 }, + { url = "https://files.pythonhosted.org/packages/01/63/833549bd97f230189dbdbd90d036ef9c6e18e4505527c5ae9cbf7864c420/zstd-1.5.7.1-cp311-cp311-manylinux_2_4_x86_64.whl", hash = "sha256:bf1c15a7260fd8ecd309bd13f7171bb1eb04bf451c2d8543b068143c01b04b07", size = 302694 }, + { url = "https://files.pythonhosted.org/packages/39/ef/5bb977949dab39baeee5e5435d998a228e1276d47caf57c8dfee9f3f8fa6/zstd-1.5.7.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:a1c675e740dfed865c6205fb02a83855514130c1511f464ed1e6ad14e18dff18", size = 1522484 }, + { url = "https://files.pythonhosted.org/packages/52/e1/80ad4f9079edbc60f57ef90f72dca17284699b8c2d386b2b9a1429ef346b/zstd-1.5.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b532f136c0f487f57974a453c6808d31ddd386a93c0f6b6a89770e8a3cc6646c", size = 2098375 }, + { url = "https://files.pythonhosted.org/packages/f8/80/b7e83bf25c3e4a9bf4cbaf7dc7e8e6725902b77078ff58d39f1004a9e5b3/zstd-1.5.7.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5186206449adf8f5ce41d0589519e5fbfab4449c79a80cbc34a3cec4a2dd3e1d", size = 2112184 }, + { url = "https://files.pythonhosted.org/packages/8f/aa/2480ac1f209a04af080e5213bec3e82d89361316b9e72e5ad7e5922f6873/zstd-1.5.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e3602ae5782e650eff40fb615dc50f3b1e64a2fd4eb7eb295ca83d4be669ea9f", size = 2109185 }, + { url = "https://files.pythonhosted.org/packages/ff/28/4057b83b7a788cd2a29896e7bd857289e6908978e6ca083a2fc63b0bf123/zstd-1.5.7.1-cp311-cp311-win32.whl", hash = "sha256:27f422083004f449747d471e35c2d2f287053c00934e239732591187e4197ee2", size = 149543 }, + { url = "https://files.pythonhosted.org/packages/d9/f6/1ee8171a786fb11c11b012820138acfc8c74c16b6fe4c2431f5ea774f92d/zstd-1.5.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:eba6258c0369404c792ae12a47338e1e3710469e9849605fb6692336faece02c", size = 164942 }, + { url = "https://files.pythonhosted.org/packages/64/82/3fb94996bf760f857487c55e4519f743da1d6f3355ea4802847dfe10634f/zstd-1.5.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33c544923a6f78bfb7b65051b2ddb41d7670233f1cc674047b37c3a1370b1ae8", size = 269349 }, + { url = "https://files.pythonhosted.org/packages/ed/3c/b041e0871b6ca8a4fc3056ea8372cabcd4feba8f20486934d2b35cddd282/zstd-1.5.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8693329ae29e64969fc765c310b6baa1f95712bf623c134b213f50fa9728de44", size = 228113 }, + { url = "https://files.pythonhosted.org/packages/54/eb/b1fb74c7abca731efc38e3098499e23617fcb55ddf5b8a7b958feaf937b2/zstd-1.5.7.1-cp312-cp312-manylinux_2_14_x86_64.whl", hash = "sha256:dca3d98ca194a0b74346267a315fad52d33f72e53bc4f8f4012b12b17ef30084", size = 302644 }, + { url = "https://files.pythonhosted.org/packages/b7/46/1d973ccdf53d6b8e2ddd802b35fde59476e4f0a014e60b16d387d14a10b7/zstd-1.5.7.1-cp312-cp312-manylinux_2_4_i686.whl", hash = "sha256:103420af24ec1f01d274af8df9157c1e20f70cc13d81425c50854d19bb06bff3", size = 322240 }, + { url = "https://files.pythonhosted.org/packages/55/ac/9d1300e5a00d03dd08490c54128a5ad6b4a6052a9123c105fb28fd9c95e4/zstd-1.5.7.1-cp312-cp312-win32.whl", hash = "sha256:ea6bbef6efb622bbd7dd6643f1fe59b81170293ce10bea8a6bb7e47da05fdf48", size = 149555 }, + { url = "https://files.pythonhosted.org/packages/c0/31/1c7965ca552916f7f0de69078f0afecc4b254781b21036a6d7f455c7feb9/zstd-1.5.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:d26b0b9f8f5d8c2d62648e45d5a4769849dc17ea8c571e5406fbdc69ddd8150f", size = 164978 }, + { url = "https://files.pythonhosted.org/packages/db/ab/597e3eac46f74b7f4bcd761060cfe90a71d9f44bd774909dac560f72069f/zstd-1.5.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e813581dfc2d0ae0f4a6b0570c1715394d3de7488af2ead11eb3e5e35db71b33", size = 269388 }, + { url = "https://files.pythonhosted.org/packages/19/26/77c2220498bba039ca4f1594be2c163aaa39daffea1ddc3919d7c30eebbf/zstd-1.5.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:920672f3ec9ea1dbdfd1cffed50305ff258bbc349d22ff46f462b081a0915edc", size = 228189 }, + { url = "https://files.pythonhosted.org/packages/9b/60/6476ffb0960765544215d0a3d5507d3f7b29e27187a542d53196f13334b3/zstd-1.5.7.1-cp313-cp313-manylinux_2_14_x86_64.whl", hash = "sha256:ee93f8c89bc957943f74a68c932a891c05807577d16e9a148cf8726731866015", size = 302713 }, + { url = "https://files.pythonhosted.org/packages/dd/81/99a0f67950fc444c6314f643021c2be9f8ffbb0a7b4b6e30d34c7fa9c465/zstd-1.5.7.1-cp313-cp313-win32.whl", hash = "sha256:4a88a4bf433394fa121a45c5aa78406d03bf6fbbb4e5bf99717e4b04b2f8cedc", size = 149548 }, + { url = "https://files.pythonhosted.org/packages/2c/6c/18282422c644793cafde1caf93eed1232c4aa0800e520a2bfbcee6073452/zstd-1.5.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:d1c649be27b2696485d6edeef6927751c7e9f550ec5d14035d71601e28f2d50b", size = 164968 }, + { url = "https://files.pythonhosted.org/packages/06/49/b6249f64408cba0e44f905e5eaa6651a36f87856443bb979bb1ee7f6ea16/zstd-1.5.7.1-cp313-cp313t-manylinux_2_14_x86_64.whl", hash = "sha256:751f252bcbc2e52cc71959c5ea150c077e40d70c4a22fb2438da89afeed5a9ed", size = 302836 }, + { url = "https://files.pythonhosted.org/packages/f3/fa/b4162fd76ed35247f170f46bedce6626d42895e03c101a4fe7c6211b7733/zstd-1.5.7.1-cp314-cp314-manylinux_2_14_x86_64.whl", hash = "sha256:a01a2d800b5b143f80f83d3116e82d40f71f442d8a2619d5a0ac1c7ee2374ee4", size = 302729 }, + { url = "https://files.pythonhosted.org/packages/01/53/1e90cff9191faa58e9960bf12b488c55539084bd2d3e3140f52ffa677ca9/zstd-1.5.7.1-cp314-cp314t-manylinux_2_14_x86_64.whl", hash = "sha256:5881468cad917453d57ea031325446dd2e2f9675791bab60989bdfdc17f97bf9", size = 302841 }, + { url = "https://files.pythonhosted.org/packages/cf/4f/b28c833cd65879c376895f7ef78b40b27a707d4e9b91ba4ce4b252a92e06/zstd-1.5.7.1-pp311-pypy311_pp73-manylinux_2_14_x86_64.whl", hash = "sha256:384d8026627ffb6b16db7cbf85e05ee4fa484f2284bffb365220314a4cb7b1cf", size = 315474 }, +] diff --git a/README.md b/README.md index 486196b0c568c056c35b63894de06a36ab7bda11..aff94d2b6377c90d1d09871232e7b73e11bb16bf 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,22 @@ --- -title: Adaptive Block Forcing -emoji: 📈 -colorFrom: red -colorTo: gray +title: Adaptive-Block-Forcing +app_file: /dss/dssmcmlfs01/pn39qo/pn39qo-dss-0000/di35qir2/Adaptive-Block-Forcing/Discrete-Diffusion-Forcing/D2F-eval/generate_llada_demo_block.py sdk: gradio -sdk_version: 5.49.1 -app_file: app.py -pinned: false +sdk_version: 5.49.0 --- +# Adaptive-Block-Forcing + +## Installation + +```bash +git clone https://github.com/aiPenguin/Adaptive-Block-Forcing.git +cd Adaptive-Block-Forcing +git submodule init +git submodule update + +micromamba crate -n ABF python +micromamba activate ABF +pip install -r requirements.txt +micromamba install cuda-toolkit +``` -Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference diff --git a/config/acc_config b/config/acc_config new file mode 100644 index 0000000000000000000000000000000000000000..fb6e2c95b6c8123fcc1c340d653bae80aa426909 --- /dev/null +++ b/config/acc_config @@ -0,0 +1,23 @@ +compute_environment: LOCAL_MACHINE +debug: false +deepspeed_config: + gradient_accumulation_steps: 3 + gradient_clipping: 1.0 + offload_optimizer_device: none + offload_param_device: none + zero3_init_flag: False + zero_stage: 2 +distributed_type: DEEPSPEED +downcast_bf16: 'no' +enable_cpu_affinity: True +machine_rank: 0 +main_training_function: main +mixed_precision: fp16 +num_machines: 1 +num_processes: 4 +rdzv_backend: static +same_network: false +tpu_env: [] +tpu_use_cluster: false +tpu_use_sudo: false +use_cpu: True diff --git a/config/dream_eagle.yaml b/config/dream_eagle.yaml new file mode 100644 index 0000000000000000000000000000000000000000..9bfb3862aceb4064b022359ca8c5229801491c8e --- /dev/null +++ b/config/dream_eagle.yaml @@ -0,0 +1,59 @@ +# Training mode configuration +training_mode: 'dream' # 'llada' or 'dream' + +# Model and data path configuration +paths: + model: 'Dream-org/Dream-v0-Base-7B' + experiment: 'ckpt_dream_base' + data: + bs: 'Lansechen/bs17k_collection_filtered_hard_maxlength600' + bs_easy: 'Lansechen/bs17k_collection_filtered_easy_maxlength600' + +denoiser: + encoder: + name: 'dream' + mask_id: 151666 + + decoder: + wiinit: true + name: 'eagle_rope' + num_blocks: 1 + seq_len: &seq_len 1024 + input_dim: 3584 + hidden_dim: &dim 3584 + vocab_size: 152064 + block: + seq_len: *seq_len + hidden_dim: *dim + num_heads: 32 + +train: + # Will use paths.experiment path + decoder_resume_path: + head_resume_path: + skipped_keys: + global_step: + exp_name: &exp_name 'ddt_test' + wandb_proj: *exp_name + output_dir: 'ddt_test' + logging_dir: 'logs' + mixed_precision: 'fp16' + gradient_accumulation_steps: 5 + report_to: 'wandb' + block_size: 16 + + lr: 5e-6 + num_iters: 50000 + eval_every: 100000 + save_every: 1000 + + enable_shift: true + share_steps: 2 + self_align: true + feature_align: false + self_step: true + +data: + name: 'bs17k' #['numinamath', 'bs17k'] + batch_size: 1 + max_length: *seq_len diff --git a/config/llada.yaml b/config/llada.yaml new file mode 100644 index 0000000000000000000000000000000000000000..0f8e10d99f27f01b197d54235e4063d671045e49 --- /dev/null +++ b/config/llada.yaml @@ -0,0 +1,59 @@ +# Training mode configuration +training_mode: 'llada' # 'llada' or 'dream' + +# Model and data path configuration +paths: + model: 'GSAI-ML/LLaDA-8B-Instruct' + experiment: 'ckpt_llada_instruct' + data: + bs: 'Lansechen/bs17k_collection_filtered_hard_maxlength600' + bs_easy: 'Lansechen/bs17k_collection_filtered_easy_maxlength600' + +denoiser: + encoder: + name: 'dream' + mask_id: 151666 + + decoder: + wiinit: true + name: 'eagle_rope' + num_blocks: 1 + seq_len: &seq_len 1024 + input_dim: 3584 + hidden_dim: &dim 3584 + vocab_size: 152064 + block: + seq_len: *seq_len + hidden_dim: *dim + num_heads: 32 + +train: + # Will use paths.experiment path + decoder_resume_path: + head_resume_path: + skipped_keys: + global_step: + exp_name: &exp_name 'llada_ddt_maskteacher' + wandb_proj: *exp_name + output_dir: 'ddt_test' + logging_dir: 'logs' + mixed_precision: 'fp16' + gradient_accumulation_steps: 5 + report_to: 'wandb' + block_size: 16 + + lr: 1e-5 + num_iters: 50000 + eval_every: 100000 + save_every: 1000 + + enable_shift: true + share_steps: 2 + self_align: true + feature_align: false + self_step: true + +data: + name: 'bs17k' #['numinamath', 'bs17k'] + batch_size: 1 + max_length: *seq_len diff --git a/login.sh b/login.sh new file mode 100644 index 0000000000000000000000000000000000000000..c5b41a1077c183105bc93471c4061883668eaee5 --- /dev/null +++ b/login.sh @@ -0,0 +1,11 @@ +# 1) 先设变量(路径按你提供的) +BASE="/dss/dssmcmlfs01/pn39qo/pn39qo-dss-0000/di35qir2" +IMAGE="$BASE/enroot_images/py310-torch28-cu128-vllm-flashattn-deepspeed.sqsh" +VENV="$BASE/Adaptive-Block-Forcing/venv" + +# 2) 进入你已有的作业分配里,开启容器交互shell +srun --jobid=5348695 \ + --container-image="$IMAGE" \ + --container-mounts="$BASE:$BASE" \ + --cwd="$BASE" \ + --pty bash \ No newline at end of file diff --git a/model/configuration_llada.py b/model/configuration_llada.py new file mode 100644 index 0000000000000000000000000000000000000000..3556bdac0bc0b06c6ec606830a28e9b3e6aeed56 --- /dev/null +++ b/model/configuration_llada.py @@ -0,0 +1,463 @@ +""" +LLaDA configuration +""" +from transformers import AutoConfig, PretrainedConfig + +from enum import Enum +from os import PathLike +from typing import Union +from dataclasses import asdict, dataclass, field +from glob import glob +from pathlib import Path +from typing import ( + Any, + Dict, + Iterable, + List, + Optional, + Tuple, + Type, + TypeVar, + Union, + cast, +) + + +__all__ = [ + "ActivationType", + "ActivationCheckpointingStrategy", + "BlockType", + "LayerNormType", + "InitFnType", + "ModelConfig", +] + +PathOrStr = Union[str, PathLike] + + +class StrEnum(str, Enum): + """ + This is equivalent to Python's :class:`enum.StrEnum` since version 3.11. + We include this here for compatibility with older version of Python. + """ + + def __str__(self) -> str: + return self.value + + def __repr__(self) -> str: + return f"'{str(self)}'" + + +class LayerNormType(StrEnum): + default = "default" + """ + The default LayerNorm implementation, equivalent to PyTorch's built-in version. + """ + + low_precision = "low_precision" + """ + A low-precision version of the default LayerNorm. + """ + + rms = "rms" + """ + An RMSNorm implementation. When using ``torch.compile`` this is + probably the fastest implementation. + """ + + gemma_rms = "gemma_rms" + """ + An RMSNorm implementation by gemmma. When using ``torch.compile`` this is + probably the fastest implementation. + """ + + amd_compatible = "amd_compatible" + """ + LayerNorm implemented manually to work around an issue with ROCm. + """ + + +class ActivationType(StrEnum): + gelu = "gelu" + relu = "relu" + silu = "silu" + swiglu = "swiglu" + + +class BlockType(StrEnum): + sequential = "sequential" + parallel = "parallel" + + llama = "llama" + """ + A block similar to the sequential block with slightly different + implementations of operations like attention to imitate the behavior of Llama. + """ + + +class InitFnType(StrEnum): + mitchell = "mitchell" + """ + The strategy suggested to us by Mitchell Wortsman from UW. + This uses a truncated normal distribution with an adaptive standard deviation that depends + on the size of the weights as well as the depth of the layer. + """ + + normal = "normal" + """ + All weights are initialized from the same normal distribution. + """ + + kaiming_normal = "kaiming_normal" + """ + All weights are initialized with the Kaiming method from a normal distribution. + Note this currently won't work with FSDP. + """ + + fan_in = "fan_in" + """ + "Fan-in variance scaling", i.e. normal with a standard deviation of ``1/sqrt(d_in)`` where ``d_in`` + is the input dimensionality of the kernel. + """ + + full_megatron = "full_megatron" + """ + This is what metaseq calls "full megatron init". It is the init used for Llama 2. + """ + + +@dataclass +class ModelConfig(): + """ + LLaDA (model) configuration. + """ + + # Note that the defaults for these attributes are equivalent to the base GPT2 model. + + d_model: int = 768 + """ + The hidden size of the model. + """ + + n_heads: int = 12 + """ + The number of self-attention heads. + """ + + n_kv_heads: Optional[int] = None + """ + The number of heads to use for keys and values. Defaults to `n_heads`. + Set this to ``None`` or ``n_heads`` for normal multi-head attention. + Set this to 1 for multi-query attention. + Set it to some in-between value for Llama2-style grouped query attention. + """ + + n_layers: int = 12 + """ + The number of layers/blocks. + """ + + mlp_ratio: int = 4 + """ + The ratio of the inner MLP dimensionality to ``d_model``. + This is only used when ``mlp_hidden_size`` is not set. + """ + + mlp_hidden_size: Optional[int] = None + """ + Set the exact hidden size for the MLP. Otherwise the inner MLP hidden size will be set to `mlp_ratio * d_model`. + """ + + activation_type: ActivationType = ActivationType.swiglu + """ + The activation function to use within the MLP layers. + """ + + block_type: BlockType = BlockType.sequential + """ + The transformer block implementation. + """ + + block_group_size: int = 1 + """ + The number of blocks to group together into a single parent block. + This has no affect on the number of parameters in the model and is only used to wrap groups + of blocks together with a single FSDP wrapper during training. + """ + + alibi: bool = False + """ + If ``True``, use ALiBi embeddings. Mutually exclusive with ``rope``. + """ + + alibi_bias_max: float = 8.0 + """ + Maximum absolute value of ALiBi bias. + """ + + rope: bool = False + """ + Use rotary positional embeddings (RoPE). Mutually exclusive with ``alibi``. + """ + + rope_full_precision: bool = True + """ + If ``True``, apply RoPE embeddings at full precision regardless of the input type. Otherwise, + apply RoPE at the precision of the input. + """ + + flash_attention: bool = False + """ + If ``True``, use ``FlashAttention``. + """ + + attention_dropout: float = 0.1 + """ + The dropout probability within the attention modules. + """ + + multi_query_attention: Optional[bool] = None + """ + Use the Multi-Query formulation of attention used in PaLM. This reduces the number of parameters + and is more efficient during inference. + """ + + attention_layer_norm: bool = False + """ + Apply layer norm to the keys and queries within the attention mechanism. + This can help stabilize training. + """ + + residual_dropout: float = 0.1 + """ + The dropout probability for the MLP and attention output within each block. + """ + + embedding_dropout: float = 0.1 + """ + The dropout probability for embeddings. + """ + + input_emb_norm: bool = False + """ + An input hidden_states norm implementation by gemmma. + """ + + layer_norm_type: LayerNormType = LayerNormType.default + """ + The layernorm implementation to use. + """ + + layer_norm_with_affine: bool = True + """ + Whether to include bias and weight parameters for the layer norms. + This only affects layer norms that are immediately followed by a linear layer in the forward pass, + so everything except QK-norms. To turn off affines for QK norms as well, set :attr:`attention_layer_norm_with_affine` + to ``False``. + """ + + rms_norm_eps: float = 1e-05 + """ + The rms layernorm eps param. + """ + + attention_layer_norm_with_affine: bool = True + """ + Toggle affine transform for the QK norms. + """ + + max_sequence_length: int = 1024 + """ + The maximum input sequence length supported by the model. + """ + + rope_theta: float = 10000.0 + """ + The rope base param. + """ + + include_qkv_bias: Optional[bool] = False + """ + Whether or not to include bias parameters in qkv linear layers. + """ + + include_bias: bool = False + """ + Whether or not to include bias parameters in linear layers. + In PaLM, they got rid of all bias terms because they found that large + models tend to have near 0 bias terms anyway. + """ + + bias_for_layer_norm: Optional[bool] = None + """ + Whether or not to include bias parameters in layer norm. + This is separate from the include_bias parameter, because of a ROCm crash when biases are disabled in + layer norm. + When this is None (the default), it inherits the setting from include_bias. + """ + + scale_logits: bool = False + """ + If ``True``, scale the output logits by ``1 / sqrt(d_model)``. + """ + + vocab_size: int = 50257 + """ + Vocabulary size of the model. + """ + + embedding_size: Optional[int] = 50304 + """ + The number of embeddings, i.e. the number of tokens. If set to ``None`` it will default + to ``vocab_size``. If ``vocab_size`` is not a multiple of 128, setting this to the + next multiple of 128 that's greater than ``vocab_size`` can improve throughput + substantially. + """ + + weight_tying: bool = True + """ + Whether to tie output linear weights to the input embedding. + """ + + eos_token_id: int = 50256 + """ + The ID of the end-of-sentence special token. + """ + + pad_token_id: int = 50256 + """ + The ID of the token to use for padding. Defaults to the ID of the EOS token. + """ + + mask_token_id: Optional[int] = 50256 + """ + The ID of the token to use for mask token. Defaults to the ID of the EOS token. + """ + + init_device: Optional[str] = None + """ + The torch device to use when initializing the model parameters, e.g. "cpu", "cuda:0", "meta". + """ + + init_fn: InitFnType = InitFnType.normal + """ + The weight initialization strategy. + """ + + init_std: float = 0.02 + """ + The standard deviation to use when initializing weights with a "fixed distribution" ``init_fn``, such + as "normal". + """ + + init_cutoff_factor: Optional[float] = None + """ + A positive factor used to scale the cutoff values when initializing weights with a "fixed distribution" ``init_fn``, such + as "normal". Setting this to None means values are not cutoff. + """ + + precision: Optional[str] = None + """ + Precision used to train/evaluate with. You shouldn't set this directly. + See :data:`TrainConfig.precision` instead. + """ + + @property + def effective_n_kv_heads(self) -> int: + if self.n_kv_heads is None: + if self.multi_query_attention is True: + return 1 + else: + return self.n_heads + else: + if self.multi_query_attention is None: + return self.n_kv_heads + if self.multi_query_attention: + n_kv_heads_should_be = 1 + else: + n_kv_heads_should_be = self.n_heads + if self.n_kv_heads == n_kv_heads_should_be: + return n_kv_heads_should_be + else: + raise Exception( + "You can't set `multi_query_attention` and `n_kv_heads` at the same time." + ) + +class ActivationCheckpointingStrategy(StrEnum): + whole_layer = "whole_layer" + """ + Checkpoint every transformer layer. + """ + + one_in_two = "one_in_two" + """ + Checkpoint one in two transformer layers. + """ + + one_in_three = "one_in_three" + """ + Checkpoint one in three transformer layers. + """ + + one_in_four = "one_in_four" + """ + Checkpoint one in four transformer layers. + """ + + two_in_three = "two_in_three" + """ + Checkpoint two out of every three transformer layers. + """ + + three_in_four = "three_in_four" + """ + Checkpoint three out of four of every transformer layers. + """ + + four_in_five = "four_in_five" + """ + Checkpoint four out of five of every transformer layers. + """ + + nine_in_ten = "nine_in_ten" + """ + Checkpoint nine out of ten of every transformer layers. + """ + + fine_grained = "fine_grained" + """ + Focus checkpointing on where it is cheap to recompute and saves most memory. + """ + + +class LLaDAConfig(PretrainedConfig): + model_type = "llada" + keys_to_ignore_at_inference = ["past_key_values"] # TODO: confirm + + def __init__(self, use_cache: bool = False, **kwargs): + model_config = ModelConfig() + all_kwargs = model_config.__dict__ + all_kwargs.update(kwargs) + all_kwargs.update({"use_cache": use_cache}) + all_kwargs.update( + { + "architectures": all_kwargs.get("architectures", ["LLaDAModelLM"]) + } + ) + super().__init__(**all_kwargs) + + @property + def num_attention_heads(self): + return self.n_heads + + @property + def num_hidden_layers(self): + return self.n_layers + + @property + def hidden_size(self): + return self.d_model + + +# Register the config class so that it is available for transformer pipelines, auto-loading etc. +AutoConfig.register("llada", LLaDAConfig) diff --git a/model/modeling_llada.py b/model/modeling_llada.py new file mode 100644 index 0000000000000000000000000000000000000000..4d9c4eb22e7eafa6cb93166202f4f470e18b3796 --- /dev/null +++ b/model/modeling_llada.py @@ -0,0 +1,1500 @@ +from __future__ import annotations + +import logging +import math +import sys +from abc import abstractmethod +from collections import defaultdict +from functools import partial +from typing import ( + Callable, + Dict, + Iterable, + List, + NamedTuple, + Optional, + Sequence, + Set, + Tuple, + cast, +) +from dataclasses import fields +from typing import List, Optional, Tuple, Union + +import torch +import torch.backends.cuda +import torch.nn as nn +import torch.nn.functional as F +from torch import einsum +from transformers import PreTrainedModel +from transformers.modeling_outputs import CausalLMOutputWithPast +from transformers.models.auto import AutoModel +from transformers.cache_utils import Cache + +from .configuration_llada import ( + LLaDAConfig, + StrEnum, + InitFnType, + ActivationType, + BlockType, + LayerNormType, + ModelConfig, + ActivationCheckpointingStrategy, +) + +if sys.version_info.minor > 8: + from collections.abc import MutableMapping +elif sys.version_info.minor == 8: + from typing import MutableMapping +else: + raise SystemExit("This script supports Python 3.8 or higher") + +__all__ = [ + "LayerNormBase", + "LayerNorm", + "RMSLayerNorm", + "GemmaRMSLayerNorm", + "RotaryEmbedding", + "Activation", + "GELU", + "ReLU", + "SwiGLU", + "LLaDABlock", + "LLaDASequentialBlock", + "LLaDAModel", + "LLaDAOutput", + "LLaDAGenerateOutput", +] + + +log = logging.getLogger(__name__) + + +class ModuleType(StrEnum): + in_module = "in" + out_module = "out" + emb = "emb" + final_out = "final_out" + + +def init_weights( + config: ModelConfig, + module: Union[nn.Linear, nn.Embedding], + d: Optional[int] = None, + layer_id: Optional[int] = None, + std_factor: float = 1.0, + type_of_module: Optional[ModuleType] = None, +) -> None: + """ + Initialize weights of a linear or embedding module. + + :param config: The model config. + :param module: The linear or embedding submodule to initialize. + :param d: The effective input dimensionality of the weights. This could be smaller than the actual dimensions + for fused layers. + :param layer_id: When set, the standard deviation for the "mitchell" method will be adjusted by + ``1 / sqrt(2 * (layer_id + 1))``. + """ + d = d if d is not None else config.d_model + if config.init_fn == InitFnType.normal: + std = config.init_std * std_factor + if config.init_cutoff_factor is not None: + cutoff_value = config.init_cutoff_factor * std + nn.init.trunc_normal_(module.weight, mean=0.0, std=std, a=-cutoff_value, b=cutoff_value) + else: + nn.init.normal_(module.weight, mean=0.0, std=std) + elif config.init_fn == InitFnType.mitchell: + std = std_factor / math.sqrt(d) + if layer_id is not None: + std = std / math.sqrt(2 * (layer_id + 1)) + nn.init.trunc_normal_(module.weight, mean=0.0, std=std, a=-3 * std, b=3 * std) + elif config.init_fn == InitFnType.kaiming_normal: + nn.init.kaiming_normal_(module.weight, nonlinearity="relu") + elif config.init_fn == InitFnType.fan_in: + std = std_factor / math.sqrt(d) + nn.init.normal_(module.weight, mean=0.0, std=std) + elif config.init_fn == InitFnType.full_megatron: + if type_of_module is None: + raise RuntimeError(f"When using the {InitFnType.full_megatron} init, every module must have a type.") + + cutoff_factor = config.init_cutoff_factor + if cutoff_factor is None: + cutoff_factor = 3 + + if type_of_module == ModuleType.in_module: + # for att_proj (same as QKV), ff_proj + std = config.init_std + elif type_of_module == ModuleType.out_module: + # for attn_out, ff_out + std = config.init_std / math.sqrt(2.0 * config.n_layers) + elif type_of_module == ModuleType.emb: + # positional embeddings (wpe) + # token embeddings (wte) + std = config.init_std + elif type_of_module == ModuleType.final_out: + # final output (ff_out) + std = config.d_model**-0.5 + else: + raise RuntimeError(f"Unknown module type '{type_of_module}'") + nn.init.trunc_normal_( + module.weight, + mean=0.0, + std=std, + a=-cutoff_factor * std, + b=cutoff_factor * std, + ) + else: + raise NotImplementedError(config.init_fn) + + if isinstance(module, nn.Linear): + if module.bias is not None: + nn.init.zeros_(module.bias) + + if config.init_fn == InitFnType.normal and getattr(module, "_is_residual", False): + with torch.no_grad(): + module.weight.div_(math.sqrt(2 * config.n_layers)) + + +def ensure_finite_(x: torch.Tensor, check_neg_inf: bool = True, check_pos_inf: bool = False): + """ + Modify ``x`` in place to replace ``float("-inf")`` with the minimum value of the dtype when ``check_neg_inf`` + is ``True`` and to replace ``float("inf")`` with the maximum value of the dtype when ``check_pos_inf`` is ``True``. + """ + if check_neg_inf: + x.masked_fill_(x == float("-inf"), torch.finfo(x.dtype).min) + if check_pos_inf: + x.masked_fill_(x == float("inf"), torch.finfo(x.dtype).max) + + +def activation_checkpoint_function(cfg: ModelConfig): + preserve_rng_state = ( + (cfg.attention_dropout == 0.0) and (cfg.embedding_dropout == 0.0) and (cfg.residual_dropout == 0.0) + ) + from torch.utils.checkpoint import checkpoint + + return partial( + checkpoint, + preserve_rng_state=preserve_rng_state, + use_reentrant=False, + ) + + +class BufferCache(dict, MutableMapping[str, torch.Tensor]): + """ + Cache for attention biases and other things that would normally be stored as buffers. + We avoid using buffers because we've run into various issues doing so with FSDP. + In general it appears the way FSDP handles buffers is not well-defined. + It doesn't shard them but apparently it does synchronize them across processes, which we want to avoid + since (A) it isn't necessary, and (B) we sometimes have `-inf` in these biases which might get turned into + NaNs when they're synchronized due to casting or some other issue. + """ + + +def _non_meta_init_device(config: ModelConfig) -> torch.device: + if config.init_device is not None and config.init_device != "meta": + return torch.device(config.init_device) + else: + return torch.device("cuda" if torch.cuda.is_available() else "cpu") + + +class Dropout(nn.Dropout): + def forward(self, input: torch.Tensor) -> torch.Tensor: + if self.p == 0.0: + return input + else: + return F.dropout(input, self.p, self.training, self.inplace) + + +class LayerNormBase(nn.Module): + def __init__( + self, + config: ModelConfig, + *, + size: Optional[int] = None, + elementwise_affine: Optional[bool] = True, + eps: float = 1e-05, + ): + super().__init__() + self.config = config + self.eps = eps + self.normalized_shape = (size or config.d_model,) + if elementwise_affine or (elementwise_affine is None and self.config.layer_norm_with_affine): + self.weight = nn.Parameter(torch.ones(self.normalized_shape, device=config.init_device)) + use_bias = self.config.bias_for_layer_norm + if use_bias is None: + use_bias = self.config.include_bias + if use_bias: + self.bias = nn.Parameter(torch.zeros(self.normalized_shape, device=config.init_device)) + else: + self.register_parameter("bias", None) + else: + self.register_parameter("bias", None) + self.register_parameter("weight", None) + + @abstractmethod + def forward(self, x: torch.Tensor) -> torch.Tensor: + raise NotImplementedError + + @classmethod + def build(cls, config: ModelConfig, size: Optional[int] = None, **kwargs) -> LayerNormBase: + if config.layer_norm_type == LayerNormType.default: + return LayerNorm(config, size=size, low_precision=False, **kwargs) + elif config.layer_norm_type == LayerNormType.low_precision: + return LayerNorm(config, size=size, low_precision=True, **kwargs) + elif config.layer_norm_type == LayerNormType.rms: + return RMSLayerNorm(config, size=size, **kwargs) + elif config.layer_norm_type == LayerNormType.gemma_rms: + return GemmaRMSLayerNorm(config, size=size, **kwargs) + else: + raise NotImplementedError(f"Unknown LayerNorm type: '{config.layer_norm_type}'") + + def _cast_if_autocast_enabled(self, tensor: torch.Tensor, dtype: Optional[torch.dtype] = None) -> torch.Tensor: + # NOTE: `is_autocast_enabled()` only checks for CUDA autocast, so we use the separate function + # `is_autocast_cpu_enabled()` for CPU autocast. + # See https://github.com/pytorch/pytorch/issues/110966. + if tensor.device.type == "cuda" and torch.is_autocast_enabled(): + return tensor.to(dtype=dtype if dtype is not None else torch.get_autocast_gpu_dtype()) + elif tensor.device.type == "cpu" and torch.is_autocast_cpu_enabled(): + return tensor.to(dtype=dtype if dtype is not None else torch.get_autocast_cpu_dtype()) + else: + return tensor + + def reset_parameters(self): + if self.weight is not None: + torch.nn.init.ones_(self.weight) # type: ignore + if self.bias is not None: + torch.nn.init.zeros_(self.bias) # type: ignore + + +class LayerNorm(LayerNormBase): + """ + The default :class:`LayerNorm` implementation which can optionally run in low precision. + """ + + def __init__( + self, + config: ModelConfig, + size: Optional[int] = None, + low_precision: bool = False, + elementwise_affine: Optional[bool] = None, + eps: float = 1e-05, + ): + super().__init__(config, size=size, elementwise_affine=elementwise_affine, eps=eps) + self.low_precision = low_precision + + def forward(self, x: torch.Tensor) -> torch.Tensor: + if self.low_precision: + module_device = x.device + downcast_x = self._cast_if_autocast_enabled(x) + downcast_weight = ( + self._cast_if_autocast_enabled(self.weight) if self.weight is not None else self.weight + ) + downcast_bias = self._cast_if_autocast_enabled(self.bias) if self.bias is not None else self.bias + with torch.autocast(enabled=False, device_type=module_device.type): + return F.layer_norm( + downcast_x, self.normalized_shape, weight=downcast_weight, bias=downcast_bias, eps=self.eps + ) + else: + return F.layer_norm(x, self.normalized_shape, weight=self.weight, bias=self.bias, eps=self.eps) + + +class RMSLayerNorm(LayerNormBase): + """ + RMS layer norm, a simplified :class:`LayerNorm` implementation + """ + + def __init__( + self, + config: ModelConfig, + size: Optional[int] = None, + elementwise_affine: Optional[bool] = None, + eps: float = 1e-5, + ): + super().__init__(config, size=size, elementwise_affine=elementwise_affine, eps=config.rms_norm_eps) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + # with torch.autocast(enabled=False, device_type=x.device.type): + og_dtype = x.dtype + x = x.to(torch.float32) + # print(x.dtype,x.shape) + variance = x*x + # print(variance) + variance = variance.mean(dim=-1,keepdim=True) + x = x * torch.rsqrt(variance + self.eps) + x = x.to(og_dtype) + + if self.weight is not None: + if self.bias is not None: + return self.weight * x + self.bias + else: + return self.weight * x + else: + return x + + +class GemmaRMSLayerNorm(LayerNormBase): + """ + Gemma RMS layer norm, a simplified :class:`LayerNorm` implementation + """ + + def __init__( + self, + config: ModelConfig, + size: Optional[int] = None, + elementwise_affine: Optional[bool] = None, + eps: float = 1e-5, + ): + super().__init__(config, size=size, elementwise_affine=elementwise_affine, eps=config.rms_norm_eps) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + with torch.autocast(enabled=False, device_type=x.device.type): + og_dtype = x.dtype + x = x.to(torch.float32) + variance = x.pow(2).mean(-1, keepdim=True) + x = x * torch.rsqrt(variance + self.eps) + x = x.to(og_dtype) + + if self.weight is not None: + if self.bias is not None: + return x * (1 + self.weight) + self.bias + else: + return x * (1 + self.weight) + else: + return x + + +class RotaryEmbedding(nn.Module): + """ + [Rotary positional embeddings (RoPE)](https://arxiv.org/abs/2104.09864). + """ + + def __init__(self, config: ModelConfig, cache: BufferCache): + super().__init__() + self.config = config + self.__cache = cache + # Warm up cache. + self.rope_theta = config.rope_theta + self.get_rotary_embedding(config.max_sequence_length, _non_meta_init_device(config)) + + def get_rotary_embedding(self, seq_len: int, device: torch.device) -> Tuple[torch.Tensor, torch.Tensor]: + if ( + (pos_sin := self.__cache.get("rope_pos_sin")) is not None + and (pos_cos := self.__cache.get("rope_pos_cos")) is not None + and pos_sin.shape[-2] >= seq_len + and pos_cos.shape[-2] >= seq_len + ): + if pos_sin.device != device: + pos_sin = pos_sin.to(device) + self.__cache["rope_pos_sin"] = pos_sin + if pos_cos.device != device: + pos_cos = pos_cos.to(device) + self.__cache["rope_pos_cos"] = pos_cos + return pos_sin[:, :, :seq_len, :], pos_cos[:, :, :seq_len, :] + + with torch.autocast(device.type, enabled=False): + dim = self.config.d_model // self.config.n_heads + inv_freq = 1.0 / (self.rope_theta ** (torch.arange(0, dim, 2, device=device, dtype=torch.float) / dim)) + seq = torch.arange(seq_len, device=device, dtype=torch.float) + freqs = einsum("i , j -> i j", seq, inv_freq) + positions = torch.cat((freqs, freqs), dim=-1) + pos_sin, pos_cos = positions.sin()[None, None, :, :], positions.cos()[None, None, :, :] + self.__cache["rope_pos_sin"] = pos_sin + self.__cache["rope_pos_cos"] = pos_cos + return pos_sin, pos_cos + + def rotate_half(self, x: torch.Tensor) -> torch.Tensor: + B, nh, T, hs = x.size() + x = x.view(B, nh, T, 2, hs // 2) + x1, x2 = x.unbind(dim=-2) + return torch.cat((-x2, x1), dim=-1) + + def apply_rotary_pos_emb(self, pos_sin: torch.Tensor, pos_cos: torch.Tensor, t: torch.Tensor) -> torch.Tensor: + return ((t * pos_cos) + (self.rotate_half(t) * pos_sin)).to(t.dtype) + + def forward(self, q: torch.Tensor, k: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]: + if self.config.rope_full_precision: + q_, k_ = q.float(), k.float() + else: + q_, k_ = q, k + + with torch.autocast(q.device.type, enabled=False): + query_len, key_len = q_.shape[-2], k_.shape[-2] # could be different if layer_past not None + pos_sin, pos_cos = self.get_rotary_embedding(key_len, q_.device) + pos_sin = pos_sin.type_as(q_) + pos_cos = pos_cos.type_as(q_) + q_ = self.apply_rotary_pos_emb( + pos_sin[:, :, key_len - query_len : key_len, :], + pos_cos[:, :, key_len - query_len : key_len, :], + q_, + ) + k_ = self.apply_rotary_pos_emb(pos_sin, pos_cos, k_) + return q_.type_as(q), k_.type_as(k) + + +class Activation(nn.Module): + def __init__(self, config: ModelConfig): + super().__init__() + self.config = config + + @abstractmethod + def forward(self, x: torch.Tensor) -> torch.Tensor: + raise NotImplementedError + + @property + @abstractmethod + def output_multiplier(self) -> float: + raise NotImplementedError + + @classmethod + def build(cls, config: ModelConfig) -> Activation: + if config.activation_type == ActivationType.gelu: + return cast(Activation, GELU(approximate="none")) + elif config.activation_type == ActivationType.relu: + return cast(Activation, ReLU(inplace=False)) + elif config.activation_type == ActivationType.silu: + return cast(Activation, SiLU(inplace=False)) + elif config.activation_type == ActivationType.swiglu: + return SwiGLU(config) + else: + raise NotImplementedError(f"Unknown activation: '{config.activation_type}'") + + +class GELU(nn.GELU): + @property + def output_multiplier(self) -> float: + return 1.0 + + +class ReLU(nn.ReLU): + @property + def output_multiplier(self) -> float: + return 1.0 + +class SiLU(nn.SiLU): + @property + def output_multiplier(self) -> float: + return 1.0 + +class SwiGLU(Activation): + def forward(self, x: torch.Tensor) -> torch.Tensor: + x, gate = x.chunk(2, dim=-1) + return F.silu(gate) * x + + @property + def output_multiplier(self) -> float: + return 0.5 + + +def causal_attention_bias(seq_len: int, device: torch.device) -> torch.FloatTensor: + att_bias = torch.triu( + torch.ones(seq_len, seq_len, device=device, dtype=torch.float), + diagonal=1, + ) + att_bias.masked_fill_(att_bias == 1, torch.finfo(att_bias.dtype).min) + return att_bias.view(1, 1, seq_len, seq_len) # type: ignore + + +def get_causal_attention_bias(cache: BufferCache, seq_len: int, device: torch.device) -> torch.Tensor: + if (causal_bias := cache.get("causal_attention_bias")) is not None and causal_bias.shape[-1] >= seq_len: + if causal_bias.device != device: + causal_bias = causal_bias.to(device) + cache["causal_attention_bias"] = causal_bias + return causal_bias + with torch.autocast(device.type, enabled=False): + causal_bias = causal_attention_bias(seq_len, device) + cache["causal_attention_bias"] = causal_bias + return causal_bias + + +def alibi_attention_bias(seq_len: int, config: ModelConfig, device: torch.device) -> torch.FloatTensor: + alibi_bias = torch.arange(1 - seq_len, 1, dtype=torch.float, device=device).view(1, 1, 1, seq_len) + + # shape: (1, 1, seq_len, seq_len) + alibi_bias = alibi_bias - torch.arange(1 - seq_len, 1, dtype=torch.float, device=device).view(1, 1, seq_len, 1) + alibi_bias.abs_().mul_(-1) + + # shape: (n_heads,) + m = torch.arange(1, config.n_heads + 1, dtype=torch.float, device=device) + m.mul_(config.alibi_bias_max / config.n_heads) + + # shape: (1, n_heads, seq_len, seq_len) + return alibi_bias * (1.0 / (2 ** m.view(1, config.n_heads, 1, 1))) # type: ignore + + +class LLaDABlock(nn.Module): + """ + A base class for transformer block implementations. + """ + + def __init__(self, layer_id: int, config: ModelConfig, cache: BufferCache): + super().__init__() + self.layer_id = layer_id + self.config = config + self.hidden_size = ( + config.mlp_hidden_size if config.mlp_hidden_size is not None else config.mlp_ratio * config.d_model + ) + self.__cache = cache + assert config.d_model % config.n_heads == 0 + + self._activation_checkpoint_fn = None + + # Dropout. + self.dropout = Dropout(config.residual_dropout) + + # Layer norms. + self.k_norm: Optional[LayerNormBase] = None + self.q_norm: Optional[LayerNormBase] = None + if config.attention_layer_norm: + self.k_norm = LayerNormBase.build( + config, + size=(config.d_model // config.n_heads) * config.effective_n_kv_heads, + elementwise_affine=config.attention_layer_norm_with_affine, + ) + self.q_norm = LayerNormBase.build(config, elementwise_affine=config.attention_layer_norm_with_affine) + + # Activation function. + self.act = Activation.build(config) + assert (self.act.output_multiplier * self.hidden_size) % 1 == 0 + + # Attention output projection. + self.attn_out = nn.Linear( + config.d_model, config.d_model, bias=config.include_bias, device=config.init_device + ) + + # Feed-forward output projection. + self.ff_out = nn.Linear( + int(self.act.output_multiplier * self.hidden_size), + config.d_model, + bias=config.include_bias, + device=config.init_device, + ) + self.ff_out._is_residual = True # type: ignore + + # Rotary embeddings. + if self.config.rope: + self.rotary_emb = RotaryEmbedding(config, self.__cache) + + self.flash_attn_func = None + if config.flash_attention: + try: + from flash_attn import flash_attn_func # type: ignore + + self.flash_attn_func = flash_attn_func + except ModuleNotFoundError: + pass + + def reset_parameters(self): + if self.k_norm is not None: + self.k_norm.reset_parameters() + if self.q_norm is not None: + self.q_norm.reset_parameters() + init_weights( + self.config, + self.attn_out, + d=self.config.d_model, + layer_id=self.layer_id, + type_of_module=ModuleType.out_module, + ) + init_weights( + self.config, + self.ff_out, + d=self.ff_out.in_features, + layer_id=self.layer_id, + type_of_module=ModuleType.out_module, + ) + + def set_activation_checkpointing(self, strategy: Optional[ActivationCheckpointingStrategy]): + if strategy == ActivationCheckpointingStrategy.fine_grained: + self._activation_checkpoint_fn = activation_checkpoint_function(self.config) + else: + self._activation_checkpoint_fn = None + + @classmethod + def _cast_attn_bias(cls, bias: torch.Tensor, input_dtype: torch.dtype) -> torch.Tensor: + target_dtype = input_dtype + # NOTE: `is_autocast_enabled()` only checks for CUDA autocast, so we use the separate function + # `is_autocast_cpu_enabled()` for CPU autocast. + # See https://github.com/pytorch/pytorch/issues/110966. + if bias.device.type == "cuda" and torch.is_autocast_enabled(): + target_dtype = torch.get_autocast_gpu_dtype() + elif bias.device.type == "cpu" and torch.is_autocast_cpu_enabled(): + target_dtype = torch.get_autocast_cpu_dtype() + if bias.dtype != target_dtype: + bias = bias.to(target_dtype) + ensure_finite_(bias, check_neg_inf=True, check_pos_inf=False) + return bias + + def _scaled_dot_product_attention( + self, + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + attn_mask: Optional[torch.Tensor] = None, + dropout_p: float = 0.0, + is_causal: bool = False, + ) -> torch.Tensor: + """ + Computes scaled dot product attention on query, key and value tensors, using an optional + attention mask if passed, and applying dropout if a probability greater than 0.0 is specified. + """ + if self.flash_attn_func is not None and attn_mask is None: + r = self.flash_attn_func( + q.transpose(1, 2), k.transpose(1, 2), v.transpose(1, 2), dropout_p=dropout_p, causal=False + ) + return r.transpose(1, 2) + else: + # torch's sdpa doesn't support GQA, so we're doing this + assert k.size(1) == v.size(1) + num_kv_heads = k.size(1) + num_q_heads = q.size(1) + if num_q_heads != num_kv_heads: + assert num_q_heads % num_kv_heads == 0 + k = k.repeat_interleave(num_q_heads // num_kv_heads, dim=1, output_size=num_q_heads) + v = v.repeat_interleave(num_q_heads // num_kv_heads, dim=1, output_size=num_q_heads) + # Modify: MDM set causal to False, and with no attn_mask. + return F.scaled_dot_product_attention( + q, + k, + v, + attn_mask=attn_mask, + dropout_p=dropout_p, + is_causal=False, + ) + + def attention( + self, + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + attention_bias: Optional[torch.Tensor] = None, + layer_past: Optional[Tuple[torch.Tensor, torch.Tensor]] = None, + use_cache: bool = False, + ) -> Tuple[torch.Tensor, Optional[Tuple[torch.Tensor, torch.Tensor]]]: + B, T, C = q.size() # batch size, sequence length, d_model + dtype = k.dtype + + # Optionally apply layer norm to keys and queries. + if self.q_norm is not None and self.k_norm is not None: + q = self.q_norm(q).to(dtype=dtype) + k = self.k_norm(k).to(dtype=dtype) + + # Move head forward to be next to the batch dim. + # shape: (B, nh, T, hs) + q = q.view(B, T, self.config.n_heads, C // self.config.n_heads).transpose(1, 2) + # shape: (B, n_kv_h, T, hs) + k = k.view(B, T, self.config.effective_n_kv_heads, C // self.config.n_heads).transpose(1, 2) + # shape: (B, n_kv_h, T, hs) + v = v.view(B, T, self.config.effective_n_kv_heads, C // self.config.n_heads).transpose(1, 2) + + if layer_past is not None: + past_key, past_value = layer_past + k = torch.cat((past_key, k), dim=-2) + v = torch.cat((past_value, v), dim=-2) + + present = (k, v) if use_cache else None + query_len, key_len = q.shape[-2], k.shape[-2] # could be different if layer_past not None + + if self.config.rope: + # Apply rotary embeddings. + q, k = self.rotary_emb(q, k) + + # if attention_bias is not None: + # # Resize and cast attention bias. + # # The current dtype of the attention bias might not match the dtype that the SDP attn function will + # # run in if AMP is enabled, and this can be a problem if some tokens are masked out due to padding + # # as down-casting the attention bias to the autocast precision will result in -infs, which will + # # cause the SDP attn function to produce NaNs. + # attention_bias = self._cast_attn_bias( + # attention_bias[:, :, key_len - query_len : key_len, :key_len], dtype + # ) + + # Get the attention scores. + # shape: (B, nh, T, hs) + att = self._scaled_dot_product_attention( + q, + k, + v, + attn_mask=attention_bias, + dropout_p=0.0 if not self.training else self.config.attention_dropout, + is_causal=False, + ) + + # Re-assemble all head outputs side-by-side. + att = att.transpose(1, 2).contiguous().view(B, T, C) + + # Apply output projection. + return self.attn_out(att), present + + @abstractmethod + def forward( + self, + x: torch.Tensor, + attention_bias: Optional[torch.FloatTensor] = None, + layer_past: Optional[Tuple[torch.Tensor, torch.Tensor]] = None, + use_cache: bool = False, + ) -> Tuple[torch.Tensor, Optional[Tuple[torch.Tensor, torch.Tensor]]]: + raise NotImplementedError + + @classmethod + def build(cls, layer_id: int, config: ModelConfig, cache: BufferCache) -> LLaDABlock: + if config.block_type == BlockType.sequential: + return LLaDASequentialBlock(layer_id, config, cache) + elif config.block_type == BlockType.llama: + return LLaDALlamaBlock(layer_id, config, cache) + else: + raise NotImplementedError(f"Unknown block type: '{config.block_type}'") + + +class LLaDASequentialBlock(LLaDABlock): + """ + This is a typical transformer block where the output is computed as ``MLP(LN(x + Attention(LN(x))))`` + (plus another skip connection). + """ + + def __init__(self, layer_id: int, config: ModelConfig, cache: BufferCache): + super().__init__(layer_id, config, cache) + # Layer norms. + self.attn_norm = LayerNorm.build(config) + self.ff_norm = LayerNorm.build(config) + # Attention input projection. Projects x -> (q, k, v) + head_dim = config.d_model // config.n_heads + self.fused_dims = ( + config.d_model, + config.effective_n_kv_heads * head_dim, + config.effective_n_kv_heads * head_dim, + ) + self.att_proj = nn.Linear( + config.d_model, sum(self.fused_dims), bias=config.include_bias | config.include_qkv_bias, device=config.init_device + ) + # Feed-forward input projection. + self.ff_proj = nn.Linear( + config.d_model, self.hidden_size, bias=config.include_bias, device=config.init_device + ) + + def reset_parameters(self): + super().reset_parameters() + self.attn_norm.reset_parameters() + self.ff_norm.reset_parameters() + # NOTE: the standard deviation for these weights does not depend on the layer. + init_weights( + self.config, self.att_proj, d=self.config.d_model, layer_id=None, type_of_module=ModuleType.in_module + ) + init_weights( + self.config, self.ff_proj, d=self.config.d_model, layer_id=None, type_of_module=ModuleType.in_module + ) + + def forward( + self, + x: torch.Tensor, + attention_bias: Optional[torch.Tensor] = None, + layer_past: Optional[Tuple[torch.Tensor, torch.Tensor]] = None, + use_cache: bool = False, + ) -> Tuple[torch.Tensor, Optional[Tuple[torch.Tensor, torch.Tensor]]]: + # Get query, key, value projections. + # shape: + # - for regular attn q, k, v: (batch_size, seq_len, d_model) + # - for multi-query attn q: (batch_size, seq_len, d_model) + # k, v: (batch_size, seq_len, d_model // n_heads) + # - for group query attn q: (batch_size, seq_len, d_model) + # k, v: (batch_size, seq_len, d_model // n_kv_heads) + if self._activation_checkpoint_fn is not None: + q, k, v = self.att_proj(self._activation_checkpoint_fn(self.attn_norm, x)).split( + self.fused_dims, dim=-1 + ) + else: + q, k, v = self.att_proj(self.attn_norm(x)).split(self.fused_dims, dim=-1) + + # Get attention scores. + if self._activation_checkpoint_fn is not None: + att, cache = self._activation_checkpoint_fn( # type: ignore + self.attention, q, k, v, attention_bias, layer_past=layer_past, use_cache=use_cache + ) + else: + att, cache = self.attention(q, k, v, attention_bias, layer_past=layer_past, use_cache=use_cache) + + # Add attention scores. + # shape: (B, T, C) + x = x + self.dropout(att) + + # Add feed-forward projection. + # shape: (batch_size, seq_len, d_model) + og_x = x + if self._activation_checkpoint_fn is not None: + x = self._activation_checkpoint_fn(self.ff_norm, x) # type: ignore + else: + x = self.ff_norm(x) + x = self.ff_proj(x) + if self._activation_checkpoint_fn is not None: + x = self._activation_checkpoint_fn(self.act, x) # type: ignore + else: + x = self.act(x) + x = self.ff_out(x) + x = self.dropout(x) + x = og_x + x + + return x, cache + + +class LLaDALlamaBlock(LLaDABlock): + """ + This is a transformer block where the output is computed as ``MLP(LN(x + Attention(LN(x))))`` + (plus another skip connection). This block is similar to `LLaDASequentialBlock` + but some operations have slightly different implementations to imitate the + behavior of Llama. + """ + + def __init__(self, layer_id: int, config: ModelConfig, cache: BufferCache): + super().__init__(layer_id, config, cache) + # Layer norms. + self.attn_norm = LayerNorm.build(config) + self.ff_norm = LayerNorm.build(config) + self.__cache = cache + + # Attention input projection. Projects x -> (q, k, v) + head_dim = config.d_model // config.n_heads + q_proj_out_dim = config.d_model + k_proj_out_dim = config.effective_n_kv_heads * head_dim + v_proj_out_dim = config.effective_n_kv_heads * head_dim + self.q_proj = nn.Linear( + config.d_model, q_proj_out_dim, bias=config.include_bias | config.include_qkv_bias, device=config.init_device + ) + self.k_proj = nn.Linear( + config.d_model, k_proj_out_dim, bias=config.include_bias | config.include_qkv_bias, device=config.init_device + ) + self.v_proj = nn.Linear( + config.d_model, v_proj_out_dim, bias=config.include_bias | config.include_qkv_bias, device=config.init_device + ) + + # Feed-forward input projection. + self.ff_proj = nn.Linear( + config.d_model, self.hidden_size, bias=config.include_bias, device=config.init_device + ) + # new add + self.up_proj = nn.Linear( + config.d_model, self.hidden_size, bias=config.include_bias, device=config.init_device + ) + + def reset_parameters(self): + super().reset_parameters() + self.attn_norm.reset_parameters() + self.ff_norm.reset_parameters() + # NOTE: the standard deviation for these weights does not depend on the layer. + init_weights(self.config, self.q_proj, d=self.config.d_model, layer_id=None) + init_weights(self.config, self.k_proj, d=self.config.d_model, layer_id=None) + init_weights(self.config, self.v_proj, d=self.config.d_model, layer_id=None) + init_weights(self.config, self.ff_proj, d=self.config.d_model, layer_id=None) + init_weights(self.config, self.up_proj, d=self.config.d_model, layer_id=None) # new add + + def forward( + self, + x: torch.Tensor, + attention_bias: Optional[torch.Tensor] = None, + layer_past: Optional[Tuple[torch.Tensor, torch.Tensor]] = None, + use_cache: bool = False, + ) -> Tuple[torch.Tensor, Optional[Tuple[torch.Tensor, torch.Tensor]]]: + # Get query, key, value projections. + # shape: + # - for regular attn q, k, v: (batch_size, seq_len, d_model) + # - for multi-query attn q: (batch_size, seq_len, d_model) + # k, v: (batch_size, seq_len, d_model // n_heads) + # - for group query attn q: (batch_size, seq_len, d_model) + # k, v: (batch_size, seq_len, d_model // n_kv_heads) + # print(x) + x_normed = self.attn_norm(x) + q = self.q_proj(x_normed) + k = self.k_proj(x_normed) + v = self.v_proj(x_normed) + + # Get attention scores. + if self._activation_checkpoint_fn is not None: + att, cache = self._activation_checkpoint_fn( # type: ignore + self.attention, q, k, v, attention_bias, layer_past=layer_past, use_cache=use_cache + ) + else: + att, cache = self.attention(q, k, v, attention_bias, layer_past=layer_past, use_cache=use_cache) + + # Add attention scores. + # shape: (B, T, C) + x = x + self.dropout(att) + + # Add feed-forward projection. + # shape: (batch_size, seq_len, d_model) + og_x = x + if self._activation_checkpoint_fn is not None: + x = self._activation_checkpoint_fn(self.ff_norm, x) # type: ignore + else: + x = self.ff_norm(x) + x, x_up = self.ff_proj(x), self.up_proj(x) # new add + if self._activation_checkpoint_fn is not None: + x = self._activation_checkpoint_fn(self.act, x) # type: ignore + else: + x = self.act(x) + x = x * x_up # new add + x = self.ff_out(x) + x = self.dropout(x) + x = og_x + x + + return x, cache + + +class LLaDAOutput(NamedTuple): + logits: torch.FloatTensor + """ + A tensor of shape `(batch_size, seq_len, vocab_size)` representing the log probabilities + for the next token *before* normalization via (log) softmax. + """ + + attn_key_values: Optional[List[Tuple[torch.Tensor, torch.Tensor]]] + """ + Attention keys and values from each block. + """ + + hidden_states: Optional[Tuple[torch.Tensor]] + """ + Hidden states from each block. + """ + + +class LLaDAGenerateOutput(NamedTuple): + token_ids: torch.LongTensor + """ + The generated token IDs, a tensor of shape `(batch_size, beam_size, max_steps)`. + These do *not* include the original input IDs. + """ + + scores: torch.FloatTensor + """ + The scores of the generated sequences, a tensor of shape `(batch_size, beam_size)`. + """ + + +class LLaDABlockGroup(nn.ModuleList): + def __init__(self, config: ModelConfig, layer_offset: int, modules: Optional[Iterable[nn.Module]] = None): + super().__init__(modules) + self.config = config + self.layer_offset = layer_offset + self.activation_checkpointing_strategy: Optional[ActivationCheckpointingStrategy] = None + self._activation_checkpoint_fn = activation_checkpoint_function(self.config) + + def forward( + self, + x: torch.Tensor, + attention_bias: Optional[torch.FloatTensor] = None, + layers_past: Optional[List[Tuple[torch.Tensor, torch.Tensor]]] = None, + use_cache: bool = False, + ) -> Tuple[torch.Tensor, Optional[List[Tuple[torch.Tensor, torch.Tensor]]]]: + attn_key_values: Optional[List[Tuple[torch.Tensor, torch.Tensor]]] = [] if use_cache else None + for block_idx, block in enumerate(self): + layer_past = None if layers_past is None else layers_past[block_idx] + block_idx += self.layer_offset + if ( + (self.activation_checkpointing_strategy == ActivationCheckpointingStrategy.whole_layer) + or ( + self.activation_checkpointing_strategy == ActivationCheckpointingStrategy.one_in_two + and block_idx % 2 == 0 + ) + or ( + self.activation_checkpointing_strategy == ActivationCheckpointingStrategy.one_in_three + and block_idx % 3 == 0 + ) + or ( + self.activation_checkpointing_strategy == ActivationCheckpointingStrategy.one_in_four + and block_idx % 4 == 0 + ) + ): + # shape: (batch_size, seq_len, d_model) + x, cache = self._activation_checkpoint_fn( # type: ignore + block, x, attention_bias=attention_bias, layer_past=layer_past, use_cache=use_cache + ) + else: + # shape: (batch_size, seq_len, d_model) + x, cache = block(x, attention_bias=attention_bias, layer_past=layer_past, use_cache=use_cache) + if attn_key_values is not None: + assert cache is not None + attn_key_values.append(cache) + return x, attn_key_values + + def reset_parameters(self): + for block in self: + block.reset_parameters() + + def set_activation_checkpointing(self, strategy: Optional[ActivationCheckpointingStrategy]): + self.activation_checkpointing_strategy = strategy + for block in self: + block.set_activation_checkpointing(strategy) + + +class LLaDAModel(nn.Module): + def __init__(self, config: ModelConfig, init_params: bool = True): + super().__init__() + self.config = config + self.__cache = BufferCache() + + # Validate config. + if self.config.alibi and self.config.flash_attention: + raise Exception("ALiBi is currently not supported with FlashAttention") + + if self.config.alibi and self.config.rope: + raise Exception("ALiBi and RoPE are mutually exclusive") + + if self.config.embedding_size is not None and self.config.embedding_size != self.config.vocab_size: + if self.config.embedding_size < self.config.vocab_size: + raise Exception("embedding size should be at least as big as vocab size") + elif self.config.embedding_size % 128 != 0: + import warnings + + warnings.warn( + "Embedding size is not a multiple of 128! This could hurt throughput performance.", UserWarning + ) + + self.activation_checkpointing_strategy: Optional[ActivationCheckpointingStrategy] = None + self._activation_checkpoint_fn: Callable = activation_checkpoint_function(self.config) + + if not ( + 0 < self.config.block_group_size <= self.config.n_layers + and self.config.n_layers % self.config.block_group_size == 0 + ): + raise Exception("n layers must be divisible by block group size") + + torch.backends.cuda.enable_flash_sdp(True) + torch.backends.cuda.enable_mem_efficient_sdp(False) # this is super slow so make sure torch won't use it + + self.transformer = nn.ModuleDict( + dict( + wte=nn.Embedding( + config.embedding_size or config.vocab_size, config.d_model, device=config.init_device + ), + emb_drop=Dropout(config.embedding_dropout), + ln_f=LayerNorm.build(config), + ) + ) + + blocks = [LLaDABlock.build(i, config, self.__cache) for i in range(config.n_layers)] + if self.config.block_group_size > 1: + block_groups = [ + LLaDABlockGroup(config, i, blocks[i : i + config.block_group_size]) + for i in range(0, config.n_layers, config.block_group_size) + ] + self.transformer.update({"block_groups": nn.ModuleList(block_groups)}) + else: + self.transformer.update({"blocks": nn.ModuleList(blocks)}) + + if not (self.config.alibi or self.config.rope): + self.transformer.update( + {"wpe": nn.Embedding(config.max_sequence_length, config.d_model, device=config.init_device)} + ) + if not config.weight_tying: + self.transformer.update( + { + "ff_out": nn.Linear( + config.d_model, + config.embedding_size or config.vocab_size, + bias=config.include_bias, + device=config.init_device, + ) + } + ) + # When `init_device="meta"` FSDP will call `reset_parameters()` to initialize weights. + if init_params and self.config.init_device != "meta": + self.reset_parameters() + self.__num_fwd_flops: Optional[int] = None + + # Warm up cache. + if self.config.alibi: + get_causal_attention_bias(self.__cache, config.max_sequence_length, _non_meta_init_device(config)) + self.get_alibi_attention_bias(config.max_sequence_length, _non_meta_init_device(config)) + + def set_activation_checkpointing(self, strategy: Optional[ActivationCheckpointingStrategy]): + self.activation_checkpointing_strategy = strategy + if self.config.block_group_size != 1: + for block_group in self.transformer.block_groups: + block_group.set_activation_checkpointing(strategy) + else: + for block in self.transformer.blocks: + block.set_activation_checkpointing(strategy) + + @property + def device(self) -> torch.device: + device: torch.device = self.transformer.wte.weight.device # type: ignore + if device.type == "meta": + return _non_meta_init_device(self.config) + else: + return device + + def reset_parameters(self): + log.info("Initializing model parameters...") + # Top-level embeddings / linear layers. + init_weights( + self.config, + self.transformer.wte, # type: ignore + std_factor=(0.5 * math.sqrt(self.config.d_model)) if self.config.scale_logits else 1.0, + type_of_module=ModuleType.emb, + ) + if hasattr(self.transformer, "wpe"): + init_weights(self.config, self.transformer.wpe, type_of_module=ModuleType.emb) # type: ignore + + # Top-level layer norm. + self.transformer.ln_f.reset_parameters() # type: ignore + + # Output weights. + if hasattr(self.transformer, "ff_out"): + init_weights(self.config, self.transformer.ff_out, type_of_module=ModuleType.final_out) # type: ignore + + # Let the blocks handle themselves. + if self.config.block_group_size == 1: + for block in self.transformer.blocks: + block.reset_parameters() + else: + for block_group in self.transformer.block_groups: + block_group.reset_parameters() + + def get_alibi_attention_bias(self, seq_len: int, device: torch.device) -> torch.Tensor: + if (alibi_bias := self.__cache.get("alibi_attention_bias")) is not None and alibi_bias.shape[ + -1 + ] >= seq_len: + if alibi_bias.device != device: + alibi_bias = alibi_bias.to(device) + self.__cache["alibi_attention_bias"] = alibi_bias + return alibi_bias + with torch.autocast(device.type, enabled=False): + alibi_bias = alibi_attention_bias(seq_len, self.config, device) + self.__cache["alibi_attention_bias"] = alibi_bias + return alibi_bias + + def forward( + self, + input_ids: torch.LongTensor, + input_embeddings: Optional[torch.FloatTensor] = None, + attention_mask: Optional[torch.Tensor] = None, + attention_bias: Optional[torch.Tensor] = None, + past_key_values: Optional[Sequence[Tuple[torch.Tensor, torch.Tensor]]] = None, + use_cache: bool = False, + update_kvcache: bool = False, + last_logits_only: bool = False, + output_hidden_states: Optional[bool] = None, + ) -> LLaDAOutput: + """ + :param input_ids: A tensor of shape `(batch_size, seq_len)`. + :param input_embeddings: A tensor of shape `(batch_size, seq_len, d_model)` with input + embeddings. When provided, it is treated as the output of the input embedding layer. + :param attention_mask: A tensor of shape `(batch_size, seq_len)` that indicates + which input IDs are masked. A `1` value in the mask means that + the corresponding input ID should *not* be ignored. A `0` means + that the corresponding input ID is masked. + + This has the same meaning as the `attention_mask` in HuggingFace's `transformers` + library. + :param attention_bias: A tensor of shape `(batch_size, 1, seq_len, seq_len)`, + `(1, 1, seq_len, seq_len)`, or `(seq_len, seq_len)`. This is used + to introduce causal or other biases. + + If the tensor is a bool or byte tensor, a `True` or `1` at `attention_bias[:, :, i, j]` + indicates that the i-th element in the sequence is allowed to attend to the j-th + element in the sequence. + + If the tensor is a float tensor, it will just be added to the attention + scores before the softmax. + + The default is causal, which corresponds to a lower-diagonal byte matrix of ones. + :param past_key_values: Pre-computed keys and values for each attention block. + Can be used to speed up sequential decoding. The `input_ids` which have + their past given to this model should not be passed as `input_ids` as they have already been computed. + :param use_cache: If `True`, return key and value tensors for each block. + :param last_logits_only: If `True`, only compute the logits for the last token of each sequence. + This can speed up decoding when you only care about the next token. + """ + # Add Basic MDM Model config check + # print(input_ids.dtype) + assert not self.config.alibi, "Alibi length extrapolation is not supported for MDM." + assert self.config.rope, "Rope must be used in Llama-Encoder for MDM." + # assert (past_key_values is None and not use_cache), "The kvcache is not suppotred for MDM." + + output_hidden_states = output_hidden_states if output_hidden_states is not None else False + + if past_key_values: + assert len(past_key_values) == self.config.n_layers + + batch_size, seq_len = input_ids.size() if input_embeddings is None else input_embeddings.size()[:2] + if past_key_values is None: + past_length = 0 + else: + past_length = past_key_values[0][0].size(-2) + + # Get embeddings of input. + # shape: (batch_size, seq_len, d_model) + # print(input_ids.dtype,"wte") + x = self.transformer.wte(input_ids) if input_embeddings is None else input_embeddings # type: ignore + + if self.config.input_emb_norm: + x = x * (self.config.d_model**0.5) + + if not (self.config.alibi or self.config.rope): + # Get positional embeddings. + # shape: (1, seq_len) + pos = torch.arange(past_length, past_length + seq_len, dtype=torch.long, device=x.device).unsqueeze(0) + # shape: (1, seq_len, d_model) + pos_emb = self.transformer.wpe(pos) # type: ignore + x = pos_emb + x + + # Add input + positional embeddings and apply dropout. + # shape: (batch_size, seq_len, d_model) + x = self.transformer.emb_drop(x) # type: ignore + + # Transform the attention mask into what the blocks expect. + if attention_mask is not None and 0.0 in attention_mask: + # shape: (batch_size, 1, 1, seq_len) + attention_mask = attention_mask.to(dtype=torch.float).view(batch_size, -1)[:, None, None, :] + attention_mask = (1.0 - attention_mask) * torch.finfo(attention_mask.dtype).min + else: + attention_mask = None + + # Merge attention mask with attention bias. + if ( + attention_bias is not None + or attention_mask is not None + or self.config.alibi + # NOTE (epwalsh): we need to initialize the attn bias in order for attn to work properly + # with key+value cache. Otherwise `F.scaled_dot_product_attention()` doesn't seem to compute + # scores correctly. + or past_key_values is not None + ): + if attention_bias is None and self.config.alibi: + attention_bias = get_causal_attention_bias( + self.__cache, past_length + seq_len, x.device + ) + self.get_alibi_attention_bias(past_length + seq_len, x.device) + elif attention_bias is None: + attention_bias = get_causal_attention_bias(self.__cache, past_length + seq_len, x.device) + elif attention_bias.dtype in (torch.int8, torch.bool): + attention_bias = attention_bias.to(dtype=torch.float) + attention_bias.masked_fill_(attention_bias == 0.0, torch.finfo(attention_bias.dtype).min) + + # Transform to the right shape and data type. + mask_len = seq_len + if attention_mask is not None: + mask_len = attention_mask.shape[-1] + elif past_key_values is not None: + mask_len = past_key_values[0][0].shape[-2] + seq_len + attention_bias = attention_bias[:, :, :mask_len, :mask_len].to(dtype=torch.float) + + # Add in the masking bias. + if attention_mask is not None: + attention_bias = attention_bias + attention_mask + # Might get -infs after adding attention mask, since dtype.min + dtype.min = -inf. + # `F.scaled_dot_product_attention()` doesn't handle -inf like you'd expect, instead + # it can produce NaNs. + ensure_finite_(attention_bias, check_neg_inf=True, check_pos_inf=False) + + attn_key_values: Optional[List[Tuple[torch.Tensor, torch.Tensor]]] = [] if use_cache else None + + # decoder layers + all_hidden_states = [] + + # Apply blocks one-by-one. + if self.config.block_group_size == 1: + for block_idx, block in enumerate(self.transformer.blocks): + if output_hidden_states: + # add hidden states + all_hidden_states.append(x) + + layer_past = None if past_key_values is None else past_key_values[block_idx] + if ( + (self.activation_checkpointing_strategy == ActivationCheckpointingStrategy.whole_layer) + or ( + self.activation_checkpointing_strategy == ActivationCheckpointingStrategy.one_in_two + and block_idx % 2 == 0 + ) + or ( + self.activation_checkpointing_strategy == ActivationCheckpointingStrategy.one_in_three + and block_idx % 3 == 0 + ) + or ( + self.activation_checkpointing_strategy == ActivationCheckpointingStrategy.one_in_four + and block_idx % 4 == 0 + ) + ): + # shape: (batch_size, seq_len, d_model) + x, cache = self._activation_checkpoint_fn( + block, x, attention_bias=attention_bias, layer_past=layer_past, use_cache=use_cache + ) + else: + # shape: (batch_size, seq_len, d_model) + x, cache = block(x, attention_bias=attention_bias, layer_past=layer_past, use_cache=use_cache) + if attn_key_values is not None: + if update_kvcache == True: + attn_key_values.append(cache) + else: + for group_idx, block_group in enumerate(self.transformer.block_groups): + if output_hidden_states: + # add hidden states + all_hidden_states.append(x) + + layers_past = ( + None + if past_key_values is None + else past_key_values[ + group_idx * self.config.block_group_size : (group_idx + 1) * self.config.block_group_size + ] + ) + x, cache = block_group( + x, attention_bias=attention_bias, layers_past=layers_past, use_cache=use_cache + ) + if attn_key_values is not None: + assert cache is not None + attn_key_values.extend(cache) + + if last_logits_only: + # shape: (batch_size, 1, d_model) + x = x[:, -1, :].unsqueeze(1) + + # Apply final layer norm. + # shape: (batch_size, seq_len or 1, d_model) + x = self.transformer.ln_f(x) # type: ignore + if output_hidden_states: + # add final hidden state post-final-layernorm, following HuggingFace's convention + all_hidden_states.append(x) + + # Get logits. + # shape: (batch_size, seq_len or 1, vocab_size) + if self.config.weight_tying: + logits = F.linear(x, self.transformer.wte.weight, None) # type: ignore + else: + logits = self.transformer.ff_out(x) # type: ignore + if self.config.scale_logits: + logits.mul_(1 / math.sqrt(self.config.d_model)) + if use_cache == True and update_kvcache == False: + attn_key_values=past_key_values + return LLaDAOutput(logits=logits, attn_key_values=attn_key_values, hidden_states=tuple(all_hidden_states) if output_hidden_states else None) # type: ignore[arg-type] + + +def create_model_config_from_pretrained_config(config: LLaDAConfig): + """ + Utility function + """ + + kwargs = {} + for field in fields(ModelConfig): + kwargs[field.name] = getattr(config, field.name) + + model_config = ModelConfig(**kwargs) + return model_config + + +class LLaDAModelLM(PreTrainedModel): + """ + Extremely barebones HF model wrapper. + """ + + config_class = LLaDAConfig + base_model_prefix = "model" + _no_split_modules = ["LLaDABlock", "LLaDASequentialBlock", "LLaDALlamaBlock"] + + def __init__(self, config: LLaDAConfig, model: Optional[LLaDAModel] = None, init_params: bool = False): + super().__init__(config) + + if not model: + model_config = create_model_config_from_pretrained_config(config) + # Initialize model (always on CPU to start with so we don't run out of GPU memory). + model_config.init_device = "cpu" + self.model = LLaDAModel(model_config, init_params=init_params) + else: + self.model = model + + def forward( + self, + input_ids: torch.LongTensor = None, + inputs_embeds: Optional[torch.FloatTensor] = None, + attention_mask: Optional[torch.Tensor] = None, + attention_bias: Optional[torch.Tensor] = None, + past_key_values: Optional[List[torch.FloatTensor]] = None, + labels: Optional[torch.LongTensor] = None, + use_cache: Optional[bool] = None, + output_attentions: Optional[bool] = None, + output_hidden_states: Optional[bool] = None, + return_dict: Optional[bool] = None, + cache_position: Optional[Cache] = None, # This is a hack mitigation of an issue in transformers `4.39.x` + ) -> Union[Tuple, CausalLMOutputWithPast]: + if use_cache is None: + use_cache = self.config.use_cache + + if output_attentions: + raise ValueError("output_attentions is not yet supported in LLaDA") + + return_dict = return_dict if return_dict is not None else self.config.use_return_dict + + # decoder outputs consists of (dec_features, layer_state, dec_hidden, dec_attn) + outputs = self.model.forward( + input_ids=input_ids, + input_embeddings=inputs_embeds, + attention_mask=attention_mask, + attention_bias=attention_bias, + past_key_values=past_key_values, + use_cache=use_cache, + output_hidden_states=output_hidden_states, + ) + + logits = outputs.logits + hidden_states = outputs.hidden_states + + loss = None + if labels is not None: + import warnings + warnings.warn("Note that for LLaDA, you cannot calculate the loss here.", UserWarning) + if not return_dict: + output = (logits,) + outputs[1:] + return (loss,) + output if loss is not None else output + + return CausalLMOutputWithPast( + logits=logits, + past_key_values=outputs.attn_key_values, + hidden_states=hidden_states, + ) + + def can_generate(self) -> bool: + return True + + def prepare_inputs_for_generation( + self, input_ids: torch.LongTensor, past_key_values: Optional[List[Tuple]] = None, **kwargs + ): + if past_key_values: + # This is because we want the model to only process the last generated token. + input_ids = input_ids[:, -1:] + model_inputs = {"input_ids": input_ids, "past_key_values": past_key_values} + + model_inputs.update(kwargs) + model_inputs["use_cache"] = kwargs.pop("use_cache", self.config.use_cache) + return model_inputs + + # TODO: these are required to make the implementation complete. + # def resize_position_embeddings(self, new_num_position_embeddings: int): + # pass + # + # def get_position_embeddings(self) -> Union[nn.Embedding, Tuple[nn.Embedding]]: + # pass + # + # def _reorder_cache(self, past_key_values, beam_idx): + # pass + + def get_input_embeddings(self) -> torch.nn.Module: + return self.model.transformer.wte + + def set_input_embeddings(self, value: torch.nn.Module): + self.model.transformer.wte = value + + def get_output_embeddings(self): + if self.config.weight_tying: + return self.model.transformer.wte + else: + return self.model.transformer.ff_out + + def set_output_embeddings(self, value: torch.nn.Module): + if self.config.weight_tying: + self.model.transformer.wte = value + else: + self.model.transformer.ff_out = value + + def tie_weights(self): + if self.config.weight_tying: + self.model.transformer.ff_out = self.model.transformer.wte + +# Register the model so that it is available for transformer pipelines, auto-loading, etc. +AutoModel.register(LLaDAConfig, LLaDAModelLM) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..b57b9322e86c2a0d6ea33b117d5c89415a01d899 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,11 @@ +transformers==4.49.0 +lm_eval==0.4.8 +accelerate==0.34.2 +antlr4-python3-runtime==4.11 +math_verify +sympy +hf_xet +pydantic==2.10.6 +gradio +omegaconf +deepspeed \ No newline at end of file diff --git a/test.sh b/test.sh new file mode 100644 index 0000000000000000000000000000000000000000..68d8222fcba19a5bc88746843bba6cac1506517a --- /dev/null +++ b/test.sh @@ -0,0 +1,2 @@ +#!/bin/bash +echo hi diff --git a/train.py b/train.py new file mode 100644 index 0000000000000000000000000000000000000000..5e106ce1df58a609dcf41a399775e36a696725fc --- /dev/null +++ b/train.py @@ -0,0 +1,222 @@ +# from peft import PeftModel, PeftConfig, get_peft_model +from datasets import load_dataset +from torch.utils.data import DataLoader +from peft import PeftModel, PeftConfig, get_peft_model +from utils.util import flatten_dict,shift_logits +from utils.data import get_bs17k_dataloader,get_llada_bs17k_dataloader,get_dataloader_by_config +from utils.model import get_model,get_llada,get_model_by_config +from utils.loss import compute_loss,compute_llada_loss,compute_normal_loss,compute_loss_by_config +from utils.generation import sample_tokens +# import dataloader + +import os +import torch +import argparse +import torch.distributed as dist +from omegaconf import OmegaConf +from tqdm import tqdm +from accelerate import Accelerator +from accelerate.utils import ProjectConfiguration +import ipdb +os.environ['TOKENIZERS_PARALLELISM'] = 'false' + +if os.environ.get("DEBUGPY", "0") == "1": + import debugpy + debugpy.listen(("0.0.0.0", 5678)) + print(f"Waiting for VS Code debugger attach (PID {os.getpid()})...", flush=True) + debugpy.wait_for_client() + + +def get_accelerator(config, global_config): + # Select experiment path based on config + if hasattr(global_config, 'paths') and hasattr(global_config.paths, 'experiment'): + root_path = global_config.paths.experiment + else: + root_path = config.root if hasattr(config, 'root') else '/tmp/experiment' + + output_dir = os.path.join(root_path, config.exp_name, config.output_dir) + os.makedirs(output_dir, exist_ok=True) + logging_dir = os.path.join(output_dir, config.logging_dir) + project_config = ProjectConfiguration(project_dir=config.output_dir, logging_dir=logging_dir) + accelerator = Accelerator( + log_with=None if config.report_to == 'no' else config.report_to, + mixed_precision=config.mixed_precision, + project_config=project_config, + gradient_accumulation_steps=config.gradient_accumulation_steps, + ) + + return accelerator, output_dir + +def main(args): + config = OmegaConf.load(args.config) + accelerator, output_dir = get_accelerator(config.train, config) + # Use unified model and data loading functions + denoiser, tokenizer = get_model_by_config(config) + dataloader = get_dataloader_by_config(tokenizer, config.data, config) + + if config.train.decoder_resume_path is not None: + ckpt = torch.load(config.train.decoder_resume_path, map_location='cpu', weights_only=True) + if config.train.skipped_keys: + ckpt = {k: v for k, v in ckpt.items() if k not in config.train.skipped_keys} + m, u = denoiser.load_state_dict(ckpt, strict=False) + if accelerator.is_main_process: + print(f'model ckpt loaded from {config.train.decoder_resume_path}') + + # ckpt = torch.load(config.train.head_resume_path, map_location='cpu', weights_only=True) + # if config.train.skipped_keys: + # ckpt = {k: v for k, v in ckpt.items() if k not in config.train.skipped_keys} + # m, u = denoiser.lm_head.load_state_dict(ckpt, strict=False) + # if accelerator.is_main_process: + # print(f'model ckpt loaded from {config.train.head_resume_path}') + + global_step = config.train.global_step if config.train.global_step is not None else 0 + params_to_learn = list(param for param in denoiser.parameters() if param.requires_grad) + optimizer = torch.optim.AdamW( + params_to_learn, + lr = config.train.lr, + betas = (0.9, 0.95), + weight_decay = 5e-2, + eps = 1e-8, + ) + + denoiser, dataloader, optimizer = accelerator.prepare(denoiser, dataloader, optimizer) + + config.device_count = accelerator.num_processes + if accelerator.is_main_process: + accelerator.init_trackers(config.train.wandb_proj, config=flatten_dict(config)) + + training_done = False + epoch = 0 + progress_bar = tqdm( + total = config.train.num_iters, + initial = global_step, + desc = 'Steps', + disable = not accelerator.is_local_main_process, + ) + + if accelerator.is_main_process: + print(f'Learnable parameters: {sum(p.numel() for p in params_to_learn if p.requires_grad) / 1e9} B') + + while not training_done: + if accelerator.is_main_process: + print(f'Epoch: {epoch}') + for batch in dataloader: + with accelerator.accumulate([denoiser]): + denoiser.train() + ipdb.set_trace() + input_ids = batch['data'] + # print("input_ids",input_ids.dtype) + question_length = batch['question_length'] + + # Use unified loss function selection + losses = compute_loss_by_config( + input_ids, + denoiser, + question_length, + block_size = config.train.block_size, + mask_id = config.denoiser.encoder.mask_id, + enable_shift = config.train.enable_shift, + share_steps = config.train.share_steps, + self_align = config.train.self_align, + feature_align = config.train.feature_align, + self_step = config.train.self_step, + eos_id = tokenizer.eos_token_id, + config = config + ) + + if config.train.share_steps > 1: + loss_tgt = losses['loss'] + # loss_1 = losses['loss_1'] + # loss_2 = losses['loss_2'] + else: + raise NotImplementedError + torch.cuda.empty_cache() + accelerator.backward(loss_tgt) + if accelerator.sync_gradients: + accelerator.clip_grad_norm_(params_to_learn, 1.0) + + optimizer.step() + optimizer.zero_grad() + + if accelerator.sync_gradients: + global_step += 1 + progress_bar.update(1) + logs = dict() + loss_tgt = accelerator.gather(loss_tgt.detach()).mean().item() + logs['loss'] = loss_tgt + # if config.train.share_steps > 1: + # loss_1 = accelerator.gather(loss_1.detach()).mean().item() + # loss_2 = accelerator.gather(loss_2.detach()).mean().item() + # logs['loss_1'] = loss_1 + # logs['loss_2'] = loss_2 + + accelerator.log(logs, step=global_step) + progress_bar.set_postfix(**logs) + + if global_step > 0 and global_step % config.train.eval_every == 0 and accelerator.is_main_process: + denoiser.eval(); + question = 'Henry made two stops during his 60-mile bike trip. He first stopped after 20 miles. His second stop was 15 miles before the end of the trip. How many miles did he travel between his first and second stops?' + # prompt = tokenizer(question)['input_ids'] + # prompt = torch.tensor(prompt).to(accelerator.device).unsqueeze(0) + messages = [ + {"role": "user", "content": question} + ] + prompt = tokenizer.apply_chat_template( + messages, return_tensors="pt", return_dict=True, add_generation_prompt=True + ).input_ids + prompt = prompt.to(accelerator.device) + + mask_id = 151666 + gen_len = 512 - prompt.shape[1] + temperature = 0.2 + top_p = 0.95 + + x_t = torch.cat([prompt, torch.tensor([[mask_id]*gen_len]).to(accelerator.device)], dim=1) + with torch.inference_mode(): + for i in range(gen_len): + mask_index = (x_t == mask_id) + if i % 2 == 0: + z_t = denoiser.module.encoder(x_t, output_hidden_states=True).hidden_states[-1] + hidden_state = denoiser.module.decoder(x_t, z_t) + logits = denoiser.module.encoder.lm_head(hidden_state) + else: + hidden_state = denoiser.module.decoder(x_t, z_t) + logits = denoiser.module.lm_head(hidden_state) + + if config.train.enable_shift: + logits = shift_logits(logits) + + mask_logits = logits[mask_index] + confidence, x0 = sample_tokens(mask_logits, temperature, top_p=top_p, top_k=None, neg_entropy=True) + + number_transfer_tokens = 1 + _, transfer_index = torch.topk(confidence, number_transfer_tokens) + x0_ = torch.zeros_like(x0, device=accelerator.device, dtype=torch.long) + mask_id + x0_[transfer_index] = x0[transfer_index].clone() + x_t[mask_index] = x0_ + + answer = tokenizer.batch_decode(x_t[:, prompt.shape[1]:], skip_special_tokens=True)[0] + print(answer) + + accelerator.wait_for_everyone() + + if global_step > 0 and global_step % config.train.save_every == 0 and accelerator.is_main_process: + denoiser.eval() + decoder_state_dict = accelerator.unwrap_model(denoiser).save_pretrained(os.path.join(output_dir, f"Decoder-{config.train.exp_name}-{global_step // 1000}k")) + # lmhead_state_dict = accelerator.unwrap_model(denoiser).lm_head.state_dict() + # torch.save(lmhead_state_dict, os.path.join(output_dir, f"LMhead-{config.train.exp_name}-{global_step // 1000}k")) + accelerator.wait_for_everyone() + if global_step >= config.train.num_iters: + training_done = True + break + epoch += 1 + accelerator.end_training() + if dist.is_initialized(): + dist.destroy_process_group() + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--config', type=str, default='config/dream.yaml') + args = parser.parse_args() + main(args) \ No newline at end of file diff --git a/train.sh b/train.sh new file mode 100644 index 0000000000000000000000000000000000000000..6323df6c323eb10295739afbc9b3583a653e7a89 --- /dev/null +++ b/train.sh @@ -0,0 +1,62 @@ +#!/bin/bash + +# Exit on any error +set -e + +# Print script start +echo "==========================================" +echo "Starting Adaptive Block Forcing Training" +echo "==========================================" + +# Print system information +echo "System Information:" +echo " Hostname: $(hostname)" +echo " Date: $(date)" +echo " User: $(whoami)" +echo " Working Directory: $(pwd)" +echo " Python Version: $(python --version 2>/dev/null || echo 'Python not found')" +echo "" + +# Activate micromamba environment +# echo "Activating micromamba environment 'abf'..." +# eval "$(micromamba shell hook --shell bash)" +# micromamba activate abf + +# Print environment information +echo "Environment Information:" +echo " Active Environment: $CONDA_DEFAULT_ENV" +echo " Python Path: $(which python)" +echo " Python Version: $(python --version)" +echo " CUDA Available: $(python -c 'import torch; print(torch.cuda.is_available())' 2>/dev/null || echo 'PyTorch not available')" +if python -c 'import torch' 2>/dev/null; then + echo " CUDA Version: $(python -c 'import torch; print(torch.version.cuda)')" + echo " GPU Count: $(python -c 'import torch; print(torch.cuda.device_count())')" +fi +echo "" + +# Set environment variables +export CUDA_VISIBLE_DEVICES=0 +export DEBUGPY=0 +# export CUDA_LAUNCH_BLOCKING=1 + +echo "Starting training with the following configuration:" +echo " CUDA_VISIBLE_DEVICES: $CUDA_VISIBLE_DEVICES" +echo " DEBUGPY: $DEBUGPY" +echo " Config File: config/llada.yaml" +echo " Accelerate Config: config/acc_config" +echo " Number of Processes: 1" +echo " Main Process Port: 29577" +echo "" + +# Launch training +echo "Launching training..." +accelerate launch \ + --config_file config/acc_config \ + --num_processes 1 \ + --main_process_port 29577 \ + train.py --config config/llada.yaml + +echo "" +echo "==========================================" +echo "Training completed!" +echo "==========================================" diff --git a/utils/__init__.py b/utils/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/utils/data.py b/utils/data.py new file mode 100644 index 0000000000000000000000000000000000000000..32b0ecc51e849589836045693a06c121adbaca4d --- /dev/null +++ b/utils/data.py @@ -0,0 +1,313 @@ +from datasets import load_dataset +from torch.utils.data import DataLoader,Dataset +from peft import PeftModel, PeftConfig, get_peft_model +# from modelscope.msdatasets import MsDataset +import torch +import json +import re +def extract_answer(text): + pattern = r"<\|begin_of_solution\|>(.*?)<\|end_of_solution\|>" + match = re.search(pattern, text, re.DOTALL) + + if match: + solution_content = match.group(1).strip() + # print("Extracted content:\n") + # print(solution_content) + return solution_content + else: + # print("No matching content found.") + return None +def collate_fn(batch, tokenizer, max_length): + """ + batch: list of raw text samples (str) + tokenizer: huggingface tokenizer + max_length: maximum length to pad to (int) + """ + encoded_batch = [] + for text in batch: + # Encode text, return dictionary, note no automatic padding + enc = tokenizer(text["text"], add_special_tokens=False, return_tensors="pt") + input_ids = enc["input_ids"].squeeze(0) # (seq_len,) + + # Add eos_token_id + eos_id = tokenizer.eos_token_id + if eos_id is None: + raise ValueError("tokenizer does not have eos_token_id") + + input_ids = torch.cat([input_ids, torch.tensor([eos_id], device=input_ids.device)]) + + # Padding to max_length + pad_id = tokenizer.pad_token_id + if pad_id is None: + raise ValueError("tokenizer does not have pad_token_id") + + seq_len = input_ids.size(0) + if seq_len > max_length: + # Truncate if too long + input_ids = input_ids[:max_length] + else: + # Pad right side if not long enough + pad_len = max_length - seq_len + padding = torch.full((pad_len,), pad_id, device=input_ids.device, dtype=input_ids.dtype) + input_ids = torch.cat([input_ids, padding]) + + encoded_batch.append(input_ids) + + return torch.stack(encoded_batch) + +def prepare_dataloader(data, tokenizer, batch_size, max_length): + dataset = CustomDataset(data) + dataloader = DataLoader( + dataset, + batch_size = batch_size, + collate_fn = lambda x: collate_fn(x, tokenizer, max_length=max_length), + num_workers = 0, + shuffle = True, + pin_memory = True, + ) + + return dataloader + +def read_math(): + math_data = [] + dataset = load_dataset("microsoft/orca-math-word-problems-200k", split="train") + for item in dataset: + math_data.append({"question": item['question'], "answer": item['answer']}) + return math_data + +def read_python(): + python_data = [] + dataset = load_dataset("microsoft/orca-math-word-problems-200k", split="train") + for item in dataset: + python_data.append({"question": item['question'], "answer": item['answer']}) + return python_data + +def read_numinamath(): + math_data = read_math() + python_data = read_python() + return math_data + python_data + +def read_bs(config=None): + data=[] + # Get path from config, use default path if no config + if config and hasattr(config, 'paths') and hasattr(config.paths, 'data') and hasattr(config.paths.data, 'bs'): + dataset_path = config.paths.data.bs + else: + dataset_path = "/data1/xck/dllm_block_wx/data/Lansechen/bs17k_collection_filtered_hard_maxlength600" + + dataset=load_dataset(dataset_path, split="train") + for item in dataset: + data.append({"question": item['question'], "answer": item['qwen7b_answer']}) + return data + +def read_bs_easy(config=None): + data=[] + # Get path from config, use default path if no config + if config and hasattr(config, 'paths') and hasattr(config.paths, 'data') and hasattr(config.paths.data, 'bs_easy'): + dataset_path = config.paths.data.bs_easy + else: + dataset_path = "/data1/xck/dllm_block_wx/data/Lansechen/bs17k_collection_filtered_easy_maxlength600" + + dataset=load_dataset(dataset_path, split="train") + for item in dataset: + data.append({"question": item['question'], "answer": item['qwen7b_answer']}) + return data + +def read_bs_17k(): + data=[] + dataset=load_dataset("/data/wx/dataset/bespokelabs/Bespoke-Stratos-17k",split="train") + for item in dataset: + item=item["conversations"] + data.append({"question": item[0]['value'], "answer": extract_answer(item[1]['value'])}) + return data +class CustomDataset(Dataset): + def __init__(self, data): + self.data = data + + def __len__(self): + return len(self.data) + + def __getitem__(self, idx): + return self.data[idx] +def read_llada(file_path="/home/wx/dllm_block/data/merged_bs17k_easy_hard_llada_collected.jsonl"): + data = [] + with open(file_path, 'r', encoding='utf-8') as file: + for line in file: + try: + json_obj = json.loads(line) + data.append(json_obj) + except json.JSONDecodeError: + print(f'JSONDecodeError: {line}') + return data +def get_bs17k_dataloader(tokenizer, config, max_length=1024): + train_dataset = [] + # Pass global config to data reading functions + global_config = getattr(config, '_parent', config) # Try to get parent config + data_dict=read_bs(global_config)+read_bs_easy(global_config) + for data in data_dict: + question = data['question'] + answer = data['answer'] + + # messages = [ + # {"role": "user", "content": "Janet's ducks lay 16 eggs per day. She eats three for breakfast every morning and bakes muffins for her friends every day with four. She sells the remainder at the farmers' market daily for $2 per fresh duck egg. How much in dollars does she make every day at the farmers' market?"}, + # ] + messages = [ + {"role": "user", "content": question} + ] + question = tokenizer.apply_chat_template( + messages, return_tensors="pt", return_dict=True, add_generation_prompt=True + ).input_ids[0] + + # question = tokenizer(question, return_tensors='pt')['input_ids'][0] + answer = tokenizer(answer, return_tensors='pt')['input_ids'][0] + answer = torch.cat((answer, torch.tensor([tokenizer.eos_token_id])), dim=-1) + + question_length = question.shape[-1] + answer_length = answer.shape[-1] + combined_length = question_length + answer_length + if question_length > max_length-100: + continue + if combined_length > max_length: + padded_data = torch.cat((question, answer), dim=-1) + padded_data = padded_data[:max_length] # Truncate to max_length + else: + padding_length = max_length - combined_length + padding = torch.full((padding_length,), tokenizer.eos_token_id, dtype=question.dtype) + padded_data = torch.cat((question, answer, padding), dim=-1) + + train_dataset.append( + dict( + data = padded_data, + question_length = question_length, + length = combined_length, + ) + ) + + dataset = CustomDataset(train_dataset) + dataloader = DataLoader( + dataset, + batch_size = config.batch_size, + num_workers = 0, + shuffle = True, + pin_memory = True, + ) + + return dataloader + +# def get_gsm8k_dataloader(tokenizer, config, max_length=1024): +# train_dataset = [] +# data_dict = read_numinamath() +# for data in data_dict: +# question = data['question'] +# answer = data['answer'] + +# question = tokenizer(question, return_tensors='pt')['input_ids'][0] +# answer = tokenizer(answer, return_tensors='pt')['input_ids'][0] +# answer = torch.cat((answer, torch.tensor([tokenizer.eos_token_id])), dim=-1) + +# question_length = question.shape[-1] +# answer_length = answer.shape[-1] +# combined_length = question_length + answer_length + +# if combined_length > max_length: +# continue + +# padding_length = max_length - combined_length +# padding = torch.full((padding_length,), tokenizer.eos_token_id, dtype=question.dtype) +# padded_data = torch.cat((question, answer, padding), dim=-1) + +# train_dataset.append( +# dict( +# data = padded_data, +# question_length = question_length, +# length = combined_length, +# ) +# ) + +# dataset = CustomDataset(train_dataset) +# dataloader = DataLoader( +# dataset, +# batch_size = config.batch_size, +# collate_fn = lambda x: collate_fn_pad(x, tokenizer, max_length=max_length), +# num_workers = 0, +# shuffle = True, +# pin_memory = True, +# ) + +# return dataloader +def get_llada_bs17k_dataloader(tokenizer, config, max_length=1024): + train_dataset = [] + # Pass global config to data reading functions + global_config = getattr(config, '_parent', config) # Try to get parent config + data_dict = read_bs(global_config) + python_dict=read_bs_easy(global_config) + data_dict=data_dict+python_dict + print("Data length:",len(data_dict)) + # data_dict = read_llada() + for data in data_dict: + question = data['question'] + answer = data['answer'] + + # messages = [ + # {"role": "user", "content": "Janet's ducks lay 16 eggs per day. She eats three for breakfast every morning and bakes muffins for her friends every day with four. She sells the remainder at the farmers' market daily for $2 per fresh duck egg. How much in dollars does she make every day at the farmers' market?"}, + # ] + messages = [ + {"role": "user", "content": question} + ] + question = tokenizer.apply_chat_template( + messages, return_tensors="pt", return_dict=True, add_generation_prompt=True + ).input_ids[0] + + # question = tokenizer(question, return_tensors='pt')['input_ids'][0] + answer = tokenizer(answer, return_tensors='pt')['input_ids'][0] + answer = torch.cat((answer, torch.tensor([126348])), dim=-1) + + question_length = question.shape[-1] + answer_length = answer.shape[-1] + combined_length = question_length + answer_length + + if combined_length > max_length: + continue + + padding_length = max_length - combined_length + padding = torch.full((padding_length,), tokenizer.eos_token_id, dtype=question.dtype) + padded_data = torch.cat((question, answer, padding), dim=-1) + + train_dataset.append( + dict( + data = padded_data, + question_length = question_length, + length = combined_length, + ) + ) + + dataset = CustomDataset(train_dataset) + dataloader = DataLoader( + dataset, + batch_size = config.batch_size, + num_workers = 0, + shuffle = True, + pin_memory = True, + ) + + return dataloader +if __name__ == "__main__": + text="<|begin_of_thought|>\n\nOkay, let me try to figure out this problem. So, we have this operation defined as a⊗b = a²/b. And we need to compute [(1⊗2)⊗3] - [1⊗(2⊗3)]. Then choose the correct answer from the options given. Alright, let's break it down step by step.\n\nFirst, I need to remember that the operation ⊗ is not associative, right? Because the problem is asking for the difference between two different groupings: (1⊗2)⊗3 and 1⊗(2⊗3). So, the order in which we perform the operations matters here. That's probably why there's a subtraction between them.\n\nLet me start by computing each part separately. Let's tackle the first part: (1⊗2)⊗3.\n\nStarting with the innermost operation, which is 1⊗2. According to the definition, a⊗b = a²/b. So here, a is 1 and b is 2. Plugging those in: 1² / 2 = 1/2. So, 1⊗2 equals 1/2.\n\nNow, we take that result and perform the next operation with 3. So, (1⊗2)⊗3 becomes (1/2)⊗3. Again, using the same definition: a is now 1/2 and b is 3. So, ( (1/2)² ) / 3 = (1/4) / 3 = 1/12. So, (1⊗2)⊗3 equals 1/12.\n\nAlright, that's the first part. Now let's compute the second part: 1⊗(2⊗3). Again, starting with the innermost operation, which is 2⊗3. Applying the definition: a is 2 and b is 3. So, 2² / 3 = 4/3. Therefore, 2⊗3 equals 4/3.\n\nNow, we need to compute 1⊗(4/3). Here, a is 1 and b is 4/3. Using the operation definition: 1² / (4/3) = 1 / (4/3) = 3/4. So, 1⊗(2⊗3) equals 3/4.\n\nNow, the problem asks for the difference between the two results: [(1⊗2)⊗3] - [1⊗(2⊗3)] = (1/12) - (3/4). To subtract these fractions, they need a common denominator. The denominators are 12 and 4, so 12 is the common denominator.\n\nConverting 3/4 to twelfths: 3/4 = 9/12. So, 1/12 - 9/12 = (1 - 9)/12 = -8/12. Simplifying that fraction by dividing numerator and denominator by 4: -8/12 = -2/3.\n\nHmm, looking at the answer choices, option A is -2/3. So, is that the answer? Wait, but let me double-check my calculations to make sure I didn't make a mistake somewhere.\n\nFirst, checking (1⊗2): 1² / 2 = 1/2. Correct. Then, (1/2)⊗3: (1/2)² / 3 = (1/4)/3 = 1/12. That seems right.\n\nNow, for 2⊗3: 2² / 3 = 4/3. Correct. Then, 1⊗(4/3): 1² / (4/3) = 1 / (4/3) = 3/4. Yes, that's correct.\n\nSubtracting 3/4 from 1/12: 1/12 - 3/4. Convert 3/4 to 9/12, so 1/12 - 9/12 = -8/12 = -2/3. Yes, that all checks out. So the answer should be -2/3, which is option A.\n\nWait, but let me think again. The operation is defined for all nonzero numbers, so we don't have any issues with division by zero here. 2⊗3 is 4/3, which is fine, and then 1⊗(4/3) is 3/4. Correct.\n\nAlternatively, maybe there's a different way to approach the problem? Let me try expanding both expressions using variables to see if there's a pattern.\n\nLet's denote the first expression: (a⊗b)⊗c. Using the definition:\n\nFirst, compute a⊗b = a²/b.\n\nThen, take that result and ⊗ with c: (a²/b)⊗c = ( (a²/b)² ) / c = a⁴ / (b² c).\n\nNow, the second expression: a⊗(b⊗c). First compute b⊗c = b²/c.\n\nThen, a⊗(b²/c) = a² / (b²/c) = a² * (c / b²) = (a² c) / b².\n\nTherefore, the difference between the two expressions is:\n\n(a⁴ / (b² c)) - (a² c / b²) = (a⁴ - a² c²) / (b² c) = a² (a² - c²) / (b² c).\n\nHmm, factoring that, it's a² (a - c)(a + c) / (b² c).\n\nBut in our specific problem, a = 1, b = 2, c = 3. Plugging those values in:\n\n1² (1 - 3)(1 + 3) / (2² * 3) = 1 * (-2)(4) / (4 * 3) = (-8) / 12 = -2/3. Same result. So that confirms the answer is indeed -2/3.\n\nTherefore, I think my initial calculation was correct, and the answer is option A.\n\n**Final Answer**\n\\boxed{A}\n\n<|end_of_thought|>\n\n<|begin_of_solution|>\n\nTo determine the value of \\([(1 \\otimes 2) \\otimes 3] - [1 \\otimes (2 \\otimes 3)]\\) where the operation \\(\\otimes\\) is defined by \\(a \\otimes b = \\frac{a^2}{b}\\), we proceed as follows:\n\nFirst, compute \\(1 \\otimes 2\\):\n\\[\n1 \\otimes 2 = \\frac{1^2}{2} = \\frac{1}{2}\n\\]\nNext, use this result to compute \\((1 \\otimes 2) \\otimes 3\\):\n\\[\n\\left(\\frac{1}{2}\\right) \\otimes 3 = \\frac{\\left(\\frac{1}{2}\\right)^2}{3} = \\frac{\\frac{1}{4}}{3} = \\frac{1}{12}\n\\]\n\nNow, compute \\(2 \\otimes 3\\):\n\\[\n2 \\otimes 3 = \\frac{2^2}{3} = \\frac{4}{3}\n\\]\nThen, use this result to compute \\(1 \\otimes (2 \\otimes 3)\\):\n\\[\n1 \\otimes \\left(\\frac{4}{3}\\right) = \\frac{1^2}{\\frac{4}{3}} = \\frac{1}{\\frac{4}{3}} = \\frac{3}{4}\n\\]\n\nFinally, find the difference between the two results:\n\\[\n\\frac{1}{12} - \\frac{3}{4} = \\frac{1}{12} - \\frac{9}{12} = \\frac{1 - 9}{12} = \\frac{-8}{12} = -\\frac{2}{3}\n\\]\n\nThus, the answer is \\(\\boxed{A}\\).\n\n<|end_of_solution|>" + print(extract_answer(text)) + +def get_dataloader_by_config(tokenizer, config, global_config=None, max_length=1024): + """Select different data loaders based on config file""" + if global_config is None: + global_config = config + + training_mode = global_config.get('training_mode', 'dream') + + # Add reference to global config for data loading functions to access + config._parent = global_config + + if training_mode == 'llada': + return get_llada_bs17k_dataloader(tokenizer, config, max_length) + elif training_mode == 'dream': + return get_bs17k_dataloader(tokenizer, config, max_length) + else: + raise ValueError(f"Unsupported training mode: {training_mode}") \ No newline at end of file diff --git a/utils/generation.py b/utils/generation.py new file mode 100644 index 0000000000000000000000000000000000000000..61c4d9030c2f7c1d27cea67e831293ffe7abede0 --- /dev/null +++ b/utils/generation.py @@ -0,0 +1,144 @@ +import torch +import torch.nn.functional as F +import torch.distributions as dists +from peft import PeftModel, PeftConfig +def build_custom_float_attention_mask(input_ids, prompt_length, block_size, device=None): + B,seq_len= input_ids.shape + # 初始化为全 -inf + attn_mask = torch.full((B,1,seq_len, seq_len), float('-inf'), dtype=torch.float32, device=device) + # 1. Prompt部分:每个token可以注意整个prompt + for i in range(B): + attn_mask[i,:,:,:prompt_length[i]] = 0.0 # 允许所有 token 看 prompt + + # 2. 块划分:从 prompt_length 开始划分 block + num_blocks = (seq_len - prompt_length[i] + block_size - 1) // block_size + + for b in range(num_blocks): + block_start = prompt_length[i] + b * block_size + # print(block_start,block_size,seq_len) + block_end = min(block_start + block_size, seq_len) + + # 块内全注意 + attn_mask[i,:,block_start:block_end, block_start:block_end] = 0.0 + + # 块之间因果注意(只能看前面块) + for prev_b in range(b): + prev_start = prompt_length[i] + prev_b * block_size + prev_end = min(prev_start + block_size, seq_len) + + # 当前块可以看前面块 + attn_mask[i,:,block_start:block_end, prev_start:prev_end] = 0.0 + + return attn_mask +def top_p_logits(logits, top_p=None): + sorted_logits, sorted_indices = torch.sort(logits, descending=True) + cumulative_probs = torch.cumsum(F.softmax(sorted_logits, dim=-1), dim=-1) + sorted_indices_to_remove = cumulative_probs > top_p + # Shift the indices to the right to keep the first token above the threshold + sorted_indices_to_remove[..., 1:] = sorted_indices_to_remove[..., :-1].clone() + sorted_indices_to_remove[..., 0] = 0 + + mask = torch.zeros_like(logits, dtype=torch.bool, device=logits.device) + mask = mask.scatter_(-1, sorted_indices, sorted_indices_to_remove) + logits = logits.masked_fill(mask, torch.finfo(logits.dtype).min) + return logits + +def top_k_logits(logits, top_k=None): + top_k = min(top_k, logits.size(-1)) # Safety check + # Remove all tokens with a probability less than the last token of the top-k + indices_to_remove = logits < torch.topk(logits, top_k)[0][..., -1, None] + logits = logits.masked_fill(indices_to_remove, torch.finfo(logits.dtype).min) + return logits + +def sample_tokens(logits, temperature=0.0, top_p=None, top_k=None, margin_confidence=False, neg_entropy=False): + if temperature > 0: + logits = logits / temperature + if top_p is not None and top_p < 1: + logits = top_p_logits(logits, top_p) + if top_k is not None: + logits = top_k_logits(logits, top_k) + probs = torch.softmax(logits, dim=-1) + + if temperature > 0: + try: + x0 = dists.Categorical(probs=probs).sample() + confidence = torch.gather(probs, -1, x0.unsqueeze(-1)).squeeze(-1) + except: + confidence, x0 = probs.max(dim=-1) + else: + confidence, x0 = probs.max(dim=-1) + + if margin_confidence: + sorted_probs, _ = torch.sort(probs, dim=-1, descending=True) + # Extract top1 and top2 probabilities + top1_probs = sorted_probs[:, 0] + top2_probs = sorted_probs[:, 1] + # Calculate confidence as top1 - top2 + confidence = top1_probs - top2_probs + + if neg_entropy: + epsilon = 1e-10 + log_probs = torch.log(probs + epsilon) + confidence = torch.sum(probs * log_probs, dim=-1) + + return confidence, x0 +# def generate(model,prompt,block_size,max_length,mask_id): +# def generate(model, prompt, block_size, max_length, mask_id, eos_token_id=None): +# device = prompt.device +# output = prompt.clone() + +# while output.shape[1] < max_length: +# # 添加一个 block 的 mask +# mask_block = torch.full((1, block_size), mask_id, dtype=torch.long, device=device) +# input_ids = torch.cat([output, mask_block], dim=1) +# attention_mask = build_custom_float_attention_mask(input_ids, torch.tensor([[prompt.shape[1]]]), block_size, device=device) +# attention_mask = attention_mask.to(torch.bfloat16) +# for i in range(block_size): +def generate_block(denoiser, block_size, mask_id,tokenizer,device): + denoiser.eval() + question = 'please give me a code about transformer model' + # prompt = tokenizer(question)['input_ids'] + # prompt = torch.tensor(prompt).to(accelerator.device).unsqueeze(0) + messages = [ + {"role": "user", "content": question} + ] + prompt = tokenizer.apply_chat_template( + messages, return_tensors="pt", return_dict=True, add_generation_prompt=True + ).input_ids + prompt = prompt.to(device) + + mask_id = 151666 + gen_len = (384 - prompt.shape[1])//block_size + print(gen_len) + temperature = 0.2 + top_p = 0.95 + with torch.inference_mode(): + for i in range(gen_len): + if i==0: + x_t = torch.cat([prompt, torch.tensor([[mask_id]*block_size]).to(device)], dim=1) + else: + x_t = torch.cat([x_t, torch.tensor([[mask_id]*block_size]).to(device)], dim=1) + attention_mask = build_custom_float_attention_mask(x_t, torch.tensor([[prompt.shape[1]]]), block_size, device=device) + attention_mask = attention_mask.to(torch.bfloat16) + for n in range(block_size): + mask_index = (x_t == mask_id) + if mask_index.sum() == 0: + break + logits =denoiser(x_t, attention_mask=attention_mask).logits + logits = shift_logits(logits) + mask_logits = logits[mask_index] + confidence, x0 = sample_tokens(mask_logits, temperature, top_p=top_p, top_k=None, neg_entropy=True) + number_transfer_tokens = 1 + _, transfer_index = torch.topk(confidence, number_transfer_tokens) + x0_ = torch.zeros_like(x0, device=device, dtype=torch.long) + mask_id + x0_[transfer_index] = x0[transfer_index].clone() + x_t[mask_index] = x0_ + answer = tokenizer.batch_decode(x_t[:, prompt.shape[1]:], skip_special_tokens=False)[0] + print(answer) + answer = tokenizer.batch_decode(x_t[:, prompt.shape[1]:], skip_special_tokens=False)[0] + print(answer) + +if __name__ == "__main__": + config = PeftConfig.from_pretrained("ybelkada/opt-350m-lora") + model = AutoModelForCausalLM.from_pretrained(config.base_model_name_or_path) + lora_model = PeftModel.from_pretrained(model, "ybelkada/opt-350m-lora") \ No newline at end of file diff --git a/utils/loss.py b/utils/loss.py new file mode 100644 index 0000000000000000000000000000000000000000..4f669df78554441b135a2d4d4a655ffec8e478f1 --- /dev/null +++ b/utils/loss.py @@ -0,0 +1,195 @@ +import torch +from utils.util import forward_process_length, shift_logits,forward_process +import torch.nn.functional as F + +def compute_loss_by_config( + input_ids, + denoiser, + question_length, + mask_id, + block_size, + enable_shift, + share_steps, + self_align, + feature_align, + self_step, + eos_id, + config +): + """Select different loss functions based on config file""" + training_mode = config.get('training_mode', 'dream') + + if training_mode == 'llada': + import ipdb; ipdb.set_trace() + return compute_llada_loss( + input_ids, denoiser, question_length, mask_id, block_size, + enable_shift, share_steps, self_align, feature_align, self_step, eos_id + ) + elif training_mode == 'dream': + return compute_loss( + input_ids, denoiser, question_length, mask_id, block_size, + enable_shift, share_steps, self_align, feature_align, self_step, eos_id + ) + else: + raise ValueError(f"Unsupported training mode: {training_mode}") + +def compute_loss( + input_ids, + denoiser, + question_length, + mask_id, + block_size, + enable_shift, + share_steps, + self_align, + feature_align, + self_step, + eos_id, +): + B, L = input_ids.shape + noisy_batch, masked_indices, p_mask = forward_process_length(input_ids, mask_id=mask_id,prompt_lengths=question_length, block_size=block_size,eos_id=eos_id) + token_positions = torch.arange(L, device=noisy_batch.device).expand(B, L) + prompt_mask = (token_positions < question_length.unsqueeze(1)) + noisy_batch[prompt_mask] = input_ids[prompt_mask] + # prompt_mask = prompt_mask.to(torch.int64) + noisy_batch = noisy_batch.to(denoiser.device) + attention_mask=build_custom_float_attention_mask(noisy_batch, question_length, block_size, device=noisy_batch.device) + attention_mask=attention_mask.to(torch.float16) + logits=denoiser(noisy_batch,attention_mask=attention_mask).logits + logits=shift_logits(logits) + if self_align: + with torch.no_grad(): + with denoiser.disable_adapter(): + # ref_model = denoiser + # ref_model.eval() + # print(type(ref_model)) + # denoiser.eval() + ref_logits=denoiser(noisy_batch,attention_mask=torch.zeros([1,1,noisy_batch.shape[1],noisy_batch.shape[1]],dtype=torch.float16,device=denoiser.device)).logits + ref_logits=shift_logits(ref_logits) + ref_logits = torch.nn.functional.softmax(ref_logits, dim=-1) + # denoiser.train() + token_loss_2 = F.cross_entropy(logits[masked_indices], ref_logits[masked_indices], reduction='none') / p_mask[masked_indices] + # print("token_loss_2",token_loss_2.shape) + else: + token_loss_2= F.cross_entropy(logits[masked_indices], input_ids[masked_indices], reduction='none') / p_mask[masked_indices] + losses = { + # 'loss_1': token_loss_2.mean() * 0, + 'loss': token_loss_2.mean(), + } + + return losses +def compute_normal_loss( + input_ids, + denoiser, + question_length, + mask_id, + block_size, + enable_shift, + share_steps, + self_align, + feature_align, + self_step, + eos_id, +): + B, L = input_ids.shape + noisy_batch, masked_indices, p_mask = forward_process_length(input_ids, mask_id=mask_id,prompt_lengths=question_length, block_size=block_size,eos_id=eos_id) + token_positions = torch.arange(L, device=noisy_batch.device).expand(B, L) + prompt_mask = (token_positions < question_length.unsqueeze(1)) + noisy_batch[prompt_mask] = input_ids[prompt_mask] + # prompt_mask = prompt_mask.to(torch.int64) + noisy_batch = noisy_batch.to(denoiser.device) + logits=denoiser(noisy_batch).logits + logits=shift_logits(logits) + token_loss_2= F.cross_entropy(logits[masked_indices], input_ids[masked_indices], reduction='none') / p_mask[masked_indices] + losses = { + # 'loss_1': token_loss_2.mean() * 0, + 'loss': token_loss_2.mean(), + } + + return losses +import torch +def compute_llada_loss( + input_ids, + denoiser, + question_length, + mask_id, + block_size, + enable_shift, + share_steps, + self_align, + feature_align, + self_step, + eos_id, +): + mask_id=126336 + B, L = input_ids.shape + noisy_batch, masked_indices, p_mask = forward_process_length(input_ids, mask_id=mask_id,prompt_lengths=question_length, block_size=block_size,eos_id=eos_id) + token_positions = torch.arange(L, device=noisy_batch.device).expand(B, L) + prompt_mask = (token_positions < question_length.unsqueeze(1)) + noisy_batch[prompt_mask] = input_ids[prompt_mask] + # prompt_mask = prompt_mask.to(torch.int64) + noisy_batch = noisy_batch.to(denoiser.device) + # print(noisy_batch) + import ipdb; ipdb.set_trace() + attention_mask=build_custom_float_attention_mask(noisy_batch, question_length, block_size, device=noisy_batch.device) + attention_mask=attention_mask.to(torch.float16) + # print(type(denoiser),noisy_batch.shape,attention_mask.shape) + logits=denoiser(noisy_batch,attention_bias=attention_mask).logits + # logits=shift_logits(logits) + if self_align: + with torch.no_grad(): + with denoiser.disable_adapter(): + # ref_model = denoiser + # ref_model.eval() + # print(type(ref_model)) + ref_logits=denoiser(noisy_batch,attention_bias=torch.zeros([1,1,noisy_batch.shape[1],noisy_batch.shape[1]],dtype=torch.float16,device=denoiser.device)).logits + # ref_logits=shift_logits(ref_logits) + ref_logits = torch.nn.functional.softmax(ref_logits, dim=-1) + token_loss_2 = F.cross_entropy(logits[masked_indices], ref_logits[masked_indices], reduction='none') / p_mask[masked_indices] + # print("token_loss_2",token_loss_2.shape) + else: + token_loss_2= F.cross_entropy(logits[masked_indices], input_ids[masked_indices], reduction='none') / p_mask[masked_indices] + losses = { + # 'loss_1': token_loss_2.mean() * 0, + 'loss': token_loss_2.mean(), + } + + return losses + + +def build_custom_float_attention_mask(input_ids, prompt_length, block_size, device=None): + B,seq_len= input_ids.shape + # 初始化为全 -inf + attn_mask = torch.full((B,1,seq_len, seq_len), float('-inf'), dtype=torch.float32, device=device) + # 1. Prompt部分:每个token可以注意整个prompt + for i in range(B): + attn_mask[i,:,:,:prompt_length[i]] = 0.0 # 允许所有 token 看 prompt + + # 2. 块划分:从 prompt_length 开始划分 block + num_blocks = (seq_len - prompt_length[i] + block_size - 1) // block_size + + for b in range(num_blocks): + block_start = prompt_length[i] + b * block_size + # print(block_start,block_size,seq_len) + block_end = min(block_start + block_size, seq_len) + + # 块内全注意 + attn_mask[i,:,block_start:block_end, block_start:block_end] = 0.0 + + # 块之间因果注意(只能看前面块) + for prev_b in range(b): + prev_start = prompt_length[i] + prev_b * block_size + prev_end = min(prev_start + block_size, seq_len) + + # 当前块可以看前面块 + attn_mask[i,:,block_start:block_end, prev_start:prev_end] = 0.0 + + return attn_mask # [seq_len, seq_len], float, 0.0 for allowed, -inf for disallowed +if __name__ == "__main__": + seq_len = 10 + input_ids = torch.randint(0, 100, (2, seq_len)) # 示例输入 + block_size = 4 + prompt_length = torch.tensor([2, 4]) # 示例prompt长度 + device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') + attn_mask = build_custom_float_attention_mask(input_ids, prompt_length, block_size, device) + print(attn_mask) \ No newline at end of file diff --git a/utils/model.py b/utils/model.py new file mode 100644 index 0000000000000000000000000000000000000000..8e1870621df61acddf0f6ba4a38bfe610c6544dc --- /dev/null +++ b/utils/model.py @@ -0,0 +1,59 @@ +import transformers +from transformers import AutoModel, AutoTokenizer +from peft import LoraConfig,get_peft_model +from model.modeling_llada import LLaDAModelLM +from model.configuration_llada import LLaDAConfig + +def get_model_by_config(config): + """Select different models based on config file""" + training_mode = config.get('training_mode', 'dream') + + if training_mode == 'llada': + return get_llada(config) + elif training_mode == 'dream': + return get_model(config) + else: + raise ValueError(f"Unsupported training mode: {training_mode}") + +def get_model(config): + # Use path from config, use default path if no config + model_path = config.paths.model if hasattr(config, 'paths') and hasattr(config.paths, 'model') else "/home/wx/data/model/Dream-org/Dream-v0-Base-7B" + + model = AutoModel.from_pretrained(model_path, trust_remote_code=True) + # print(model.named_modules()) + # print(model,"model + for param in model.parameters(): + param.requires_grad = False + tokenizer=AutoTokenizer.from_pretrained(model_path, trust_remote_code=True) + peft_config = LoraConfig(r=32, lora_alpha=32, lora_dropout=0.1,target_modules=["q_proj", "v_proj","k_proj", "o_proj"],) + model = get_peft_model(model, peft_config) + model.print_trainable_parameters() + return model, tokenizer + +def get_llada(config): + # Use path from config, use default path if no config + model_path = config.paths.model if hasattr(config, 'paths') and hasattr(config.paths, 'model') else "/data1/xck/models/llada-8b-instruct" + + config_obj=LLaDAConfig.from_pretrained(model_path) + model = LLaDAModelLM.from_pretrained(model_path,config=config_obj) + # print(model.named_modules()) + # print(model,"model + # print(model) + # exit() + for param in model.parameters(): + param.requires_grad = False + tokenizer=AutoTokenizer.from_pretrained(model_path, trust_remote_code=True) + peft_config = LoraConfig(r=32, lora_alpha=32, lora_dropout=0.1,target_modules=["q_proj", "v_proj","k_proj", "attn_out"],) + model = get_peft_model(model, peft_config) + model.print_trainable_parameters() + return model, tokenizer +# def create_attention_mask(input_ids, mask_id): +# """ +# Create an attention mask based on the input_ids and mask_id. + +# Args: +# input_ids (torch.Tensor): The input tensor of shape (batch_size, sequence_length). +# mask_id (int): The ID of the mask token. + +# Returns: +# torch.Tensor: The attention mask of shape (batch_size, sequence_length, sequence_length). diff --git a/utils/util.py b/utils/util.py new file mode 100644 index 0000000000000000000000000000000000000000..51fa32927ce7514024f2e98466a19980823373cd --- /dev/null +++ b/utils/util.py @@ -0,0 +1,159 @@ +import torch +from torch.distributions import Uniform + +def forward_process_block_fixed_p(x, mask_id, p_mask): + B, L = x.shape + if isinstance(p_mask, float): + p_mask = torch.full((B, 1), p_mask, device=x.device) + elif p_mask.ndim == 1: + p_mask = p_mask[:, None] + rand = torch.rand((B, L), device=x.device) + mask = rand < p_mask + x_masked = torch.where(mask, mask_id, x) + return x_masked, mask + +import torch + +def generate_monotonic_pmasks(batch_size, max_blocks, device): + """ + 生成 shape (B, max_blocks) 的单调非降随机序列,每行第一个元素在[0,1]随机,后续不小于前一个 + """ + # 第一个block p_mask随机 + p0 = torch.rand(batch_size, 1, device=device)/2+0.2 + # print(p0) + # 后续blocks生成增量 [0, 1],加起来保证不超过1(之后用 clamp) + increments = torch.rand(batch_size, max_blocks - 1, device=device) * (0.7 - p0)/ (max_blocks - 1) + # print(increments) + # 逐元素累加,保证非降 + cum_increments = torch.cumsum(increments, dim=1) + # print(cum_increments) + # 总 p_mask = p0 + 累积增量,保证不超过1 + p_masks = torch.cat([p0, p0 + cum_increments], dim=1) + p_masks = torch.clamp(p_masks, max=1.0) + # print(p_masks) + return p_masks # (B, max_blocks) + + +def forward_process_length(input_ids, mask_id, block_size, prompt_lengths,eos_id=None): + """ + Args: + input_ids: (B, L) + prompt_lengths: (B,) + Returns: + noisy_batch, masked_indices, p_mask_tensor + """ + B, L = input_ids.shape + device = input_ids.device + noisy_batch = input_ids.clone() + eos_indices= (input_ids==eos_id) + masked_indices = torch.zeros_like(input_ids,dtype=torch.bool) + p_mask_tensor = torch.zeros((B, L), device=device) + + # 计算每个样本block数 + non_prompt_lens = L - prompt_lengths + full_blocks = non_prompt_lens // block_size + remainders = non_prompt_lens % block_size + total_blocks = full_blocks + (remainders > 0).long() + + max_blocks = total_blocks.max().item() + + # 生成每个样本block的mask比率,单调非降且第一个随机 + p_masks = generate_monotonic_pmasks(B, max_blocks, device) # shape (B, max_blocks) + + for i in range(B): + prompt_len = prompt_lengths[i].item() + num_blocks = total_blocks[i].item() + start_block = torch.tensor([0]) # 随机选择一个block开始 + for block_idx in range(num_blocks): + if block_idx < start_block: + continue + start = prompt_len + block_idx * block_size + end = min(start + block_size, L) + + p_block = p_masks[i, block_idx-start_block].item() + + block = noisy_batch[i, start:end].unsqueeze(0) + masked_block, mask = forward_process_block_fixed_p(block, mask_id, p_block) + + noisy_batch[i, start:end] = masked_block.squeeze(0) + masked_indices[i, start:end] = mask.squeeze(0) + # if torch.all(input_ids[i, start:end] == eos_id): + # masked_indices[i,start:end]== False + # print("1") + + p_mask_tensor[i, start:end] = p_block + + return noisy_batch, masked_indices, p_mask_tensor + +# def forward_process_length(input_ids, mask_id, block_size, prompt_lengths, p_min=0.2, p_max=0.9): +# """ +# 返回每个 token 的实际 mask 概率 tensor(非prompt区域),其余为0。 +# """ +# B, L = input_ids.shape +# device = input_ids.device +# noisy_batch = input_ids.clone() +# masked_indices = torch.zeros_like(input_ids, dtype=torch.bool) +# p_mask_tensor = torch.zeros((B, L), device=device) # 最终返回值 + +# for i in range(B): +# prompt_len = prompt_lengths[i].item() +# non_prompt_len = L - prompt_len +# full_blocks = non_prompt_len // block_size +# remainder = non_prompt_len % block_size +# total_blocks = full_blocks + (1 if remainder > 0 else 0) + +# for block_idx in range(total_blocks): +# start = prompt_len + block_idx * block_size +# end = min(start + block_size, L) + +# # block的 mask 概率(线性递增) +# if total_blocks > 1: +# p_block = p_min + (p_max - p_min) * (block_idx / (total_blocks - 1)) +# else: +# p_block = p_max + +# block = noisy_batch[i, start:end].unsqueeze(0) +# masked_block, mask = forward_process_block_fixed_p(block, mask_id, p_block) +# noisy_batch[i, start:end] = masked_block.squeeze(0) +# masked_indices[i, start:end] = mask.squeeze(0) + +# # 记录 p_mask 到 tensor 中 +# p_mask_tensor[i, start:end] = p_block + +# return noisy_batch, masked_indices, p_mask_tensor +def forward_process(input_ids,mask_id ,t_max=1.0, eps=1e-4): + B, L = input_ids.shape + # t = torch.rand(B, device=input_ids.device) + dist = Uniform(0., t_max) + t = dist.sample((B,)).to(input_ids.device) + p_mask = (1 - eps) * t + eps + p_mask = p_mask[:, None].repeat(1, L) + masked_indices = torch.rand((B, L), device=input_ids.device) < p_mask + noisy_batch = torch.where(masked_indices, mask_id, input_ids) + + return noisy_batch, masked_indices, p_mask +def flatten_dict(d, parent_key='', sep='_'): + items = [] + for k, v in d.items(): + new_key = f"{parent_key}{sep}{k}" if parent_key else k + if isinstance(v, dict): + items.extend(flatten_dict(v, new_key, sep=sep).items()) + else: + items.append((new_key, v)) + return dict(items) + +def shift_logits(logits): + shifted_logits = torch.zeros_like(logits) + shifted_logits[:, 1:, :] = logits[:, :-1, :] + shifted_logits[:, 0, :] = 1.0 + + return shifted_logits +if __name__ == '__main__': + input_ids= torch.tensor([[1,5,4,3,25,6,7,9,5,8,7,6],[1,3,8,9,7,34,6,9,5,8,7,6]]) + mask_id=0 + block_size=3 + prompt_length=torch.tensor([2,1]) + noisy_batch, masked_indices,p_mask = forward_process_length(input_ids, mask_id, block_size, prompt_length) + print("noisy_batch:", noisy_batch) + print("masked_indices:", masked_indices) + print("p_mask:", p_mask) diff --git a/wandb/run-20251017_233420-gok04idh/files/config.yaml b/wandb/run-20251017_233420-gok04idh/files/config.yaml new file mode 100644 index 0000000000000000000000000000000000000000..d3bd875b03fdcb988d1e55a5260358b73a31e80a --- /dev/null +++ b/wandb/run-20251017_233420-gok04idh/files/config.yaml @@ -0,0 +1,137 @@ +_wandb: + value: + cli_version: 0.21.1 + e: + v5oeb732dszq8c66kasrq2edo32vexuu: + args: + - --config + - config/llada.yaml + codePath: train.py + codePathLocal: train.py + cpu_count: 48 + cpu_count_logical: 96 + cudaVersion: "12.2" + disk: + /: + total: "105089261568" + used: "24850501632" + email: hebailanc@gmail.com + executable: /dss/dssmcmlfs01/pn39qo/pn39qo-dss-0000/di35qir2/miniconda3/envs/kimi-vl/bin/python3.10 + git: + commit: 427eaafb686950b785ca9e5af062b8b208f5bcd8 + remote: git@github.com:aiPenguin/Adaptive-Block-Forcing.git + gpu: NVIDIA H100 + gpu_count: 4 + gpu_nvidia: + - architecture: Hopper + cudaCores: 16896 + memoryTotal: "100485038080" + name: NVIDIA H100 + uuid: GPU-e144561a-0715-93e9-b4a6-cd045bf3f067 + - architecture: Hopper + cudaCores: 16896 + memoryTotal: "100485038080" + name: NVIDIA H100 + uuid: GPU-b4beb508-a2ab-b27c-09d0-94532cfe08fd + - architecture: Hopper + cudaCores: 16896 + memoryTotal: "100485038080" + name: NVIDIA H100 + uuid: GPU-e5086bd0-e209-fd47-a2cd-bdbf367f9fa3 + - architecture: Hopper + cudaCores: 16896 + memoryTotal: "100485038080" + name: NVIDIA H100 + uuid: GPU-2cce6846-094d-869e-7772-757c0574ce36 + host: mcml-hgx-h100-013 + memory: + total: "811304960000" + os: Linux-5.15.0-153-generic-x86_64-with-glibc2.35 + program: /dss/dssmcmlfs01/pn39qo/pn39qo-dss-0000/di35qir2/Adaptive-Block-Forcing/train.py + python: CPython 3.10.18 + root: /dss/dssmcmlfs01/pn39qo/pn39qo-dss-0000/di35qir2/Adaptive-Block-Forcing + slurm: + conf: /etc/slurm-llnl/slurm.conf + cpus_on_node: "40" + distribution: cyclic + gpus_on_node: "4" + gtids: "0" + job_cpus_per_node: "40" + job_end_time: "1760882799" + job_gid: "4314506" + job_id: "5348695" + job_name: bash + job_nodelist: mcml-hgx-h100-013 + job_partition: mcml-hgx-h100-94x4 + job_start_time: "1760709999" + job_uid: "4352171" + job_user: di35qir2 + jobid: "5348695" + launch_node_ipaddr: 10.156.116.57 + localid: "0" + mem_per_node: "307200" + nnodes: "1" + nodeid: "0" + nodelist: mcml-hgx-h100-013 + nprocs: "1" + ntasks: "1" + prio_process: "0" + procid: "0" + pty_port: "63965" + pty_win_col: "187" + pty_win_row: "50" + srun_comm_host: 10.156.116.57 + srun_comm_port: "63966" + step_gpus: 0,1,2,3 + step_id: "1" + step_launcher_port: "63966" + step_nodelist: mcml-hgx-h100-013 + step_num_nodes: "1" + step_num_tasks: "1" + step_tasks_per_node: "1" + stepid: "1" + task_pid: "186878" + tasks_per_node: "1" + topology_addr: mcml-hgx-h100-013 + topology_addr_pattern: node + umask: "0022" + startedAt: "2025-10-17T21:34:20.354304Z" + writerId: v5oeb732dszq8c66kasrq2edo32vexuu + m: [] + python_version: 3.10.18 + t: + "1": + - 1 + - 11 + - 41 + - 49 + - 51 + - 71 + - 98 + - 105 + "2": + - 1 + - 11 + - 41 + - 49 + - 51 + - 71 + - 98 + - 105 + "4": 3.10.18 + "5": 0.21.1 + "6": 4.57.0 + "12": 0.21.1 + "13": linux-x86_64 +data: + value: '{''name'': ''bs17k'', ''batch_size'': 1, ''max_length'': 1024, ''_parent'': {''training_mode'': ''llada'', ''paths'': {''model'': ''GSAI-ML/LLaDA-8B-Instruct'', ''experiment'': ''ckpt_llada_instruct'', ''data'': {''bs'': ''Lansechen/bs17k_collection_filtered_hard_maxlength600'', ''bs_easy'': ''Lansechen/bs17k_collection_filtered_easy_maxlength600''}}, ''denoiser'': {''encoder'': {''name'': ''dream'', ''mask_id'': 151666}, ''decoder'': {''wiinit'': True, ''name'': ''eagle_rope'', ''num_blocks'': 1, ''seq_len'': 1024, ''input_dim'': 3584, ''hidden_dim'': 3584, ''vocab_size'': 152064, ''block'': {''seq_len'': 1024, ''hidden_dim'': 3584, ''num_heads'': 32}}}, ''train'': {''decoder_resume_path'': None, ''head_resume_path'': None, ''skipped_keys'': None, ''global_step'': None, ''exp_name'': ''llada_ddt_maskteacher'', ''wandb_proj'': ''llada_ddt_maskteacher'', ''output_dir'': ''ddt_test'', ''logging_dir'': ''logs'', ''mixed_precision'': ''fp16'', ''gradient_accumulation_steps'': 5, ''report_to'': ''wandb'', ''block_size'': 16, ''lr'': 1e-05, ''num_iters'': 50000, ''eval_every'': 100000, ''save_every'': 1000, ''enable_shift'': True, ''share_steps'': 2, ''self_align'': True, ''feature_align'': False, ''self_step'': True}, ''data'': {''name'': ''bs17k'', ''batch_size'': 1, ''max_length'': 1024}}}' +denoiser: + value: '{''encoder'': {''name'': ''dream'', ''mask_id'': 151666}, ''decoder'': {''wiinit'': True, ''name'': ''eagle_rope'', ''num_blocks'': 1, ''seq_len'': 1024, ''input_dim'': 3584, ''hidden_dim'': 3584, ''vocab_size'': 152064, ''block'': {''seq_len'': 1024, ''hidden_dim'': 3584, ''num_heads'': 32}}}' +device_count: + value: 1 +paths: + value: '{''model'': ''GSAI-ML/LLaDA-8B-Instruct'', ''experiment'': ''ckpt_llada_instruct'', ''data'': {''bs'': ''Lansechen/bs17k_collection_filtered_hard_maxlength600'', ''bs_easy'': ''Lansechen/bs17k_collection_filtered_easy_maxlength600''}}' +train: + value: '{''decoder_resume_path'': None, ''head_resume_path'': None, ''skipped_keys'': None, ''global_step'': None, ''exp_name'': ''llada_ddt_maskteacher'', ''wandb_proj'': ''llada_ddt_maskteacher'', ''output_dir'': ''ddt_test'', ''logging_dir'': ''logs'', ''mixed_precision'': ''fp16'', ''gradient_accumulation_steps'': 5, ''report_to'': ''wandb'', ''block_size'': 16, ''lr'': 1e-05, ''num_iters'': 50000, ''eval_every'': 100000, ''save_every'': 1000, ''enable_shift'': True, ''share_steps'': 2, ''self_align'': True, ''feature_align'': False, ''self_step'': True}' +training_mode: + value: llada diff --git a/wandb/run-20251017_233420-gok04idh/files/requirements.txt b/wandb/run-20251017_233420-gok04idh/files/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..a597473a49e2e77a6a9de6e1a0851c6bc5e6cd36 --- /dev/null +++ b/wandb/run-20251017_233420-gok04idh/files/requirements.txt @@ -0,0 +1,258 @@ +pybind11_global==2.13.6 +nltk==3.9.1 +charset-normalizer==3.4.3 +py-cpuinfo==9.0.0 +safehttpx==0.1.6 +lxml==6.0.1 +asttokens==3.0.0 +aiofiles==24.1.0 +nvidia-cuda-runtime-cu12==12.8.90 +ffmpy==0.6.1 +safetensors==0.6.2 +gradio==5.49.0 +anyio==4.11.0 +pyarrow==21.0.0 +blake3==1.0.7 +validators==0.35.0 +sympy==1.14.0 +xgrammar==0.1.25 +openai-harmony==0.0.4 +ptyprocess==0.7.0 +gmpy2==2.2.1 +timm==1.0.20 +dnspython==2.8.0 +openai==2.1.0 +kiwisolver==1.4.9 +httpcore==1.0.9 +pfzy==0.3.4 +lark==1.2.2 +pyasn1_modules==0.4.2 +jsonschema-specifications==2025.9.1 +Jinja2==3.1.6 +timeout-decorator==0.5.0 +pybind11==2.13.6 +httptools==0.6.4 +pydantic_core==2.33.2 +pydantic_core==2.41.4 +fsspec==2025.7.0 +gitdb==4.0.12 +aiosignal==1.4.0 +nvidia-cufft-cu12==11.3.3.83 +mistral_common==1.8.5 +frozenlist==1.8.0 +platformdirs==4.3.8 +portalocker==3.2.0 +prometheus-fastapi-instrumentator==7.1.0 +cachetools==6.2.0 +partial-json-parser==0.2.1.1.post6 +multiprocess==0.70.16 +starlette==0.48.0 +pyzmq==27.1.0 +certifi==2025.8.3 +inquirerpy==0.3.4 +nvidia-cublas-cu12==12.8.4.1 +rpds-py==0.27.1 +torchaudio==2.8.0 +pydantic==2.11.7 +pydantic==2.12.2 +fastrlock==0.8.3 +async-timeout==5.0.1 +annotated-types==0.7.0 +annotated-types==0.6.0 +sentry-sdk==2.35.0 +antlr4-python3-runtime==4.9.3 +nvidia-nvjitlink-cu12==12.8.93 +contourpy==1.3.2 +imageio==2.37.0 +gradio_client==1.13.3 +hjson==3.1.0 +protobuf==6.32.0 +yarl==1.21.0 +wcwidth==0.2.13 +soxr==1.0.0 +hydra-core==1.3.2 +six==1.17.0 +diskcache==5.6.3 +python-multipart==0.0.20 +parso==0.8.5 +tqdm==4.67.1 +nvidia-curand-cu12==10.3.9.90 +rignore==0.7.0 +lm-format-enforcer==0.11.3 +h11==0.16.0 +et_xmlfile==2.0.0 +astor==0.8.1 +google-auth==2.41.1 +vllm==0.11.0 +pycparser==2.23 +nvidia-ml-py==12.535.133 +pyparsing==3.2.4 +jsonschema==4.25.1 +frozendict==2.4.6 +gguf==0.17.1 +typing-inspection==0.4.2 +typing-inspection==0.4.1 +tzdata==2025.2 +cloudpickle==3.1.1 +transformers==4.57.0 +pexpect==4.9.0 +ninja==1.13.0 +python-json-logger==4.0.0 +deepspeed==0.18.0 +decorator==5.2.1 +filelock==3.17.0 +filelock==3.19.1 +email-validator==2.3.0 +watchfiles==1.1.0 +referencing==0.36.2 +typing_extensions==4.15.0 +nvidia-cusparse-cu12==12.5.8.93 +multidict==6.6.4 +lpips==0.1.4 +datasets==4.1.1 +urllib3==2.5.0 +soundfile==0.13.1 +torchvision==0.23.0 +openpyxl==3.1.5 +mdurl==0.1.2 +attrdict==2.0.1 +torch==2.8.0 +cffi==2.0.0 +scipy==1.15.3 +networkx==3.4.2 +numba==0.61.2 +pyasn1==0.6.1 +stack-data==0.6.3 +depyf==0.19.0 +huggingface-hub==0.34.4 +fastapi==0.118.0 +prompt_toolkit==3.0.51 +sniffio==1.3.1 +rich-toolkit==0.15.1 +msgspec==0.19.0 +pure_eval==0.2.3 +nvidia-cusolver-cu12==11.7.3.90 +GitPython==3.1.45 +janus==1.0.0 +pillow==11.3.0 +xformers==0.0.32.post1 +wandb==0.21.1 +opencv-python-headless==4.12.0.88 +qwen-vl-utils==0.0.11 +peft==0.17.1 +semantic-version==2.10.0 +cycler==0.12.1 +rsa==4.9.1 +requests==2.32.5 +llguidance==0.7.30 +distro==1.9.0 +fastapi-cloud-cli==0.3.0 +groovy==0.1.2 +idna==3.10 +MarkupSafe==3.0.2 +pycountry==24.6.1 +regex==2025.7.34 +tomli==2.2.1 +jedi==0.19.2 +tomlkit==0.13.3 +opencv-python==4.12.0.88 +omegaconf==2.3.0 +google-genai==1.41.0 +rich==14.1.0 +ipython==8.37.0 +packaging==25.0 +sentencepiece==0.2.1 +executing==2.2.1 +psutil==7.0.0 +joblib==1.5.1 +ipdb==0.13.13 +pycryptodomex==3.23.0 +outlines_core==0.2.11 +xxhash==3.6.0 +tokenizers==0.22.1 +wheel==0.45.1 +pydantic-extra-types==2.10.5 +nvidia-cuda-cupti-cu12==12.8.90 +av==15.0.0 +jiter==0.11.0 +fastapi-cli==0.0.13 +msgpack==1.1.1 +orjson==3.11.3 +numpy==2.2.6 +mpmath==1.3.0 +sty==1.0.6 +einops==0.8.1 +uvicorn==0.37.0 +Brotli==1.1.0 +flash_attn==2.8.3 +nvidia-nccl-cu12==2.27.3 +nvidia-nvtx-cu12==12.8.90 +compressed-tensors==0.11.0 +setuptools==78.1.1 +setproctitle==1.3.7 +cupy-cuda12x==13.6.0 +nvidia-cufile-cu12==1.13.1.3 +uvloop==0.21.0 +ray==2.49.2 +tabulate==0.9.0 +markdown-it-py==4.0.0 +fonttools==4.60.0 +tiktoken==0.11.0 +matplotlib-inline==0.1.7 +matplotlib==3.10.6 +Pygments==2.19.2 +triton==3.4.0 +click==8.2.1 +prometheus_client==0.23.1 +pydub==0.25.1 +cbor2==5.7.0 +traitlets==5.14.3 +pybase64==1.4.2 +smmap==5.0.2 +ruff==0.13.3 +attrs==25.3.0 +exceptiongroup==1.3.0 +tenacity==9.1.2 +typer==0.19.2 +nvidia-cuda-nvrtc-cu12==12.8.93 +optree==0.14.1 +pytz==2025.2 +aiohappyeyeballs==2.6.1 +python-dateutil==2.9.0.post0 +pandas==2.3.2 +python-dotenv==1.1.1 +shellingham==1.5.4 +blobfile==3.0.0 +interegular==0.3.3 +httpx==0.28.1 +aiohttp==3.12.15 +dotenv==0.9.9 +pip==25.1 +propcache==0.4.0 +PyYAML==6.0.2 +dill==0.4.0 +accelerate==1.10.0 +json_repair==0.52.0 +nvidia-cusparselt-cu12==0.7.1 +llvmlite==0.44.0 +hf-xet==1.1.8 +nvidia-cudnn-cu12==9.10.2.21 +xlsxwriter==3.2.9 +websockets==15.0.1 +BPE==0.1.0 +jaraco.collections==5.1.0 +packaging==24.2 +importlib_metadata==8.0.0 +tomli==2.0.1 +backports.tarfile==1.2.0 +typing_extensions==4.12.2 +jaraco.context==5.3.0 +typeguard==4.3.0 +autocommand==2.2.2 +jaraco.text==3.12.1 +more-itertools==10.3.0 +platformdirs==4.2.2 +wheel==0.45.1 +inflect==7.3.1 +jaraco.functools==4.0.1 +zipp==3.19.2 diff --git a/wandb/run-20251017_233420-gok04idh/files/wandb-metadata.json b/wandb/run-20251017_233420-gok04idh/files/wandb-metadata.json new file mode 100644 index 0000000000000000000000000000000000000000..61f95427f14338faf3243f30108e3f2613c34ca6 --- /dev/null +++ b/wandb/run-20251017_233420-gok04idh/files/wandb-metadata.json @@ -0,0 +1,111 @@ +{ + "os": "Linux-5.15.0-153-generic-x86_64-with-glibc2.35", + "python": "CPython 3.10.18", + "startedAt": "2025-10-17T21:34:20.354304Z", + "args": [ + "--config", + "config/llada.yaml" + ], + "program": "/dss/dssmcmlfs01/pn39qo/pn39qo-dss-0000/di35qir2/Adaptive-Block-Forcing/train.py", + "codePath": "train.py", + "codePathLocal": "train.py", + "git": { + "remote": "git@github.com:aiPenguin/Adaptive-Block-Forcing.git", + "commit": "427eaafb686950b785ca9e5af062b8b208f5bcd8" + }, + "email": "hebailanc@gmail.com", + "root": "/dss/dssmcmlfs01/pn39qo/pn39qo-dss-0000/di35qir2/Adaptive-Block-Forcing", + "host": "mcml-hgx-h100-013", + "executable": "/dss/dssmcmlfs01/pn39qo/pn39qo-dss-0000/di35qir2/miniconda3/envs/kimi-vl/bin/python3.10", + "cpu_count": 48, + "cpu_count_logical": 96, + "gpu": "NVIDIA H100", + "gpu_count": 4, + "disk": { + "/": { + "total": "105089261568", + "used": "24850501632" + } + }, + "memory": { + "total": "811304960000" + }, + "gpu_nvidia": [ + { + "name": "NVIDIA H100", + "memoryTotal": "100485038080", + "cudaCores": 16896, + "architecture": "Hopper", + "uuid": "GPU-e144561a-0715-93e9-b4a6-cd045bf3f067" + }, + { + "name": "NVIDIA H100", + "memoryTotal": "100485038080", + "cudaCores": 16896, + "architecture": "Hopper", + "uuid": "GPU-b4beb508-a2ab-b27c-09d0-94532cfe08fd" + }, + { + "name": "NVIDIA H100", + "memoryTotal": "100485038080", + "cudaCores": 16896, + "architecture": "Hopper", + "uuid": "GPU-e5086bd0-e209-fd47-a2cd-bdbf367f9fa3" + }, + { + "name": "NVIDIA H100", + "memoryTotal": "100485038080", + "cudaCores": 16896, + "architecture": "Hopper", + "uuid": "GPU-2cce6846-094d-869e-7772-757c0574ce36" + } + ], + "cudaVersion": "12.2", + "slurm": { + "conf": "/etc/slurm-llnl/slurm.conf", + "cpus_on_node": "40", + "distribution": "cyclic", + "gpus_on_node": "4", + "gtids": "0", + "job_cpus_per_node": "40", + "job_end_time": "1760882799", + "job_gid": "4314506", + "job_id": "5348695", + "job_name": "bash", + "job_nodelist": "mcml-hgx-h100-013", + "job_partition": "mcml-hgx-h100-94x4", + "job_start_time": "1760709999", + "job_uid": "4352171", + "job_user": "di35qir2", + "jobid": "5348695", + "launch_node_ipaddr": "10.156.116.57", + "localid": "0", + "mem_per_node": "307200", + "nnodes": "1", + "nodeid": "0", + "nodelist": "mcml-hgx-h100-013", + "nprocs": "1", + "ntasks": "1", + "prio_process": "0", + "procid": "0", + "pty_port": "63965", + "pty_win_col": "187", + "pty_win_row": "50", + "srun_comm_host": "10.156.116.57", + "srun_comm_port": "63966", + "step_gpus": "0,1,2,3", + "step_id": "1", + "step_launcher_port": "63966", + "step_nodelist": "mcml-hgx-h100-013", + "step_num_nodes": "1", + "step_num_tasks": "1", + "step_tasks_per_node": "1", + "stepid": "1", + "task_pid": "186878", + "tasks_per_node": "1", + "topology_addr": "mcml-hgx-h100-013", + "topology_addr_pattern": "node", + "umask": "0022" + }, + "writerId": "v5oeb732dszq8c66kasrq2edo32vexuu" +} \ No newline at end of file diff --git a/wandb/run-20251017_233420-gok04idh/files/wandb-summary.json b/wandb/run-20251017_233420-gok04idh/files/wandb-summary.json new file mode 100644 index 0000000000000000000000000000000000000000..7d427799bc6c27f399cf47fce23d117def941589 --- /dev/null +++ b/wandb/run-20251017_233420-gok04idh/files/wandb-summary.json @@ -0,0 +1 @@ +{"_wandb":{"runtime":57122},"_runtime":57122} \ No newline at end of file diff --git a/wandb/run-20251017_233420-gok04idh/run-gok04idh.wandb b/wandb/run-20251017_233420-gok04idh/run-gok04idh.wandb new file mode 100644 index 0000000000000000000000000000000000000000..d85ec9d116a9fdc6623fda501341fa8d30b30242 --- /dev/null +++ b/wandb/run-20251017_233420-gok04idh/run-gok04idh.wandb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d0c1a2b3ba9492379f15478f5517c0059f5196c3deb72fbb6e56a6a2e8538cec +size 7718607